diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index a4a941e6d..000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: sunli829 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 249d8ca51..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: 'Bug Report' -about: 'Report a new bug' -title: '' -labels: bug ---- - -## Expected Behavior - - -## Actual Behavior - - -## Steps to Reproduce the Problem - -1. -2. -3. - -## Specifications - -- Version: -- Platform: -- Subsystem: \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index cd10703bb..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -name: 'Feature Request' -about: 'Report a new feature to be implemented' -title: '<Title>' -labels: enhancement ---- - -## Description of the feature - - -## Code example (if possible) \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index eb08e1042..000000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: 'Question' -about: 'If something needs clarification' -title: '<Title>' -labels: question ---- - -<!-- What is your question? Please be as specific as possible! --> \ No newline at end of file diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml deleted file mode 100644 index fbe390670..000000000 --- a/.github/workflows/book.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Book - -on: - push: - branches: - - release - paths: - - "docs/**" - -jobs: - deploy_en: - name: Deploy book on gh-pages - runs-on: ubuntu-22.04 - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install mdBook - uses: peaceiris/actions-mdbook@v1 - - name: Render book - run: | - mdbook build -d gh-pages docs/en - mdbook build -d gh-pages docs/zh-CN - mkdir gh-pages - mv docs/en/gh-pages gh-pages/en - mv docs/zh-CN/gh-pages gh-pages/zh-CN - mv docs/index.html gh-pages - - name: Deploy - uses: peaceiris/actions-gh-pages@v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - allow_empty_commit: true - keep_files: false - publish_dir: gh-pages - publish_branch: gh-pages diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index bf9e6bdc3..000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,187 +0,0 @@ -name: CI - -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - tests: - name: Run tests - Rust (${{ matrix.rust }}) on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - { rust: stable, os: ubuntu-22.04 } - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: true - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: ${{ matrix.rust }} - components: rustfmt - - name: Extract metadata - id: meta - run: | - FEATURES=$(cargo metadata --format-version=1 --no-deps | jq -r '.packages[] | select(.name == "async-graphql") | .features | keys | map(select(. != "boxed-trait")) | join(",")') - echo "features=$FEATURES" >> "$GITHUB_OUTPUT" - - name: Build with all features - run: cargo build --all-features - - name: Build with all features except boxed-trait - run: cargo build --features ${{ steps.meta.outputs.features }} - - name: Build - run: cargo build --workspace --verbose - - name: Run tests - run: cargo test --workspace --features ${{ steps.meta.outputs.features }} - - name: Clean - run: cargo clean - - tests_min_compat: - name: Run min rust version tests - Rust (${{ matrix.rust }}) on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - { rust: 1.86.0, os: ubuntu-22.04 } - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: true - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: ${{ matrix.rust }} - components: rustfmt - - name: Extract metadata - id: meta - run: | - FEATURES=$(cargo metadata --format-version=1 --no-deps | jq -r '.packages[] | select(.name == "async-graphql") | .features | keys | map(select(. != "boxed-trait")) | join(",")') - echo "features=$FEATURES" >> "$GITHUB_OUTPUT" - - name: Build with all features - run: cargo build --all-features - - name: Build with all features except boxed-trait - run: cargo build --features ${{ steps.meta.outputs.features }} - - name: Build - run: cargo build --workspace --verbose - - name: Run tests - run: cargo test --workspace --features ${{ steps.meta.outputs.features }} - - name: Clean - run: cargo clean - - rustfmt: - name: Run rustfmt - Rust (${{ matrix.rust }}) on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - { rust: nightly-2025-04-09, os: ubuntu-22.04 } - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: true - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: ${{ matrix.rust }} - components: rustfmt - - name: Check format - run: cargo +${{ matrix.rust }} fmt --all -- --check - - name: Check examples format - working-directory: ./examples - run: cargo +${{ matrix.rust }} fmt --all -- --check - - clippy: - name: Run clippy - Rust (${{ matrix.rust }}) on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - { rust: stable, os: ubuntu-22.04 } - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: true - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: ${{ matrix.rust }} - components: clippy - - name: Check with clippy - run: cargo clippy --all - - book_examples: - name: Test book examples - runs-on: ubuntu-22.04 - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: true - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - - name: Install mdBook - uses: peaceiris/actions-mdbook@v1 - with: - mdbook-version: "0.4.49" - - name: Extract metadata - id: meta - run: | - FEATURES=$(cargo metadata --format-version=1 --no-deps | jq -r '.packages[] | select(.name == "async-graphql") | .features | keys | map(select(. != "boxed-trait")) | join(",")') - echo "features=$FEATURES" >> "$GITHUB_OUTPUT" - - name: Build with all features - run: cargo build --workspace --features ${{ steps.meta.outputs.features }} - - name: Run book tests for en language - run: mdbook test -L target/debug/deps ./docs/en - - name: Run book tests for zh-CN language - run: mdbook test -L target/debug/deps ./docs/zh-CN - - name: Clean - run: cargo clean - - examples: - name: Build examples - Rust (${{ matrix.rust }}) on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - { rust: stable, os: ubuntu-22.04 } - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: true - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: ${{ matrix.rust }} - components: clippy, rustfmt - - name: Check examples with clippy - run: cargo clippy --all - working-directory: ./examples - - name: Build examples - run: cargo build --workspace --verbose - working-directory: ./examples - - name: Clean examples - run: cargo clean - working-directory: ./examples - - autocorrect: - name: AutoCorrect Lint - runs-on: ubuntu-latest - steps: - - name: Check source code - uses: actions/checkout@v4 - - - name: AutoCorrect - uses: huacnlee/autocorrect-action@v2 - with: - args: --lint --no-diff-bg-color docs/ diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml deleted file mode 100644 index 7dbc64b3f..000000000 --- a/.github/workflows/code-coverage.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Code Coverage - -on: - push: - branches: - - master - -jobs: - cover: - runs-on: ubuntu-22.04 - env: - CARGO_TERM_COLOR: always - steps: - - uses: actions/checkout@v4 - - name: Install stable toolchain - uses: dtolnay/rust-toolchain@stable - with: - toolchain: 1.86.0 - - name: Rust Cache - uses: Swatinem/rust-cache@v2 - - name: Install cargo-llvm-cov - uses: taiki-e/install-action@cargo-llvm-cov - - name: Run cargo-tarpaulin - run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info - - name: Upload to codecov.io - uses: codecov/codecov-action@v4 - with: - token: ${{secrets.CODECOV_TOKEN}} - files: lcov.info - fail_ci_if_error: true - - name: Archive code coverage results - uses: actions/upload-artifact@v4 - with: - name: code-coverage-report - path: cobertura.xml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 76f49d2a3..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: Release -on: - push: - branches: - - release - paths: - - "**/Cargo.toml" - - ".github/workflows/release.yml" - -jobs: - publish: - runs-on: ubuntu-22.04 - strategy: - fail-fast: false - max-parallel: 1 - matrix: - package: - - name: async-graphql-value - registryName: async-graphql-value - path: value - - name: async-graphql-parser - registryName: async-graphql-parser - path: parser - - name: async-graphql-derive - registryName: async-graphql-derive - path: derive - - name: async-graphql - registryName: async-graphql - path: . - - name: async-graphql-actix-web - registryName: async-graphql-actix-web - path: integrations/actix-web - - name: async-graphql-warp - registryName: async-graphql-warp - path: integrations/warp - - name: async-graphql-poem - registryName: async-graphql-poem - path: integrations/poem - - name: async-graphql-axum - registryName: async-graphql-axum - path: integrations/axum - - name: async-graphql-rocket - registryName: async-graphql-rocket - path: integrations/rocket - - name: async-graphql-tide - registryName: async-graphql-tide - path: integrations/tide - steps: - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - - name: Checkout - uses: actions/checkout@v4 - - 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.CRATES_TOKEN }} - - name: cargo package - if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION - working-directory: ${{ matrix.package.path }} - run: | - cargo package - echo "We will publish:" $PACKAGE_VERSION - echo "This is current latest:" $PUBLISHED_VERSION - - name: Publish ${{ matrix.package.name }} - if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION - working-directory: ${{ matrix.package.path }} - run: | - echo "# Cargo Publish" - cargo publish --no-verify diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 3a7306c44..000000000 --- a/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -/target -Cargo.lock -.idea -.vscode -.DS_Store -node_modules - -benchmark/target diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index b0fb2eee0..000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "examples"] - path = examples - url = https://github.com/async-graphql/examples diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/.rustfmt.toml b/.rustfmt.toml deleted file mode 100644 index 364029d1d..000000000 --- a/.rustfmt.toml +++ /dev/null @@ -1,12 +0,0 @@ -edition = "2024" -newline_style = "Unix" -# comments -normalize_comments = true -wrap_comments = true -format_code_in_doc_comments = true -# imports -imports_granularity = "Crate" -group_imports = "StdExternalCrate" -# report -#report_fixme="Unnumbered" -#report_todo="Unnumbered" diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md deleted file mode 100644 index e09b5fac1..000000000 --- a/ARCHITECTURE.md +++ /dev/null @@ -1,42 +0,0 @@ -# async-graphql Architecture - -This document describes the internal architecture of `async-graphql`, and can be useful to -people wanting to contribute. - -## Schema - -When you create a schema, the first thing it does it asks the query, mutation and subscription types -to register themselves in the schema's list of GraphQL types called the **registry**. Those types -will then recursively register all the types that they depend on in the registry, and so on until -every single type that is used has been registered in the registry. - -## Query Execution - -First of all, `async-graphql` will use the `async-graphql-parser` crate (located in the `parser/` -directory) to parse the request document source. This also performs some necessary validations -such as making sure that operation (i.e. query/mutation/subscription) names are unique and that the -query does not contain an anonymous operation as well as a named one. - -It then will validate the document as per the rest of GraphQL's validation rules. The `validation/` -module handles this, and it works by walking through the entire document once and notifying the -validation rules on the way to perform their validations and report errors if necessary. If -`ValidationMode::Fast` is turned on, far far fewer rules are used. - -Also at this stage some non-validators use the same architecture, such as the query depth calculator -which keeps track of how deeply nested the query gets as the document is walked through. - -At this point all the unnecessary operations (ones not selected by `operationName`) are dropped, and -we will execute just one. - -At the core of all the resolver logic there are two traits: `InputType` and `OutputType` -which represent a GraphQL input value and GraphQL output value respectively. `InputType` just -requires conversions to and from `async_graphql::Value`. `OutputType` is an async trait with a -single method, `resolve`, which takes a field (e.g. `user(name: "sunli829") { display_name }`) and -resolves it to a single value. - -Scalars and enums are expected to ignore the input and serialize themselves, while objects, -interfaces and unions are expected to read the selection set in the field and resolve and serialize -each one of their fields. - -As implementing `OutputType::resolve` manually quickly becomes very tedious helpful utilities -are provided in the `resolver_utils` module and via macros. diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 02e5a3b25..000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,1044 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -# [7.0.17] 2025-05-24 - -- update MSRV to `1.86.0` -- Allow exporting SDL with spaces [#1688](https://github.com/async-graphql/async-graphql/pull/1688) -- Update GraphiQLSource to use React v18 [#1705](https://github.com/async-graphql/async-graphql/pull/1705) -- fix: generate description of directives. [#1681](https://github.com/async-graphql/async-graphql/pull/1681) -- feat: add @requiresScopes support [#1695](https://github.com/async-graphql/async-graphql/pull/1695) -- chore: fix clippy and fmt errors [#1713](https://github.com/async-graphql/async-graphql/pull/1713) -- use preferred mime-type [#1714](https://github.com/async-graphql/async-graphql/pull/1714) -- Add GraphiQLSource version [#1704](https://github.com/async-graphql/async-graphql/pull/1704) - -# [7.0.16] 2025-03-20 - -- dynamic: fixed `__typename` always returned `null` when introspection was disabled. -- update MSRV to `1.83.0` - -# [7.0.15] 2025-02-03 - -- feat: Add `custom-error-conversion` feature [#1631](https://github.com/async-graphql/async-graphql/pull/1631) -- Update graphql annotation directive property to support paths [#1663](https://github.com/async-graphql/async-graphql/pull/1663) - -# [7.0.14] 2025-01-22 - -- Update error messages for character length validation [#1657](https://github.com/async-graphql/async-graphql/pull/1657) -- Upgrade to axum v0.8 [#1653](https://github.com/async-graphql/async-graphql/pull/1653) -- Fix position calculator for unicode symbols [#1648](https://github.com/async-graphql/async-graphql/pull/1648) - -# [7.0.13] 2024-12-10 - -- add support introspection inputValueDeprecation [#1621](https://github.com/async-graphql/async-graphql/issues/1621) - -# [7.0.12] 2024-12-08 - -- update MSRV to `1.83.0` -- Add specified complexity for fields in `SimpleObject`. -- feat: expose SDL export utilities in ExtensionContext [#1606](https://github.com/async-graphql/async-graphql/pull/1606) -- feat(dynamic-schema): specify type directives in schema [#1607](https://github.com/async-graphql/async-graphql/pull/1607) -- Make http2 optional for actix [#1612](https://github.com/async-graphql/async-graphql/pull/1612) -- chore: use std OnceLock instead LazyLock [#1613](https://github.com/async-graphql/async-graphql/pull/1613) -- Add UUID validator [#1588](https://github.com/async-graphql/async-graphql/pull/1588) -- Update secrecy and support new types [#1627](https://github.com/async-graphql/async-graphql/pull/1627) -- fix [#1626](https://github.com/async-graphql/async-graphql/issues/1626) -- Allow non-native concrete types in generic structs deriving SimpleObject + InputObject [#1629](https://github.com/async-graphql/async-graphql/pull/1629) -- chore: update opentelemetry to 0.27 [#1614](https://github.com/async-graphql/async-graphql/pull/1614) -- connection: Allow 'first' and 'last' parameters to exist at the same time [#1602](https://github.com/async-graphql/async-graphql/pull/1602) -- feat(dynamic-schema): specify type directives in schema [#1607](https://github.com/async-graphql/async-graphql/pull/1607) -- Make boxed_any and borrowed_any for FieldValue work with trait objects again [#1636](https://github.com/async-graphql/async-graphql/pull/1636) -- Add new altair option [#1642](https://github.com/async-graphql/async-graphql/pull/1642) -- Fix Clippy for latest stable [#1639](https://github.com/async-graphql/async-graphql/pull/1639) -- Add `boxed-trait` feature [#1641](https://github.com/async-graphql/async-graphql/pull/1641) -- Support directive in complex object [#1644](https://github.com/async-graphql/async-graphql/pull/1644) - -# [7.0.11] 2024-09-26 - -- fix [#1598](https://github.com/async-graphql/async-graphql/issues/1598) - -# [7.0.10] 2024-09-24 - -- add `SchemeBuilder.limit_directives` method to set the maximum number of directives on a single field. -- remove needless ?Sized [#1593](https://github.com/async-graphql/async-graphql/pull/1593) -- fix: generate each variant description correctly. [#1589](https://github.com/async-graphql/async-graphql/pull/1589) -- Make `From<T>` for [Error] set source [#1561](https://github.com/async-graphql/async-graphql/pull/1561) -- feat(graphiql): add support for WS connection params [#1597](https://github.com/async-graphql/async-graphql/pull/1597) - -# [7.0.9] 2024-09-02 - -- add `on_ping` callback to `WebSocket` - -# [7.0.8] 2024-09-01 - -- chore: Make Extensions nullable [#1563](https://github.com/async-graphql/async-graphql/pull/1563) -- expose `rejection` in `async_graphql_axum` [#1571](https://github.com/async-graphql/async-graphql/pull/1571) -- Updated crate `time` to `3.36`, as it fixes a compilation error in rust `1.80` [#1572](https://github.com/async-graphql/async-graphql/pull/1572) -- Impl `Debug` for `dynamic::FieldValue` & Improve error messages for its methods [#1582](https://github.com/async-graphql/async-graphql/pull/1582) -- Support scraping `#[doc = ...]` attributes when generating descriptions [#1581](https://github.com/async-graphql/async-graphql/pull/1581) -- add `Websocket::keepalive_timeout` method to sets a timeout for receiving an acknowledgement of the keep-alive ping. - -# [7.0.7] 2024-07-14 - -- Support raw values from serde_json [#1554](https://github.com/async-graphql/async-graphql/pull/1554) -- The custom directive `ARGUMENT_DEFINITION` is not being output at the appropriate location in SDL [#1559](https://github.com/async-graphql/async-graphql/pull/1559) -- Support for JSON extended representations of BSON ObjectId and Uuid [#1542](https://github.com/async-graphql/async-graphql/pull/1542) -- feat: get directives from SelectionField [#1548](https://github.com/async-graphql/async-graphql/pull/1548) -- Support Directives on Subscriptions [#1500](https://github.com/async-graphql/async-graphql/pull/1500) -- fix subscription err typo [#1556](https://github.com/async-graphql/async-graphql/pull/1556) - -# [7.0.6] 2024-06-08 - -- add license files to each project [#1523](https://github.com/async-graphql/async-graphql/issues/1523) -- Improve alignment of directive behavior with GraphQL spec [#1524](https://github.com/async-graphql/async-graphql/pull/1524) -- dynamic schema: pass default vals to ResolverContext [#1527](https://github.com/async-graphql/async-graphql/pull/1527) -- Add [altair](https://github.com/altair-graphql/altair) source [#1530](https://github.com/async-graphql/async-graphql/pull/1530) -- feat: Add support for using `Interface` and `OneofObject` on the same struct [#1534](https://github.com/async-graphql/async-graphql/pull/1534) - -# [7.0.5] 2024-05-09 - -- Fix compiler and clippy warnings [#1501](https://github.com/async-graphql/async-graphql/pull/1501) -- Added support for deploying to wasm targets with axum - (without subscriptions) [#1517](https://github.com/async-graphql/async-graphql/pull/1517) -- Bump opentelemetry (0.21.0 -> 0.22.0) [#1513](https://github.com/async-graphql/async-graphql/pull/1513) -- Update lru dependency [#1504](https://github.com/async-graphql/async-graphql/pull/1504) -- Support TypeDirective for ArgumentDefinition, Enum, EnumValue, InputFieldDefinition, InputObject, Interface [#1509](https://github.com/async-graphql/async-graphql/pull/1509) -- Add `display` attribute for Enum macro [#1518](https://github.com/async-graphql/async-graphql/issues/1518) - -# [7.0.3] 2024-03-16 - -- Sort schema fields & enums if required [#1475](https://github.com/async-graphql/async-graphql/pull/1475) -- Change the `type_name` of `EmptySubscription` fix [#1435](https://github.com/async-graphql/async-graphql/issues/1435) [#1475](https://github.com/async-graphql/async-graphql/pull/1475) -- add `Request::set_parsed_query` method [#1483](https://github.com/async-graphql/async-graphql/pull/1483) -- Upgrade strum to 0.26 [#1485](https://github.com/async-graphql/async-graphql/pull/1485) -- Fix validation of non-nullable variables with default values [#1491](https://github.com/async-graphql/async-graphql/pull/1491) -- add `NextExecute::run_with_data` method to attach context data before execution -- feat: add registry method in dynamic::Registry [#1492](https://github.com/async-graphql/async-graphql/pull/1492) -- Allow non-scalars to be used as directive arguments [#1493](https://github.com/async-graphql/async-graphql/pull/1493) -- fix: add description to __schema introspection result [#1489](https://github.com/async-graphql/async-graphql/pull/1489) - -# [7.0.2] 2024-02-18 - -- Fix `#[derive(OneofObject)]` rejecting enums where the type comes from a macro substitution [#1473](https://github.com/async-graphql/async-graphql/pull/1473) -- Optimize object proc-macro codegen [#1470](https://github.com/async-graphql/async-graphql/pull/1470) -- Use `impl Future` instead of `async-trait` in most traits. [#1468](https://github.com/async-graphql/async-graphql/pull/1468) -- Upgrade `base64` to `0.21` [#1466](https://github.com/async-graphql/async-graphql/pull/1466) -- Standardize space between Args, Lists and Binary items [#1392](https://github.com/async-graphql/async-graphql/pull/1392) -- feat: support bigdecimal 0.4.x [#1358](https://github.com/async-graphql/async-graphql/pull/1358) - -# [7.0.1] 2024-01-21 - -- Add `Shareable` Attribute To InputObjectField [#1459](https://github.com/async-graphql/async-graphql/pull/1459) -- Feature Generic Unions [#1424](https://github.com/async-graphql/async-graphql/pull/1424) -- Address axum integration compilation error with non-Sync body [#1460](https://github.com/async-graphql/async-graphql/pull/1460) -- fix: date cursor precision string format [#1462](https://github.com/async-graphql/async-graphql/pull/1462) - -# [7.0.0] 2024-01-06 - -- upgrade to `http1` -- Feature extend ResolveInfo with field attribute [#1428](https://github.com/async-graphql/async-graphql/pull/1428) - -# [6.0.11] 2023-11-19 - -- Clean up example docs [#1411](https://github.com/async-graphql/async-graphql/pull/1411) -- Run batch requests concurrently [#1420](https://github.com/async-graphql/async-graphql/pull/1420) -- Update opentelemetry to `v0.21.x` [#1422](https://github.com/async-graphql/async-graphql/pull/1422) - -# [6.0.10] 2023-11-04 - -- bump opentelemetry `0.20.0` [#1406](https://github.com/async-graphql/async-graphql/pull/1406) -- fix check for serial [#1405](https://github.com/async-graphql/async-graphql/pull/1405) -- fixes complexity visitor -- bump Rocket from `0.5.0-rc.2` to `0.5.0-rc.4` - -# [6.0.9] 2023-10-21 - -- add support uploading files in dynamic schema [#1384](https://github.com/async-graphql/async-graphql/discussions/1384) -- Include `@composeDirective` in Federation's `_service` field and document `#[TypeDirective]` [#1400](https://github.com/async-graphql/async-graphql/pull/1400) - -# [6.0.7] 2023-09-23 - -- initialize source field in tracing extension parse_query method [#1367](https://github.com/async-graphql/async-graphql/pull/1367) -- test(variables): empty object passes but empty array fails [#1377](https://github.com/async-graphql/async-graphql/pull/1377) -- Add support for entities without a reference resolver [#1378](https://github.com/async-graphql/async-graphql/pull/1378) -- Fixes [#1356](https://github.com/async-graphql/async-graphql/pull/1356) - -# [6.0.6] 2023-09-04 - -- fixed SDL formatting for resolver argument comments regressed [#1363](https://github.com/async-graphql/async-graphql/issues/1363) - -# [6.0.5] 2023-08-20 - -- Implement exporting argument documentation [#1352](https://github.com/async-graphql/async-graphql/pull/1352) -- Add `ValueAccessor::as_value` and `ListAccessor::as_values_slice` methods [#1353](https://github.com/async-graphql/async-graphql/pull/1353) -- dynamic: fixes key not found when using entity resolver [#1362](https://github.com/async-graphql/async-graphql/issues/1362) -- fix panic in complexity visitor [#1359](https://github.com/async-graphql/async-graphql/pull/1359) -- update MSRV to `1.70.0` - -# [6.0.4] 2023-08-18 - -- Parse "repeatable" in directive definitions. [#1336](https://github.com/async-graphql/async-graphql/pull/1336) -- add support `multipart/mixed` request. [#1348](https://github.com/async-graphql/async-graphql/issues/1348) -- async-graphql-actix-web: add `GraphQL` handler. -- async-graphql-axum: add `GraphQL` service. - -# [6.0.3] 2023-08-15 - -- dynamic: fix the error that some methods of `XXXAccessor` return reference lifetimes that are smaller than expected. -- dynamic: no longer throws an error if the Query object does not contain any fields but the schema contains entities. -- chore: make accessors public and reexport indexmap [#1329](https://github.com/async-graphql/async-graphql/pull/1329) -- feat: added `OutputType` implementation for `std::sync::Weak` [#1334](https://github.com/async-graphql/async-graphql/pull/1334) - -# [6.0.1] 2023-08-02 - -- dynamic: remove `TypeRefInner` -- update MSRV to `1.67.0` - -# [6.0.0] 2023-07-29 - -- Bump `syn` from `1.0` to `2.0` -- Bump `darling` from `0.14` to `0.20` -- Bump `indexmap` from `1.6.2` to `2` -- Attributes `guard`, `process_with`, `complexity` support expression or string as value [#1295](https://github.com/async-graphql/async-graphql/issues/1295) -- Schema (type) level directive support with optional support of federation composeDirective [#1308](https://github.com/async-graphql/async-graphql/pull/1308) -- Add support for generic structs derriving InputObject and SimpleObject [#1313](https://github.com/async-graphql/async-graphql/pull/1313) -- chore: trim up some unnecessary code [#1324](https://github.com/async-graphql/async-graphql/pull/1324) -- Adds `Dataloader::get_cached_values` method to the dataloader cache so that callers can access the contents of the cache without knowing the keys. [#1326](https://github.com/async-graphql/async-graphql/pull/1326) - -## Breaking Changes - -- Since `syn 2.0` no longer supports keywords as meta path, rename the parameter used to specify interface field types from `type` to `ty`. - - https://github.com/dtolnay/syn/issues/1458 - https://github.com/TedDriggs/darling/issues/238 - -```rust -#[derive(Interface)] -#[graphql(field(name = "id", ty = "&i32"))] // rename from type to ty -enum Node { - MyObj(MyObj), -} -``` - -- Change the parameter `location` of the macro `Directive` to *PascalCase* - -```rust -// #[Directive(location = "field")] -#[Directive(location = "Field")] -pub fn lowercase() -> impl CustomDirective { - LowercaseDirective -} -``` - -# [5.0.10] 2023-06-07 - -- Upgrade opentelemetry to 0.19.0 [#1252](https://github.com/async-graphql/async-graphql/pull/1262) -- Remove internal `CursorScalar` type and expose `Edge::cursor` member [#1302](https://github.com/async-graphql/async-graphql/pull/1302) - -# [5.0.9] 2023-05-25 - -- Prevent input check stack overflow [#1293](https://github.com/async-graphql/async-graphql/pull/1293) -- Change batch requests to run concurrently [#1290](https://github.com/async-graphql/async-graphql/issues/1290) - -# [5.0.8] 2023-05-09 - -- Improve documentation on Dataloader [#1282](https://github.com/async-graphql/async-graphql/pull/1282) -- Prevent recursive input type checking from hitting stack overflow [#1284](https://github.com/async-graphql/async-graphql/pull/1284) -- update MSRV to `1.65.0` - -# [5.0.7] 2023-03-25 - -- Disable default-features in workspace.dependencies [#1232](https://github.com/async-graphql/async-graphql/pull/1232) -- Copy edit extensions section of The Book [#1234](https://github.com/async-graphql/async-graphql/pull/1234) -- disable default features for async-graphql in workspace dependencies [#1237](https://github.com/async-graphql/async-graphql/pull/1237) -- chore: make edge field and connection field shareable [#1246](https://github.com/async-graphql/async-graphql/pull/1246) -- Added 3 new fns to the ObjectAccessor. [#1244](https://github.com/async-graphql/async-graphql/pull/1244) -- Dataloader futures lose span context [#1256](https://github.com/async-graphql/async-graphql/pull/1256) -- Propagate ErrorExtensionValues when calling InputValueError.propagate [#1257](https://github.com/async-graphql/async-graphql/pull/1257) -- Correct error string for object in ValueAccessor [#1260](https://github.com/async-graphql/async-graphql/pull/1260) - -# [5.0.6] 2023-02-11 - -- docs: Tweak dataloader example and link to full example [#1194](https://github.com/async-graphql/async-graphql/pull/1194) -- docs: Mention the importance of using dataloader with federation/entities [#1194](https://github.com/async-graphql/async-graphql/pull/1194) -- chore: enable GraphiQL/Playground via feature flag [#1202](https://github.com/async-graphql/async-graphql/pull/1202) -- fix: Export directives to federation SDL so they can be composed. [#1209](https://github.com/async-graphql/async-graphql/pull/1209) -- Fix doc contents details and add AutoCorrect lint to CI. [#1210](https://github.com/async-graphql/async-graphql/pull/1210) -- fix: provide correct type for _service with dynamic schema [#1212](https://github.com/async-graphql/async-graphql/pull/1212) -- feat(subscription): support generics in MergedSubscription types [#1222](https://github.com/async-graphql/async-graphql/pull/1222) -- feat: modify Connection to allow optionally disable nodes field in gql output. [#1218](https://github.com/async-graphql/async-graphql/pull/1218) -- fixes interface type condition query [#1228](https://github.com/async-graphql/async-graphql/pull/1228) -- fixes [#1226](https://github.com/async-graphql/async-graphql/issues/1226) -- update MSRV to `1.64.0` - -# [5.0.5] 2023-01-03 - -- dynamic schema: add boxed_any function [#1179](https://github.com/async-graphql/async-graphql/pull/1179) -- Improve GraphiQL v2 [#1182](https://github.com/async-graphql/async-graphql/pull/1182) -- Fix: __Type.oneOf to __Type.isOneOf [#1188](https://github.com/async-graphql/async-graphql/pull/1188) -- Implement From<ID> for ConstValue [#1169](https://github.com/async-graphql/async-graphql/pull/1169) -- Fixes [#1192](https://github.com/async-graphql/async-graphql/issues/1192) - -# [5.0.4] 2022-12-17 - -- Fix named_list_nn [#1172](https://github.com/async-graphql/async-graphql/pull/1172) -- Add `DynamicRequestExt::root_value` to specify the root value for the request -- Change `CustomValidator::check` returns error type from `String` to `InputValueError<T>`. -- Add support that custom validators can set error extensions. [#1174](https://github.com/async-graphql/async-graphql/issues/1174) - -# [5.0.3] 2022-12-07 - -- Fixes [#1163](https://github.com/async-graphql/async-graphql/issues/1163) -- Fixes [#1161](https://github.com/async-graphql/async-graphql/issues/1161) - -# [5.0.2] 2022-11-30 - -- Fixes [#1157](https://github.com/async-graphql/async-graphql/issues/1157) - -# [5.0.1] 2022-11-29 - -- Add boolean dynamic ValueAccessor method [#1153](https://github.com/async-graphql/async-graphql/pull/1153) - -# [5.0.0] 2022-11-27 - -- Update MSRV to `1.60.0` -- [async-graphql-axum] bump axum from `0.5.1` to `0.6.0` [#1106](https://github.com/async-graphql/async-graphql/issues/1106) - -# [5.0.0-alpha.5] 2022-11-21 - -- Fixes [#1138](https://github.com/async-graphql/async-graphql/issues/1138) -- Fixes [#1140](https://github.com/async-graphql/async-graphql/issues/1140) -- Add `dynamic::Scalar::validator` method to set value validator. - -# [5.0.0-alpha.4] 2022-11-12 - -- Add support to federation(v2) for dynamic schema - -# [5.0.0-alpha.3] 2022-11-12 - -- Simplified way to create type reference `dynamic::TypeRef` - -# [5.0.0-alpha.2] 2022-11-11 - -- Keep object 'implements' order stable in SDL export [#1142](https://github.com/async-graphql/async-graphql/pull/1142) -- Fix regression on `ComplexObject` descriptions [#1141](https://github.com/async-graphql/async-graphql/pull/1141) - -# [5.0.0-alpha.1] 2022-11-10 - -- Add support for dynamic schema -- Add `tempfile` feature, enabled by default - -# [4.0.17] 2022-10-24 - -- Add support for using `Union` and `OneofObject` on the same struct [#1116](https://github.com/async-graphql/async-graphql/issues/1116) - -# [4.0.16] 2022-10-20 - -- Add credentials to GraphiQL 2 [#1105](https://github.com/async-graphql/async-graphql/pull/1105) -- Add TypeName support for InputObject [#1110](https://github.com/async-graphql/async-graphql/pull/1110) -- Fix error message [#1058](https://github.com/async-graphql/async-graphql/pull/1058) -- Add TypeName support for Enum, Union, OneofInputObject, Subscription, MergedObject, MergedSubscription, Scalar, Interface, Directive -- Fixes [#1052](https://github.com/async-graphql/async-graphql/issues/1052) -- Implement `CustomValidator<T>` for `F: Fn(&T) -> Result<(), E: Into<String>>` -- Add `validator` attribute to `InputObject` macro [#1072](https://github.com/async-graphql/async-graphql/issues/1072) - -# [4.0.15] 2022-10-07 - -- Dynamic Document Title for GraphiQL v2 and GraphQL Playground [#1099](https://github.com/async-graphql/async-graphql/pull/1099) -- Skip tracing for introspection queries. [#841](https://github.com/async-graphql/async-graphql/issues/841) -- Add `SchemaBuilder::disable_suggestions` method to disable field suggestions. [#1101](https://github.com/async-graphql/async-graphql/issues/1101) - -# [4.0.14] 2022-09-25 - -- Implement a simple approach to using the link directive. [#1060](https://github.com/async-graphql/async-graphql/pull/1060) -- docs: Update federation docs with examples of each directive. [#1080](https://github.com/async-graphql/async-graphql/pull/1080) -- Add support for parse request from query string. [#1085](https://github.com/async-graphql/async-graphql/issues/1085) - -# [4.0.13] 2022-09-09 - -- Compare to expected schema [#1048](https://github.com/async-graphql/async-graphql/pull/1048) -- docs: readme flair [#1054](https://github.com/async-graphql/async-graphql/pull/1054) -- Remove `bson-uuid` feature [#1032](https://github.com/async-graphql/async-graphql/issues/1032) -- Add `no_cache` for `cache_control` attribute [#1051](https://github.com/async-graphql/async-graphql/issues/1051) -- Resurrect code generation through tests [#1062](https://github.com/async-graphql/async-graphql/pull/1062) -- Support for primitive type in CursorType [#1049](https://github.com/async-graphql/async-graphql/pull/1049) -- Add `SDLExportOptions::include_specified_by` method to enable `specifiedBy` directive [#1065](https://github.com/async-graphql/async-graphql/issues/1065) - -# [4.0.12] 2022-08-24 - -- Update MSRV to `1.59.0` -- Support `@specifiedBy` directive in SDL export [#1041](https://github.com/async-graphql/async-graphql/pull/1041) -- Add GraphiQL v2 [#1044](https://github.com/async-graphql/async-graphql/pull/1044) -- Export SDL: consistently avoid trailing spaces [#1043](https://github.com/async-graphql/async-graphql/pull/1043) - -# [4.0.11] 2022-08-23 - -- Define `override` directive on fields [#1029](https://github.com/async-graphql/async-graphql/pull/1029) -- Add `@tag` support [#1038](https://github.com/async-graphql/async-graphql/pull/1038) -- Export SDL: avoid trailing space for scalar definitions [#1036](https://github.com/async-graphql/async-graphql/pull/1036) -- Fixes [#1039](https://github.com/async-graphql/async-graphql/issues/1039) - -# [4.0.10] 2022-08-18 - -- Fixes extension `request.data(X)` being lost in the resolver [#1018](https://github.com/async-graphql/async-graphql/pull/1018) -- Add Apollo federation `@shareable` directive support [#1025](https://github.com/async-graphql/async-graphql/pull/1025) -- Add Apollo Federation `@inaccessible` directive support [#1026](https://github.com/async-graphql/async-graphql/pull/1026) - -# [4.0.9] 2022-08-15 - -- `on_connection_init` takes `FnOnce` instead of `Fn` [#1022](https://github.com/async-graphql/async-graphql/issues/1022#issuecomment-1214575590) - -# [4.0.8] 2022-08-12 - -- Add tracing to dataloader methods when the tracing feature is enabled. [#996](https://github.com/async-graphql/async-graphql/pull/996) - -# [4.0.7] 2022-08-09 - -- Limit parser recursion depth to `64`. - -# [4.0.6] 2022-07-21 - -- Limit execution recursion depth to `32` by default. - -# [4.0.5] 2022-07-18 - -- Fix serializing of JSON default values [#969](https://github.com/async-graphql/async-graphql/issues/969) -- Bump `rocket-0.5.0-rc.1` to `rocket-0.5.0-rc.2` for `async-graphql-rocket` [#968](https://github.com/async-graphql/async-graphql/pull/968) -- Implement `Default` for `StringNumber` [#980](https://github.com/async-graphql/async-graphql/issues/980) -- Implement `Guard` for `Fn` -- Fix impossible to specify both `name` and `input_name` [#987](https://github.com/async-graphql/async-graphql/issues/987) - - -# [4.0.4] 2022-6-25 - -- Bump Actix-web from `4.0.1` to `4.1.0` -- Add a `prefer_single_line_descriptions` option on `SDLExportOptions` [#955](https://github.com/async-graphql/async-graphql/pull/955) -- Fixes [#957](https://github.com/async-graphql/async-graphql/issues/957) -- Fixes [#943](https://github.com/async-graphql/async-graphql/issues/943) - -# [4.0.3] 2022-6-20 - -- Custom error type in axum request extractor [#945](https://github.com/async-graphql/async-graphql/pull/945) -- Add nodes exposure on `ConnectionType` so nesting through edges isn't always needed. [#952](https://github.com/async-graphql/async-graphql/pull/952) -- Make email-validator optional [#950](https://github.com/async-graphql/async-graphql/pull/950) - -# [4.0.2] 2022-6-10 - -- Expose `Edge::node` to allow better testing. [#933](https://github.com/async-graphql/async-graphql/pull/933) -- Integrate with the [`bigdecimal` crate](https://crates.io/crates/bigdecimal). [#926](https://github.com/async-graphql/async-graphql/pull/926) -- Improve the examples in the book. [#940](https://github.com/async-graphql/async-graphql/pull/940) -- Fixed [#941](https://github.com/async-graphql/async-graphql/issues/941) -- Fixed [#848](https://github.com/async-graphql/async-graphql/issues/848) -- Bump `darling` from `0.13.0` to `0.14.0` [#939](https://github.com/async-graphql/async-graphql/pull/939) -- Fixed [#9461](https://github.com/async-graphql/async-graphql/issues/946) - -# [4.0.1] 2022-5-24 - -- Add `Schema::build_with_ignore_name_conflicts` method to specifies a list to ignore type conflict detection. - -# [4.0.0] 2022-5-17 - -- Implement the `ConnectionNameType` and `EdgeNameType` traits to specify GraphQL type names for `Connection` and `Edge`, which can be automatically generated using `DefaultConnectionName` and `DefaultEdgeName`. -- Add `#[non_exhaustive]` attribute to Request/Response types. -- Introduce ability to pre-parse Request's query. [#891](https://github.com/async-graphql/async-graphql/pull/891) -- Add `introspection-only` mode. [#894](https://github.com/async-graphql/async-graphql/pull/894) -- Add `bson-uuid` feature to implement `ScalarType` for `bson::Uuid`. [#875](https://github.com/async-graphql/async-graphql/pull/875) -- Bump `regex` crate from `1.4.5` to `1.5.5`. [#862](https://github.com/async-graphql/async-graphql/pull/862) -- Bump `chrono-tz` crate from `0.5.3` to `0.6.1`. [#831](https://github.com/async-graphql/async-graphql/pull/831) -- Move the pest parser code generation step into a test. [#901](https://github.com/async-graphql/async-graphql/pull/901) -- Update `log` to version `0.4.16`. [#903](https://github.com/async-graphql/async-graphql/pull/903) -- Added impl of `CursorType` for floats [#897](https://github.com/async-graphql/async-graphql/pull/897) -- Implement `OutputType` for `tokio::sync::RwLock` and `tokio::sync::Mutex`. [#896](https://github.com/async-graphql/async-graphql/pull/896) -- Bump [`uuid`](https://crates.io/crates/uuid) to `1.0.0`. [#907](https://github.com/async-graphql/async-graphql/pull/907/files) -- Add some options for exporting SDL. [#877](https://github.com/async-graphql/async-graphql/issues/877) -- Cache parsed `ExecuteDocument` in APQ. [#919](https://github.com/async-graphql/async-graphql/issues/919) -- Fixed `OneofObject` restriction on inner types being unique. [#923](https://github.com/async-graphql/async-graphql/issues/923) - -# [3.0.38] 2022-4-8 - -- Update Axum integration to Axum 0.5.1 [#883](https://github.com/async-graphql/async-graphql/pull/883) -- Support macro type in enum variant. [#884](https://github.com/async-graphql/async-graphql/pull/884) -- Introduce process_with for input object [#817](https://github.com/async-graphql/async-graphql/pull/817) -- Add `MaybeUndefined::update_to` method. [#881](https://github.com/async-graphql/async-graphql/issues/881) - -# [3.0.37] 2022-3-30 - -- Panics when the same Rust type has the same name. [#880](https://github.com/async-graphql/async-graphql/issues/880) - -# [3.0.36] 2022-3-22 - -- Generate `@deprecated` to SDL. [#874](https://github.com/async-graphql/async-graphql/issues/874) -- Expose `Connection::edges`. [#871](https://github.com/async-graphql/async-graphql/issues/871) - -# [3.0.35] 2022-3-14 - -- Make `HashMap` more generics for `InputOutput` and `OutputType`. -- Add support `group` attribute to Object/SimpleObject/ComplexObject/Subscription macros. [#838](https://github.com/async-graphql/async-graphql/issues/838) -- Fixed recursive generic input objects failing to compile. [#859](https://github.com/async-graphql/async-graphql/issues/859) -- Add `ErrorExtensionValues::get` method. [#855](https://github.com/async-graphql/async-graphql/issues/855) - -# [3.0.34] 2022-3-5 - -- Export `@oneOf` directive to SDL when Oneof type is defined. [#766](https://github.com/async-graphql/async-graphql/issues/766) - -# [3.0.33] 2022-3-4 - -- Add support for oneof field on object. [#766](https://github.com/async-graphql/async-graphql/issues/766) - -# [3.0.32] 2022-3-4 - -- Bump `Actix-web` from `4.0.0-rc.3` to `4.0.1`. - -# [3.0.31] 2022-02-17 - -- Add `OneOfObject` macro to support for oneof input object. -- Bump actix-web from `4.0.0-rc.2` to `4.0.0-rc.3`. - -# [3.0.30] 2022-2-15 - -- Implement `ScalarType` for `time::Date`. [#822](https://github.com/async-graphql/async-graphql/pull/822) - -# [3.0.29] 2022-2-6 - -- Pass context to resolvers with flatten attribute. [#813](https://github.com/async-graphql/async-graphql/pull/813) -- Add support for using both `ComplexObject` and `InputObject`. -- Bump `Actix-web` from `4.0.0-beta.19` to `4.0.0-rc.2`. - -# [3.0.28] 2022-1-30 - -- Implement `InputType` and `OutputType` for `Box<[T]>` and `Arc<[T]>`. [#805](https://github.com/async-graphql/async-graphql/issues/805) - -# [3.0.27] 2022-1-28 - -- Fix possible stack overflow in validator, thanks @quapka. - -# [3.0.26] 2022-1-26 - -- Add `skip_input` attribute to `InputObject` macro, `skip_output` attribute to `SimpleObject` macro. - -# [3.0.25] 2022-1-24 - -- Fixed some integrations overwritten HTTP headers. [#793](https://github.com/async-graphql/async-graphql/issues/793) -- Fixed variable type not checked when given a default value. [#795](https://github.com/async-graphql/async-graphql/pull/795) - -# [3.0.24] 2022-1-24 - -- Remove `'static` bound for `impl From<T> for Error`. - -# [3.0.23] 2022-1-19 - -- Bump hashbrown from `0.11.2` to `0.12.0`. -- Implement `InputType` for `Box<str>` and `Arc<str>`. [#792](https://github.com/async-graphql/async-graphql/issues/792) -- Add scalars for the `time` crate's datetime types. [#791](https://github.com/async-graphql/async-graphql/pull/791) -- Add `DataContext` trait. [#786](https://github.com/async-graphql/async-graphql/pull/786) - -## [3.0.22] 2022-1-11 - -- Add support `flatten` attribute for `SimpleObject`, `ComplexObject` and `Object` macros. [#533](https://github.com/async-graphql/async-graphql/issues/533) -- Actix integration: cbor response support + error handling improvements [#784](https://github.com/async-graphql/async-graphql/pull/784) - -## [3.0.21] 2022-1-11 - -- Add `Union` and `Interface` support for trait objects. [#780](https://github.com/async-graphql/async-graphql/issues/780) - -## [3.0.20] 2022-1-5 - -- Bump `lru` to `0.7.1`. [#773](https://github.com/async-graphql/async-graphql/pull/773) -- Align `indexmap` version to `1.6.2`. [#776](https://github.com/async-graphql/async-graphql/pull/776) -- Bump actix-web from `4.0.0-beta.18` to `4.0.0-beta.19`. -- Fix the generic `SimpleObject` can't define the lifetimes. [#774](https://github.com/async-graphql/async-graphql/issues/774) - -## [3.0.19] 2021-12-28 - -- Add `InputType` / `OutputType` support for `hashbrown` crate. -- Bump actix-web from `4.0.0-beta.14` to `4.0.0-beta.18`. [#768](https://github.com/async-graphql/async-graphql/pull/768) - -## [3.0.18] 2021-12-26 - -- Federation's `_Entity` should not be sent if empty as it's in conflict with [GraphQL Union type validation](https://spec.graphql.org/draft/#sec-Unions.Type-Validation) [#765](https://github.com/async-graphql/async-graphql/pull/765). -- Fix field guards not working on `ComplexObject`. [#767](https://github.com/async-graphql/async-graphql/issues/767) - -## [3.0.17] 2021-12-16 - -- Bump poem to `1.2.2`. - -## [3.0.16] 2021-12-16 - -- Bump poem to `1.2.1`. - -## [3.0.15] 2021-12-12 - -- Bump actix-web from `4.0.0-beta.11` to `4.0.0-beta.14`. - -## [3.0.14] 2021-12-06 - -- [async-graphql-axum] bump axum from `0.3` to `0.4`. - -## [3.0.13] 2021-12-06 - -- No longer assumes that a subscription stream that failed to resolve has ended. [#744](https://github.com/async-graphql/async-graphql/issues/744) -- Rework to implement `InputType` and `OutputType` for `HashMap` and `BTreeMap`. - -## [3.0.12] 2021-12-05 - -- Fix possible deadlock in dataloader. [#555](https://github.com/async-graphql/async-graphql/issues/555) -- Add some helper methods for `BatchRequest`. - - BatchRequest::iter - - BatchRequest::iter_mut - - BatchRequest::variables - - BatchRequest::data - - BatchRequest::disable_introspection -- Fix implicit interfaces not being exposed via the __schema introspection. [#741](https://github.com/async-graphql/async-graphql/pull/741) - -## [3.0.11] 2021-12-02 - -- Fix panic on f32-64::INFINITE/f32-64::NEG_INFINITE/f32-64::NAN output. [#735](https://github.com/async-graphql/async-graphql/issues/735) - -## [3.0.10] 2021-11-30 - -- Fix the custom validator cannot work on `Option<Vec<T>>`. - -## [3.0.9] 2021-11-30 - -- Fix the validator cannot work on `Option<Vec<T>>`. - -## [3.0.8] 2021-11-30 - -- `#[graphql(validator(list))]` no longer applies to `max_items` and `min_items`. -- Implement `InputValue`/`OutputValue` for `serde_json::Value`. -- Add support for `SmolStr` via a feature. [#730](https://github.com/async-graphql/async-graphql/pull/730) - -## [3.0.7] 2021-11-23 - -- Fix error extensions cause stack overflow. [#719](https://github.com/async-graphql/async-graphql/issues/719) - -## [3.0.6] 2021-11-19 - -- Custom directives. [Book](https://async-graphql.github.io/async-graphql/en/custom_directive.html) - -## [3.0.5] 2021-11-19 - -- Remove skipped fields from the document before executing the query. -- Add `isRepeatable` field to `__Directive` - [GraphQL - October 2021] - -## [3.0.4] 2021-11-18 - -- Remove `OutputJson` because `Json` can replace it. -- Allowed use validators on wrapper types, for example: `Option<T>`, `MaybeUnefined<T>`. - -## [3.0.3] 2021-11-18 - -- [integrations] Make `GraphQLWebSocket::new` use generic stream. -- [integrations] Add `GraphQLWebSocket::new_with_pair` method. - -## [3.0.2] 2021-11-16 - -- Add `url`, `regex` and `ip` validators. - -## [3.0.1] 2021-11-17 - -- Remove the `ctx` parameter of `CustomValidator::check`. [#710](https://github.com/async-graphql/async-graphql/issues/710) - -## [3.0.0-alpha.2] 2021-11-16 - -- Change the signature of the `connection::query` function to allow the callback to use any type that implements `Into<Error>`. -- Remove `ResolverError` and use `Error::new_with_source` instead. -- Add `ErrorExtensionValues::unset` method. -- Use the `SimpleObject` macro and the `InputObject` macro at the same time. -- Types that are not referenced will be hidden in introspection. -- Make the API of integrations is more consistent. -- Remove `async-graphql-tide`. -- Rework validators. [Book](https://async-graphql.github.io/async-graphql/en/input_value_validators.html) -- Rework guards. [Book](https://async-graphql.github.io/async-graphql/en/field_guard.html) - -## [2.11.3] 2021-11-13 - -- Implemented CursorType for i32/i64. [#701](https://github.com/async-graphql/async-graphql/pull/701) -- An error is returned when the number fails to parse. [#704](https://github.com/async-graphql/async-graphql/issues/704) -- Fix Federation entity union is empty during schema introspection. [#700](https://github.com/async-graphql/async-graphql/issues/700) - -## [2.11.2] 2021-11-11 - -- Fix the problem that `EmptyMutation` may cause when used in `MergedObject`. [#694](https://github.com/async-graphql/async-graphql/issues/694) -- If a GraphQL name conflict is detected when creating schema, it will cause panic. [#499](https://github.com/async-graphql/async-graphql/issues/499) - -## [2.11.1] 2021-11-07 - -- Add `chrono::Duration` custom scalar. [#689](https://github.com/async-graphql/async-graphql/pull/689) -- Implement `From<Option<Option<T>>>` for `MaybeUndefined<T>`. -- Add `MaybeUndefined::as_opt_ref`, `MaybeUndefined::as_opt_deref`, `MaybeUndefined::map`, `MaybeUndefined::map_value`, `MaybeUndefined::contains`, `MaybeUndefined::contains_value`, and `MaybeUndefined::transpose` methods. -- Made `MaybeUndefined::is_undefined`, `MaybeUndefined::is_null`, `MaybeUndefined::is_value`, `MaybeUndefined::value` and `MaybeUndefined::as_opt_ref` const. -- Add `ResolverError` type. [#671](https://github.com/async-graphql/async-graphql/issues/671) -- [async-graphql-axum] Bump axum from `0.2.5` to `0.3`. -- [async-graphql-poem] Export the HTTP headers in the `Context`. - -## [2.11.0] 2021-11-03 - -- Use Rust `2021` edition. -- Subscription typename - [GraphQL - October 2021] [#681](https://github.com/async-graphql/async-graphql/issues/681) -- Allow directive on variable definition - [GraphQL - October 2021] [#678](https://github.com/async-graphql/async-graphql/issues/678) -- Specified By - [GraphQL - October 2021] [#677](https://github.com/async-graphql/async-graphql/issues/677) -- Add `specified_by_url` for `Tz`, `DateTime<Tz>`, `Url`, `Uuid` and `Upload` scalars. -- Number value literal lookahead restrictions - [GraphQL - October 2021] [#685](https://github.com/async-graphql/async-graphql/issues/685) - -## [2.10.8] 2021-10-26 - -- [async-graphql-poem] Bump poem to `1.0.13`. - -## [2.10.6] 2021-10-26 - -- Add derived for object & simple object & complex object. [#667](https://github.com/async-graphql/async-graphql/pull/667) [#670](https://github.com/async-graphql/async-graphql/pull/670) -- Respect query object field order. [#612](https://github.com/async-graphql/async-graphql/issues/612) - -## [2.10.5] 2021-10-22 - -- Bump poem from `0.6.6` to `1.0.7`. - -## [2.10.4] 2021-10-22 - -- Implement `Default` for ID #659. -- Implement `ScalarType` for `bson::Bson` and `bson::Document`. [#661](https://github.com/async-graphql/async-graphql/pull/661) -- Add `CharsMinLength` and `CharsMaxLength` validators. [#656](https://github.com/async-graphql/async-graphql/pull/656) -- Fix the `Subscription` macro to work on Rust 2021. [#665](https://github.com/async-graphql/async-graphql/pull/665) - -## [2.10.3] 2021-10-12 - -- Add `visible` macro argument for union type. [#655](https://github.com/async-graphql/async-graphql/pull/655) - -## [2.10.2] 2021-09-29 - -- Add concrete names support for `Object` macro. [#633](https://github.com/async-graphql/async-graphql/issues/633) -- Add `Lookahead::selection_fields` method. [#643](https://github.com/async-graphql/async-graphql/pull/643) - -## [2.10.1] 2021-09-24 - -- Add `DataLoader::enable_all_cache` and `DataLoader::enable_cache` methods. [#642](https://github.com/async-graphql/async-graphql/issues/642) -- Change the execution order of `chain` and `race` guards. [#614](https://github.com/async-graphql/async-graphql/issues/614) -- Change log level from `error` to `info`. [#518](https://github.com/async-graphql/async-graphql/issues/518) - -## [2.10.0] 2021-09-17 - -- Add support for `graphql-ws` pings. [#635](https://github.com/async-graphql/async-graphql/issues/635) -- Add feature gate `websocket` for async-graphql-tide. [#636](https://github.com/async-graphql/async-graphql/issues/636) -- Implement GraphQL enum to `Value` conversion. [#617](https://github.com/async-graphql/async-graphql/issues/617) -- Implement `ScalarType` for `HashMap`/`BTreeMap` to use `ToString`/`FromStr`. [#585](https://github.com/async-graphql/async-graphql/issues/585) - -## [2.9.15] 2021-09-10 - -- Added Axum error handling. [#629](https://github.com/async-graphql/async-graphql/pull/629) -- Bump bson from `2.0.0-beta.1` to `2.0.0`. [#628](https://github.com/async-graphql/async-graphql/pull/628) - -## [2.9.14] 2021-09-03 - -- Add support for [serde_cbor](https://crates.io/crates/serde_cbor). [#619](https://github.com/async-graphql/async-graphql/pull/619) - -## [2.9.13] 2021-09-01 - -- Released [`Axum`](https://github.com/tokio-rs/axum) integration. [`async-graphql-axum`](https://crates.io/crates/async-graphql-axum) - -## [2.9.12] 2021-08-24 - -- Add integration for [`Poem`](https://github.com/poem-web/poem). -- Ignore items flagged `@skip` in `SelectionField` and `Lookahead`. [#605](https://github.com/async-graphql/async-graphql/pull/605) - -## [2.9.11] 2021-08-22 - -- Implement `From<MaybeUndefined<T>> for Option<Option<T>>`. [#599](https://github.com/async-graphql/async-graphql/issues/599) -- Add human readable for serializer. [#604](https://github.com/async-graphql/async-graphql/pull/604) - -## [2.9.10] 2021-08-05 - -- Change `GraphQLPlaygroundConfig::with_setting` to accept `impl Into<Value>` [#583](https://github.com/async-graphql/async-graphql/issues/583) -- Remove unnecessary unwrap in multipart handler [#594](https://github.com/async-graphql/async-graphql/pull/594) - -## [2.9.9] 2021-07-20 - -- Add binary types to `ConstValue` and `Value`. [#569](https://github.com/async-graphql/async-graphql/issues/569) -- Implemented `OutputType` for [Bytes](https://docs.rs/bytes/1.0.1/bytes/struct.Bytes.html). -- Changed Lookahead to support multiple fields. [#574](https://github.com/async-graphql/async-graphql/issues/574) -- Implement `TryFrom<&[SelectionField<'a>]>` for `Lookahead<'a>`. [#575](https://github.com/async-graphql/async-graphql/issues/575) -- Attach custom HTTP headers to the response when an error occurs. [#572](https://github.com/async-graphql/async-graphql/issues/572) -- Allow field visible to support paths. [#578](https://github.com/async-graphql/async-graphql/pull/578) -- Add `list` attribute to the input value validator. [#579](https://github.com/async-graphql/async-graphql/issues/579) - -## [2.9.8] 2021-07-12 - -- Add Extensions in Error of `InputValueValidator`. [#564](https://github.com/async-graphql/async-graphql/pull/564) - -- Fix SDL print is not stable. [#547](https://github.com/async-graphql/async-graphql/issues/547) - -## [2.9.7] 2021-07-04 - -- Add support for generic `ComplexObject`. [#562](https://github.com/async-graphql/async-graphql/pull/562) - -## [2.9.6] 2021-07-02 - -- Implement `From<SelectionField>` for `Lookahead`. [#557](https://github.com/async-graphql/async-graphql/issues/557) - -- Add Decimal scalar (from `rust_decimal` crate) [#559](https://github.com/async-graphql/async-graphql/pull/559) - -## [2.9.5] 2021-06-29 - -- Allows to get the actual field name and alias in `ResolveInfo`. [#551](https://github.com/async-graphql/async-graphql/issues/551) - -## [2.9.4] 2021-06-21 - -- Fix the bug that `MergedObject` may cause panic. [#539](https://github.com/async-graphql/async-graphql/issues/539#issuecomment-862209442) - -## [2.9.3] 2021-06-17 - -- Bump upstream crate `bson` from `v1.2.0` to `v2.0.0-beta.1`. [#516](https://github.com/async-graphql/async-graphql/pull/516) - -- Add `serial` attribute for `Object`, `SimpleObject` and `MergedObject` macros. [#539](https://github.com/async-graphql/async-graphql/issues/539) - -- Remove the `static` constraint of the `receive_body` and `receive_batch_body` functions. [#544](https://github.com/async-graphql/async-graphql/issues/544) - -- Implement `InputType` and `OutputType` for `[T; N]` array. - -## [2.9.2] 2021-06-10 - -- Allow field guards to support paths. [#536](https://github.com/async-graphql/async-graphql/issues/536) - -- Add the `operation_name` to `Extension::execute` method. [#538](https://github.com/async-graphql/async-graphql/issues/538) - -## [2.9.1] 2021-06-08 - -- Rework error propagation. [#531](https://github.com/async-graphql/async-graphql/issues/531) - -## [2.9.0] 2021-06-07 - -- Add support for returning multiple resolver errors. [#531](https://github.com/async-graphql/async-graphql/issues/531) - -- Bump upstream crate `multer` from `v1.2.2` to `v2.0.0`. - -- Aligned NaiveDateTime formatting with DateTime. [#535](https://github.com/async-graphql/async-graphql/pull/535) - -## [2.8.6] 2021-06-01 - -- Allow the ability to set GraphQL Playground settings. [#508](https://github.com/async-graphql/async-graphql/pull/508) - -- WebSocket is now generic in graphql_subscription_upgrade functions. [#530](https://github.com/async-graphql/async-graphql/pull/530) - -- Removed `Copy` trait from initializer in `graphql_subscription_with_data`. [#530](https://github.com/async-graphql/async-graphql/pull/530) - -## [2.8.5] 2021-05-11 - -- If `InputObject` contains an unnamed field, the correct error message will be given. [#498](https://github.com/async-graphql/async-graphql/issues/498) - -- Added `Websocket::with_message_stream` for client message customization. [#501](https://github.com/async-graphql/async-graphql/pull/501) - -- Added the `Secret` type using [secrecy](https://crates.io/crates/secrecy) crate. - -## [2.8.4] 2021-04-23 - -- Fix the problem that the `ComplexObject` macro cannot work due to the `secret` attribute. - -## [2.8.3] 2021-04-12 - -- Fixed an error in exporting Federation SDL. - -## [2.8.2] 2021-04-09 - -- Now when the resolver returns the `Result` type, `E` can be all types that implement `async_graphql::Into<Error>`. - -## [2.8.1] 2021-04-08 - -### Fixed - -- Fix stack overflow during Registry::create_type for recursive type while running Schema::build. [#474](https://github.com/async-graphql/async-graphql/issues/474) - -### Added - -- Add `secret` attribute for arguments, they will not appear in the log. - -```rust -#[Object] -impl Query { - async fn login(&self, username:String, #[graphql(secret)] password: String) -> i32 { - todo!() - } -} -``` - -## [2.8.0] 2021-04-05 - -### Changed - -- Rework `Extension`, now fully supports asynchronous, better to use than before, and can achieve more features, it contains a lot of changes. _(if you don't have a custom extension, it will not cause the existing code to fail to compile)_ - -### Added - -- Add `async_graphql_warp::graphql_protocol`, `async_graphql_warp::graphql_subscription_upgrade` and `async_graphql_warp::graphql_subscription_upgrade_with_data` to control WebSocket subscription more finely. - -## [2.7.4] 2021-04-02 - -- Add the `BuildHasher` generic parameter to `dataloader::HashMapCache` to allow custom hashing algorithms. [#455](https://github.com/async-graphql/async-graphql/issues/455) - -## [2.7.3] 2021-04-02 - -## Added - -- Add cache support for DataLoader. [#455](https://github.com/async-graphql/async-graphql/issues/455) -- Implements `ScalarType` for `serde_json::Value`. -- Add `SelectionField::alias` and `SelectionField::arguments` methods. - -## Fixed - -- Prevent Warp WS Close, Ping, and Pong messages from being parsed as GraphQL [#459](https://github.com/async-graphql/async-graphql/pull/459) -- Fix Schema::sdl() does not include subscription definitions. [#464](https://github.com/async-graphql/async-graphql/issues/464) - -## [2.7.2] 2021-04-01 - -## Removed - -- Remove `SchemaBuilder::override_name` method. [#437](https://github.com/async-graphql/async-graphql/issues/437) - -## Added - -- Add `name` and `visible` attributes for `Newtype` macro for define a new scalar. [#437](https://github.com/async-graphql/async-graphql/issues/437) -- `NewType` macro now also implements `From<InnerType>` and `Into<InnerType>`. - -## [2.7.1] 2021-03-31 - -- Add `Request::disable_introspection` method. [#456](https://github.com/async-graphql/async-graphql/issues/456) - -## [2.7.0] 2021-03-27 - -## Fixed - -- Fix chrono-tz integration. [#452](https://github.com/async-graphql/async-graphql/pull/452) - -## Changed - -- Rework Extension & TracingExtension & OpenTelemetryExtension - -## [2.6.5] - 2021-03-24 - -- In websocket, if the client sends `start` before `connection_init`, the connection will be immediately disconnected and return `1011` error. [#451](https://github.com/async-graphql/async-graphql/issues/451) - -## [2.6.4] - 2021-03-22 - -- Fix docs. - -## [2.6.3] - 2021-03-22 - -### Added - -- Add `extension::OpenTelemetry`. - -### Removed - -- Remove `TracingConfig`, now Request span always takes the current span as the parent, so this option is no longer needed. -- Remove `multipart` feature. - -### Changed - -- Now all features are not activated by default. - -## [2.6.2] - 2021-03-20 - -- Add `SchemaBuilder::enable_subscription_in_federation` method. [#449](https://github.com/async-graphql/async-graphql/issues/449) - -## [2.6.1] - 2021-03-19 - -- Fix tracing extension doesn't work with async code. [#448](https://github.com/async-graphql/async-graphql/issues/448) - -## [2.6.0] - 2021-03-18 - -- Add [ComplexObject](https://docs.rs/async-graphql/2.6.0/async_graphql/attr.ComplexObject.html) macro. - -## [2.5.14] - 2021-03-14 - -- Add `DataLoader::loader` method. [#441](https://github.com/async-graphql/async-graphql/issues/441) -- Fix the validation does not work on some inline fragments. - -## [2.5.13] - 2021-03-09 - -- Support generics in Subscription types. [#438](https://github.com/async-graphql/async-graphql/pull/438) - -## [2.5.12] - 2021-03-09 - -- Remove unnecessary Box from WebSocket messages. -- Export subscription type to Federation SDL. (for [GraphGate](https://github.com/async-graphql/graphgate) 😁) -- Add `extends` attribute for derive macros Subscription and MergedSubscription. -- Add `SchemaBuilder::override_name` method. [#437](https://github.com/async-graphql/async-graphql/issues/437) - -## [2.5.11] - 2021-03-07 - -- Execute `_entity` requests in parallel. [#431](https://github.com/async-graphql/async-graphql/issues/431) - -## [2.5.10] - 2021-03-06 - -- Add descriptions for the exported Federation SDL. - -## [2.5.9] - 2021-02-28 - -### Changed - -- Moved `Variables` from `async_graphql::context::Variables` to `async_graphql::Variables`. - -## [2.5.8] - 2021-02-27 - -### Added - -- Allow the `deprecation` attribute to have no reason. - - ```rust - #[derive(SimpleObject)] - struct MyObject { - #[graphql(deprecation)] - a: i32, - - #[graphql(deprecation = true)] - b: i32, - - #[graphql(deprecation = false)] - c: i32, - - #[graphql(deprecation = "reason")] - d: i32, - } - ``` - -## [2.5.7] - 2021-02-23 - -### Fixed - -- Fix the problem that the borrowing lifetime returned by the `Context::data` function is too small. - -## [2.5.6] - 2021-02-23 - -### Changed - -- When introspection is disabled, introspection related types are no longer registered. - -## [2.5.5] - 2021-02-22 - -### Added - -- Add support for Federation [nested keys](https://www.apollographql.com/docs/federation/entities/#defining-a-compound-primary-key). - -## [2.5.4] - 2021-02-15 - -### Fixed - -- Fixed the error that the directive locations `FIELD_DEFINITION` and `ENUM_VALUE` cannot be parsed. - -## [2.5.3] - 2021-02-13 - -### Fixed - -- Fixed [#409](https://github.com/async-graphql/async-graphql/issues/409) - -## [2.5.2] - 2021-02-06 - -### Added - -- Add subscription support for tide with [tide-websockets](https://crates.io/crates/tide-websockets). - -### Fixed - -- Fixed the bug that can accept subscription requests during the initialization of WebSocket. -- Fixed GraphQL over WebSocket Protocol does not support ConnectionError events. [#406](https://github.com/async-graphql/async-graphql/issues/406) diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 0f99ec8af..000000000 --- a/Cargo.toml +++ /dev/null @@ -1,163 +0,0 @@ -[package] -authors = ["sunli <scott_s829@163.com>", "Koxiaet"] -categories = ["network-programming", "asynchronous"] -description = "A GraphQL server library implemented in Rust" -documentation = "https://docs.rs/async-graphql/" -edition = "2024" -rust-version = "1.86.0" -homepage = "https://github.com/async-graphql/async-graphql" -keywords = ["futures", "async", "graphql"] -license = "MIT OR Apache-2.0" -name = "async-graphql" -readme = "README.md" -repository = "https://github.com/async-graphql/async-graphql" -version = "7.0.17" - -[features] -apollo_persisted_queries = ["lru", "sha2"] -apollo_tracing = ["chrono"] -email-validator = ["fast_chemail"] -cbor = ["serde_cbor"] -chrono-duration = ["chrono", "iso8601"] -dataloader = ["futures-channel", "lru"] -decimal = ["rust_decimal"] -default = ["email-validator", "tempfile", "playground", "graphiql"] -password-strength-validator = ["zxcvbn"] -string_number = [] -tokio-sync = ["tokio"] -tracing = ["tracinglib", "tracing-futures"] -unblock = ["blocking"] -dynamic-schema = [] -graphiql = ["handlebars"] -altair = ["handlebars", "schemars"] -playground = [] -raw_value = ["async-graphql-value/raw_value"] -uuid-validator = ["uuid"] -boxed-trait = ["async-graphql-derive/boxed-trait"] -custom-error-conversion = [] - -[[bench]] -harness = false -name = "static_schema" - -[dependencies] -async-graphql-derive.workspace = true -async-graphql-parser.workspace = true -async-graphql-value.workspace = true - -async-stream = "0.3.5" -async-trait.workspace = true -bytes.workspace = true -fnv = "1.0.7" -futures-util = { workspace = true, features = [ - "std", - "io", - "sink", - "async-await", - "async-await-macro", -] } -indexmap.workspace = true -mime = "0.3.17" -multer = "3.0.0" -num-traits = "0.2.18" -pin-project-lite = "0.2.14" -regex = "1.10.4" -serde.workspace = true -serde_json.workspace = true -static_assertions_next = "1.1.2" -thiserror.workspace = true -base64 = "0.22.0" -serde_urlencoded = "0.7.1" -http.workspace = true -futures-timer = "3.0.3" - -# Feature optional dependencies -bson = { version = "2.9.0", optional = true, features = [ - "chrono-0_4", - "uuid-1", -] } -chrono = { version = "0.4.37", optional = true, default-features = false, features = [ - "clock", - "std", -] } -chrono-tz = { version = "0.10.0", optional = true } -fast_chemail = { version = "0.9.6", optional = true } -hashbrown = { version = "0.14.5", optional = true } -iso8601 = { version = "0.6.1", optional = true } -log = { version = "0.4.21", optional = true } -opentelemetry = { version = "0.27.0", optional = true, default-features = false, features = [ - "trace", -] } -rust_decimal = { version = "1.35.0", optional = true, default-features = false } -bigdecimal = { version = ">=0.3.0, <0.5.0", optional = true, default-features = false } -secrecy = { version = "0.10.3", optional = true } -smol_str = { version = "0.3.1", optional = true } -time = { version = "0.3.36", optional = true, features = [ - "parsing", - "formatting", - "macros", -] } -tokio = { version = "1.37.0", features = ["sync"], optional = true } -tracing-futures = { version = "0.2.5", optional = true, features = [ - "std-future", - "futures-03", -] } -tracinglib = { version = "0.1.40", optional = true, package = "tracing" } -url = { version = "2.5.0", optional = true } -uuid = { version = "1.8.0", optional = true, features = ["v4", "serde"] } -tempfile = { version = "3.10.1", optional = true } - -# Non-feature optional dependencies -blocking = { version = "1.6.1", optional = true } -futures-channel = { version = "0.3.30", optional = true } -lru = { version = "0.12.3", optional = true } -serde_cbor = { version = "0.11.2", optional = true } -sha2 = { version = "0.10.8", optional = true } -zxcvbn = { version = "2.2.2", optional = true } -handlebars = { version = "5.1.2", optional = true } -schemars = { version = "0.8.21", optional = true } - -[dev-dependencies] -futures-channel = "0.3.30" -tokio = { version = "1.37.0", features = [ - "macros", - "rt-multi-thread", - "sync", - "time", -] } -criterion = { version = "0.5.1", features = ["html_reports", "async_tokio"] } -slab = "0.4.9" - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] - -[workspace] -resolver = "2" -members = [ - "value", - "parser", - "derive", - "integrations/poem", - "integrations/actix-web", - "integrations/rocket", - "integrations/warp", - "integrations/axum", - "integrations/tide", -] - -[workspace.dependencies] -async-graphql = { path = ".", version = "7.0.17", default-features = false } -async-graphql-derive = { path = "derive", version = "7.0.17" } -async-graphql-parser = { path = "parser", version = "7.0.17" } -async-graphql-value = { path = "value", version = "7.0.17" } - -serde = { version = "1.0.197", features = ["derive"] } -serde_json = "1.0.115" -indexmap = { version = "2", features = ["serde"] } -bytes = { version = "1.6.0", features = ["serde"] } -thiserror = "1.0.58" -async-trait = "0.1.79" -futures-util = { version = "0.3.30", default-features = false } -tokio-util = { version = "0.7.10", default-features = false } -http = { package = "http", version = "1.1.0" } diff --git a/LICENSE-APACHE b/LICENSE-APACHE deleted file mode 100644 index ec68b7e26..000000000 --- a/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file diff --git a/LICENSE-MIT b/LICENSE-MIT deleted file mode 100644 index b6b94c73c..000000000 --- a/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -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. \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 570966502..000000000 --- a/README.md +++ /dev/null @@ -1,239 +0,0 @@ -<div align="center"> -<samp> - -# async-graphql - -**a high-performance graphql server library that's fully specification compliant** - -</samp> - -[Book](https://async-graphql.github.io/async-graphql/en/index.html) • [中文文档](https://async-graphql.github.io/async-graphql/zh-CN/index.html) • [Docs](https://docs.rs/async-graphql) • [GitHub repository](https://github.com/async-graphql/async-graphql) • [Cargo package](https://crates.io/crates/async-graphql) - ---- - -![ci status](https://github.com/async-graphql/async-graphql/workflows/CI/badge.svg) -[![code coverage](https://codecov.io/gh/async-graphql/async-graphql/branch/master/graph/badge.svg)](https://codecov.io/gh/async-graphql/async-graphql/) -[![Unsafe Rust forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) -[![Crates.io version](https://img.shields.io/crates/v/async-graphql.svg)](https://crates.io/crates/async-graphql) -[![docs.rs docs](https://img.shields.io/badge/docs-latest-blue.svg)](https://docs.rs/async-graphql) -[![downloads](https://img.shields.io/crates/d/async-graphql.svg)](https://crates.io/crates/async-graphql) -[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/async-graphql/async-graphql/compare) - -_This crate uses `#![forbid(unsafe_code)]` to ensure everything is implemented in 100% safe Rust._ - -</div> - -## Static schema - -```rs -use std::error::Error; - -use async_graphql::{http::GraphiQLSource, EmptyMutation, EmptySubscription, Object, Schema}; -use async_graphql_poem::*; -use poem::{listener::TcpListener, web::Html, *}; - -struct Query; - -#[Object] -impl Query { - async fn howdy(&self) -> &'static str { - "partner" - } -} - -#[handler] -async fn graphiql() -> impl IntoResponse { - Html(GraphiQLSource::build().finish()) -} - -#[tokio::main] -async fn main() -> Result<(), Box<dyn Error>> { - // create the schema - let schema = Schema::build(Query, EmptyMutation, EmptySubscription).finish(); - - // start the http server - let app = Route::new().at("/", get(graphiql).post(GraphQL::new(schema))); - println!("GraphiQL: http://localhost:8000"); - Server::new(TcpListener::bind("0.0.0.0:8000")) - .run(app) - .await?; - Ok(()) -} -``` - -## Dynamic schema -Requires the `dynamic-schema` feature to be enabled. - -```rs -use std::error::Error; - -use async_graphql::{dynamic::*, http::GraphiQLSource}; -use async_graphql_poem::*; -use poem::{listener::TcpListener, web::Html, *}; - -#[handler] -async fn graphiql() -> impl IntoResponse { - Html(GraphiQLSource::build().finish()) -} - -#[tokio::main] -async fn main() -> Result<(), Box<dyn Error>> { - let query = Object::new("Query").field(Field::new( - "howdy", - TypeRef::named_nn(TypeRef::STRING), - |_| FieldFuture::new(async { "partner" }), - )); - - // create the schema - let schema = Schema::build(query, None, None).register(query).finish()?; - - // start the http server - let app = Route::new().at("/", get(graphiql).post(GraphQL::new(schema))); - println!("GraphiQL: http://localhost:8000"); - Server::new(TcpListener::bind("0.0.0.0:8000")) - .run(app) - .await?; - Ok(()) -} -``` - -## ⚠️Security - -I strongly recommend limiting the [complexity and depth](https://async-graphql.github.io/async-graphql/en/depth_and_complexity.html?highlight=complex#limiting-query-complexity) of queries in a production environment to avoid possible DDos attacks. - -- [SchemaBuilder.limit_complexity](https://docs.rs/async-graphql/latest/async_graphql/struct.SchemaBuilder.html#method.limit_complexity) -- [SchemaBuilder.limit_depth](https://docs.rs/async-graphql/latest/async_graphql/struct.SchemaBuilder.html#method.limit_depth) -- [SchemaBuilder.limit_directives](https://docs.rs/async-graphql/latest/async_graphql/struct.SchemaBuilder.html#method.limit_directives) - -## Features - -- Static and dynamic schemas are fully supported -- Fully supports async/await -- Type safety -- Rustfmt friendly (Procedural Macro) -- Custom scalars -- Minimal overhead -- Easy integration ([poem](https://crates.io/crates/poem), [axum](https://crates.io/crates/axum), [actix-web](https://crates.io/crates/actix-web), [tide](https://crates.io/crates/tide), [warp](https://crates.io/crates/warp), [rocket](https://crates.io/crates/rocket) ...) -- Upload files (Multipart request) -- Subscriptions (WebSocket transport) -- Custom extensions -- Error extensions -- Limit query complexity/depth -- Batch queries -- Apollo Persisted Queries -- Apollo Tracing extension -- Apollo Federation(v2) - -> **Note**: Minimum supported Rust version: 1.86.0 or later - -## Examples - -All examples are in the [sub-repository](https://github.com/async-graphql/examples), located in the examples directory. - -```shell -git submodule update # update the examples repo -cd examples && cargo run --bin [name] -``` - -For more information, see the [sub-repository](https://github.com/async-graphql/examples) README.md. - -## Integrations - -Integrations are what glue `async-graphql` with your web server, here are provided ones, or you can build your own! - -- Poem [async-graphql-poem](https://crates.io/crates/async-graphql-poem) -- Actix-web [async-graphql-actix-web](https://crates.io/crates/async-graphql-actix-web) -- Warp [async-graphql-warp](https://crates.io/crates/async-graphql-warp) -- Tide [async-graphql-tide](https://crates.io/crates/async-graphql-tide) -- Rocket [async-graphql-rocket](https://github.com/async-graphql/async-graphql/tree/master/integrations/rocket) -- Axum [async-graphql-axum](https://github.com/async-graphql/async-graphql/tree/master/integrations/axum) - -## Crate features - -This crate offers the following features. Most are not activated by default, except the integrations of GraphiQL (`graphiql`) and GraphQL Playground (`playground`): - -| feature | enables | -|:-------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **`apollo_tracing`** | Enable the [Apollo tracing extension](https://docs.rs/async-graphql/latest/async_graphql/extensions/struct.ApolloTracing.html). | -| **`apollo_persisted_queries`** | Enable the [Apollo persisted queries extension](https://docs.rs/async-graphql/latest/async_graphql/extensions/apollo_persisted_queries/struct.ApolloPersistedQueries.html). | -| **`boxed-trait`** | Enables [`async-trait`](https://crates.io/crates/async-trait) for all traits. | -| **`bson`** | Integrate with the [`bson` crate](https://crates.io/crates/bson). | -| **`bigdecimal`** | Integrate with the [`bigdecimal` crate](https://crates.io/crates/bigdecimal). | -| **`cbor`** | Support for [serde_cbor](https://crates.io/crates/serde_cbor). | -| **`chrono`** | Integrate with the [`chrono` crate](https://crates.io/crates/chrono). | -| **`chrono-tz`** | Integrate with the [`chrono-tz` crate](https://crates.io/crates/chrono-tz). | -| **`dataloader`** | Support [DataLoader](dataloader/struct.DataLoader.html). | -| **`decimal`** | Integrate with the [`rust_decimal` crate](https://crates.io/crates/rust_decimal). | -| **`dynamic-schema`** | Support dynamic schema | -| **`fast_chemail`** | Integrate with the [`fast_chemail` crate](https://crates.io/crates/fast_chemail). | -| **`graphiql`** | Enables the [GraphiQL IDE](https://github.com/graphql/graphiql) integration | -| **`hashbrown`** | Integrate with the [`hashbrown` crate](https://github.com/rust-lang/hashbrown). | -| **`log`** | Enable the [Logger extension](https://docs.rs/async-graphql/latest/async_graphql/extensions/struct.Logger.html). | -| **`opentelemetry`** | Enable the [OpenTelemetry extension](https://docs.rs/async-graphql/latest/async_graphql/extensions/struct.OpenTelemetry.html). | -| **`playground`** | Enables the [GraphQL playground IDE](https://github.com/graphql/graphql-playground) integration | -| **`rawvalue`** | Support raw values from [`serde_json`](https://crates.io/crates/serde_json) | -| **`secrecy`** | Integrate with the [`secrecy` crate](https://crates.io/crates/secrecy). | -| **`smol_str`** | Integrate with the [`smol_str` crate](https://crates.io/crates/smol_str). | -| **`string_number`** | Enable the [StringNumber](types/struct.StringNumber.html). | -| **`time`** | Integrate with the [`time` crate](https://github.com/time-rs/time). | -| **`tracing`** | Enable the [Tracing extension](https://docs.rs/async-graphql/latest/async_graphql/extensions/struct.Tracing.html). | -| **`tempfile`** | Save the uploaded content in the temporary file. | -| **`tokio-sync`** | Integrate with the [`tokio::sync::RwLock`](https://docs.rs/tokio/1.18.1/tokio/sync/struct.RwLock.html) and [`tokio::sync::Mutex`](https://docs.rs/tokio/1.18.1/tokio/sync/struct.Mutex.html). | -| **`unblock`** | Support [Asynchronous reader for Upload](types/struct.Upload.html) | -| **`uuid`** | Integrate with the [`uuid` crate](https://crates.io/crates/uuid). | -| **`url`** | Integrate with the [`url` crate](https://crates.io/crates/url). | - -### Observability - -One of the tools used to monitor your graphql server in production is Apollo Studio. Apollo Studio is a cloud platform that helps you build, monitor, validate, and secure your organization's data graph. -Add the extension crate [`async_graphql_apollo_studio_extension`](https://github.com/async-graphql/async_graphql_apollo_studio_extension) to make this available. - -## Who's using `async-graphql` in production? - -- [Vector](https://vector.dev/) -- [DiveDB](https://divedb.net) -- [Kairos Sports tech](https://kairostech.io/) -- [AxieInfinity](https://axieinfinity.com/) -- [Nando's](https://www.nandos.co.uk/) -- [Prima.it](https://www.prima.it/) -- [VoxJar](https://voxjar.com/) -- [Zenly](https://zen.ly/) -- [Brevz](https://brevz.io/) -- [thorndyke](https://www.thorndyke.ai/) -- [My Data My Consent](https://mydatamyconsent.com/) - -## Community Showcase - -- [rust-actix-graphql-sqlx-postgresql](https://github.com/camsjams/rust-actix-graphql-sqlx-postgresql) - Using GraphQL with Rust and Apollo Federation -- [entity-rs](https://github.com/chipsenkbeil/entity-rs) A simplistic framework based on TAO, Facebook's distributed database for Social Graph. -- [vimwiki-server](https://github.com/chipsenkbeil/vimwiki-rs/tree/master/vimwiki-server) Provides graphql server to inspect and manipulate vimwiki files. -- [Diana](https://github.com/arctic-hen7/diana) Diana is a GraphQL system for Rust that's designed to work as simply as possible out of the box, without sacrificing configuration ability. -- [cindythink](https://www.cindythink.com/) -- [sudograph](https://github.com/sudograph/sudograph) - -## Blog Posts - -- [Async GraphQL with Rust](https://formidable.com/blog/2022/async-graphql-with-rust-1/) -- [GraphQL in Rust](https://romankudryashov.com/blog/2020/12/graphql-rust/) -- [How to implement a Rust micro-service using Rocket, GraphQL, PostgreSQL](https://lionkeng.medium.com/how-to-implement-a-rust-micro-service-using-rocket-graphql-postgresql-a3f455f2ae8b) -- [Running GraphQL on Lambda with Rust](https://dylananthony.com/posts/graphql-lambda-rust) - -## References - -- [GraphQL](https://graphql.org) -- [GraphQL Multipart Request](https://github.com/jaydenseric/graphql-multipart-request-spec) -- [Multipart HTTP protocol for GraphQL subscriptions](https://www.apollographql.com/docs/router/executing-operations/subscription-multipart-protocol/) -- [GraphQL Cursor Connections Specification](https://facebook.github.io/relay/graphql/connections.htm) -- [GraphQL over WebSocket Protocol](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md) -- [Apollo Tracing](https://github.com/apollographql/apollo-tracing) -- [Apollo Federation](https://www.apollographql.com/docs/apollo-server/federation/introduction) - -## License - -Licensed under either of - -- Apache License, Version 2.0, - ([LICENSE-APACHE](./LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](./LICENSE-MIT) or http://opensource.org/licenses/MIT) - at your option. diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index c06f37028..000000000 --- a/SECURITY.md +++ /dev/null @@ -1,18 +0,0 @@ -# Security Policy - -## Supported Versions - -| Version | Supported | -|----------|--------------------| -| >= 3.0.0 | :white_check_mark: | - -## Reporting a Vulnerability - - -If you discover a vulnerability, please do the following: - -- E-mail your findings to scott_s829 [at] 163 [dot] com. -- Do not take advantage of the vulnerability or problem you have discovered, for example by downloading more data than necessary to demonstrate the vulnerability or deleting or modifying other people's data. -- Do not reveal the problem to others until it has been resolved. -- Do not use attacks on physical security, social engineering, distributed denial of service, spam or applications of third parties. -- Do provide sufficient information to reproduce the problem, so we will be able to resolve it as quickly as possible. Complex vulnerabilities may require further explanation! diff --git a/benches/static_schema.rs b/benches/static_schema.rs deleted file mode 100644 index 5e056b796..000000000 --- a/benches/static_schema.rs +++ /dev/null @@ -1,444 +0,0 @@ -use std::collections::HashMap; - -use async_graphql::{ - Context, Enum, Error, Interface, Object, OutputType, Result, - connection::{Connection, Edge, query}, - *, -}; -use criterion::{Criterion, criterion_group, criterion_main}; -use slab::Slab; - -pub struct StarWarsChar { - id: &'static str, - name: &'static str, - is_human: bool, - friends: Vec<usize>, - appears_in: Vec<Episode>, - home_planet: Option<&'static str>, - primary_function: Option<&'static str>, -} - -pub struct StarWars { - luke: usize, - artoo: usize, - chars: Slab<StarWarsChar>, - chars_by_id: HashMap<&'static str, usize>, -} - -impl StarWars { - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - let mut chars = Slab::new(); - - let luke = chars.insert(StarWarsChar { - id: "1000", - name: "Luke Skywalker", - is_human: true, - friends: vec![], - appears_in: vec![], - home_planet: Some("Tatooine"), - primary_function: None, - }); - - let vader = chars.insert(StarWarsChar { - id: "1001", - name: "Anakin Skywalker", - is_human: true, - friends: vec![], - appears_in: vec![], - home_planet: Some("Tatooine"), - primary_function: None, - }); - - let han = chars.insert(StarWarsChar { - id: "1002", - name: "Han Solo", - is_human: true, - friends: vec![], - appears_in: vec![Episode::Empire, Episode::NewHope, Episode::Jedi], - home_planet: None, - primary_function: None, - }); - - let leia = chars.insert(StarWarsChar { - id: "1003", - name: "Leia Organa", - is_human: true, - friends: vec![], - appears_in: vec![Episode::Empire, Episode::NewHope, Episode::Jedi], - home_planet: Some("Alderaa"), - primary_function: None, - }); - - let tarkin = chars.insert(StarWarsChar { - id: "1004", - name: "Wilhuff Tarkin", - is_human: true, - friends: vec![], - appears_in: vec![Episode::Empire, Episode::NewHope, Episode::Jedi], - home_planet: None, - primary_function: None, - }); - - let threepio = chars.insert(StarWarsChar { - id: "2000", - name: "C-3PO", - is_human: false, - friends: vec![], - appears_in: vec![Episode::Empire, Episode::NewHope, Episode::Jedi], - home_planet: None, - primary_function: Some("Protocol"), - }); - - let artoo = chars.insert(StarWarsChar { - id: "2001", - name: "R2-D2", - is_human: false, - friends: vec![], - appears_in: vec![Episode::Empire, Episode::NewHope, Episode::Jedi], - home_planet: None, - primary_function: Some("Astromech"), - }); - - chars[luke].friends = vec![han, leia, threepio, artoo]; - chars[vader].friends = vec![tarkin]; - chars[han].friends = vec![luke, leia, artoo]; - chars[leia].friends = vec![luke, han, threepio, artoo]; - chars[tarkin].friends = vec![vader]; - chars[threepio].friends = vec![luke, han, leia, artoo]; - chars[artoo].friends = vec![luke, han, leia]; - - let chars_by_id = chars.iter().map(|(idx, ch)| (ch.id, idx)).collect(); - Self { - luke, - artoo, - chars, - chars_by_id, - } - } - - pub fn human(&self, id: &str) -> Option<&StarWarsChar> { - self.chars_by_id - .get(id) - .copied() - .map(|idx| self.chars.get(idx).unwrap()) - .filter(|ch| ch.is_human) - } - - pub fn droid(&self, id: &str) -> Option<&StarWarsChar> { - self.chars_by_id - .get(id) - .copied() - .map(|idx| self.chars.get(idx).unwrap()) - .filter(|ch| !ch.is_human) - } - - pub fn humans(&self) -> Vec<&StarWarsChar> { - self.chars - .iter() - .filter(|(_, ch)| ch.is_human) - .map(|(_, ch)| ch) - .collect() - } - - pub fn droids(&self) -> Vec<&StarWarsChar> { - self.chars - .iter() - .filter(|(_, ch)| !ch.is_human) - .map(|(_, ch)| ch) - .collect() - } - - pub fn friends(&self, ch: &StarWarsChar) -> Vec<&StarWarsChar> { - ch.friends - .iter() - .copied() - .filter_map(|id| self.chars.get(id)) - .collect() - } -} - -/// One of the films in the Star Wars Trilogy -#[derive(Enum, Copy, Clone, Eq, PartialEq)] -pub enum Episode { - /// Released in 1977. - NewHope, - - /// Released in 1980. - Empire, - - /// Released in 1983. - Jedi, -} - -pub struct Human<'a>(&'a StarWarsChar); - -/// A humanoid creature in the Star Wars universe. -#[Object] -impl Human<'_> { - /// The id of the human. - async fn id(&self) -> &str { - self.0.id - } - - /// The name of the human. - async fn name(&self) -> &str { - self.0.name - } - - /// The friends of the human, or an empty list if they have none. - async fn friends<'ctx>(&self, ctx: &Context<'ctx>) -> Vec<Character<'ctx>> { - let star_wars = ctx.data_unchecked::<StarWars>(); - star_wars - .friends(self.0) - .into_iter() - .map(|ch| { - if ch.is_human { - Human(ch).into() - } else { - Droid(ch).into() - } - }) - .collect() - } - - /// Which movies they appear in. - async fn appears_in(&self) -> &[Episode] { - &self.0.appears_in - } - - /// The home planet of the human, or null if unknown. - async fn home_planet(&self) -> &Option<&str> { - &self.0.home_planet - } -} - -pub struct Droid<'a>(&'a StarWarsChar); - -/// A mechanical creature in the Star Wars universe. -#[Object] -impl Droid<'_> { - /// The id of the droid. - async fn id(&self) -> &str { - self.0.id - } - - /// The name of the droid. - async fn name(&self) -> &str { - self.0.name - } - - /// The friends of the droid, or an empty list if they have none. - async fn friends<'ctx>(&self, ctx: &Context<'ctx>) -> Vec<Character<'ctx>> { - let star_wars = ctx.data_unchecked::<StarWars>(); - star_wars - .friends(self.0) - .into_iter() - .map(|ch| { - if ch.is_human { - Human(ch).into() - } else { - Droid(ch).into() - } - }) - .collect() - } - - /// Which movies they appear in. - async fn appears_in(&self) -> &[Episode] { - &self.0.appears_in - } - - /// The primary function of the droid. - async fn primary_function(&self) -> &Option<&str> { - &self.0.primary_function - } -} - -pub struct QueryRoot; - -#[Object] -impl QueryRoot { - async fn hero<'a>( - &self, - ctx: &Context<'a>, - #[graphql( - desc = "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode." - )] - episode: Option<Episode>, - ) -> Character<'a> { - let star_wars = ctx.data_unchecked::<StarWars>(); - match episode { - Some(episode) => { - if episode == Episode::Empire { - Human(star_wars.chars.get(star_wars.luke).unwrap()).into() - } else { - Droid(star_wars.chars.get(star_wars.artoo).unwrap()).into() - } - } - None => Human(star_wars.chars.get(star_wars.luke).unwrap()).into(), - } - } - - async fn human<'a>( - &self, - ctx: &Context<'a>, - #[graphql(desc = "id of the human")] id: String, - ) -> Option<Human<'a>> { - ctx.data_unchecked::<StarWars>().human(&id).map(Human) - } - - async fn humans<'a>( - &self, - ctx: &Context<'a>, - after: Option<String>, - before: Option<String>, - first: Option<i32>, - last: Option<i32>, - ) -> Result<Connection<usize, Human<'a>>> { - let humans = ctx.data_unchecked::<StarWars>().humans().to_vec(); - query_characters(after, before, first, last, &humans, Human).await - } - - async fn droid<'a>( - &self, - ctx: &Context<'a>, - #[graphql(desc = "id of the droid")] id: String, - ) -> Option<Droid<'a>> { - ctx.data_unchecked::<StarWars>().droid(&id).map(Droid) - } - - async fn droids<'a>( - &self, - ctx: &Context<'a>, - after: Option<String>, - before: Option<String>, - first: Option<i32>, - last: Option<i32>, - ) -> Result<Connection<usize, Droid<'a>>> { - let droids = ctx.data_unchecked::<StarWars>().droids().to_vec(); - query_characters(after, before, first, last, &droids, Droid).await - } -} - -#[derive(Interface)] -#[graphql( - field(name = "id", ty = "&str"), - field(name = "name", ty = "&str"), - field(name = "friends", ty = "Vec<Character<'ctx>>"), - field(name = "appears_in", ty = "&[Episode]") -)] -pub enum Character<'a> { - Human(Human<'a>), - Droid(Droid<'a>), -} - -async fn query_characters<'a, F, T>( - after: Option<String>, - before: Option<String>, - first: Option<i32>, - last: Option<i32>, - characters: &[&'a StarWarsChar], - map_to: F, -) -> Result<Connection<usize, T>> -where - F: Fn(&'a StarWarsChar) -> T, - T: OutputType, -{ - query( - after, - before, - first, - last, - |after, before, first, last| async move { - let mut start = 0usize; - let mut end = characters.len(); - - if let Some(after) = after { - if after >= characters.len() { - return Ok(Connection::new(false, false)); - } - start = after + 1; - } - - if let Some(before) = before { - if before == 0 { - return Ok(Connection::new(false, false)); - } - end = before; - } - - let mut slice = &characters[start..end]; - - if let Some(first) = first { - slice = &slice[..first.min(slice.len())]; - end -= first.min(slice.len()); - } else if let Some(last) = last { - slice = &slice[slice.len() - last.min(slice.len())..]; - start = end - last.min(slice.len()); - } - - let mut connection = Connection::new(start > 0, end < characters.len()); - connection.edges.extend( - slice - .iter() - .enumerate() - .map(|(idx, item)| Edge::new(start + idx, (map_to)(item))), - ); - Ok::<_, Error>(connection) - }, - ) - .await -} - -pub const Q: &str = r#" -{ - humans { - nodes { - id - name - friends { - id - name - friends { - id - name - } - } - appearsIn - homePlanet - } - } - droids { - nodes { - id - name - friends { - id - name - friends { - id - name - } - } - appearsIn - primaryFunction - } - } -} -"#; - -fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("Static Schema", |b| { - let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription) - .data(StarWars::new()) - .finish(); - b.to_async(tokio::runtime::Runtime::new().unwrap()) - .iter(|| async { - schema.execute(Q).await.into_result().unwrap(); - }); - }); -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); diff --git a/derive/Cargo.toml b/derive/Cargo.toml deleted file mode 100644 index 88c415010..000000000 --- a/derive/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -authors = ["sunli <scott_s829@163.com>", "Koxiaet"] -categories = ["network-programming", "asynchronous"] -description = "Macros for async-graphql" -documentation = "https://docs.rs/async-graphql/" -edition = "2024" -homepage = "https://github.com/async-graphql/async-graphql" -keywords = ["futures", "async", "graphql"] -license = "MIT OR Apache-2.0" -name = "async-graphql-derive" -repository = "https://github.com/async-graphql/async-graphql" -version = "7.0.17" - -[lib] -proc-macro = true - -[features] -boxed-trait = [] - -[dependencies] -async-graphql-parser.workspace = true - -Inflector = "0.11.4" -darling = "0.20.10" -proc-macro-crate = "3.1.0" -proc-macro2 = "1.0.79" -quote = "1.0.35" -syn = { version = "2.0", features = ["extra-traits", "visit-mut", "visit"] } -thiserror.workspace = true -strum = { version = "0.26.2", features = ["derive"] } diff --git a/derive/LICENSE-APACHE b/derive/LICENSE-APACHE deleted file mode 100644 index f8e5e5ea0..000000000 --- a/derive/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file diff --git a/derive/LICENSE-MIT b/derive/LICENSE-MIT deleted file mode 100644 index 468cd79a8..000000000 --- a/derive/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -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. \ No newline at end of file diff --git a/derive/src/args.rs b/derive/src/args.rs deleted file mode 100644 index 3e3e3a390..000000000 --- a/derive/src/args.rs +++ /dev/null @@ -1,1060 +0,0 @@ -#![allow(dead_code)] - -use darling::{ - FromDeriveInput, FromField, FromMeta, FromVariant, - ast::{Data, Fields, NestedMeta}, - util::{Ignored, SpannedValue}, -}; -use inflector::Inflector; -use quote::format_ident; -use syn::{ - Attribute, Expr, GenericParam, Generics, Ident, Lit, LitBool, LitStr, Meta, Path, Type, - Visibility, -}; - -use crate::validators::Validators; - -#[derive(FromMeta, Clone)] -#[darling(default)] -pub struct CacheControl { - public: bool, - private: bool, - pub no_cache: bool, - pub max_age: usize, -} - -impl Default for CacheControl { - fn default() -> Self { - Self { - public: true, - private: false, - no_cache: false, - max_age: 0, - } - } -} - -impl CacheControl { - pub fn is_public(&self) -> bool { - !self.private && self.public - } -} - -#[derive(Debug)] -pub enum DefaultValue { - Default, - Value(Lit), -} - -impl FromMeta for DefaultValue { - fn from_word() -> darling::Result<Self> { - Ok(DefaultValue::Default) - } - - fn from_value(value: &Lit) -> darling::Result<Self> { - Ok(DefaultValue::Value(value.clone())) - } -} - -#[derive(Debug, Clone)] -pub enum Visible { - None, - HiddenAlways, - FnName(Path), -} - -impl FromMeta for Visible { - fn from_value(value: &Lit) -> darling::Result<Self> { - match value { - Lit::Bool(LitBool { value: true, .. }) => Ok(Visible::None), - Lit::Bool(LitBool { value: false, .. }) => Ok(Visible::HiddenAlways), - Lit::Str(str) => Ok(Visible::FnName(syn::parse_str::<Path>(&str.value())?)), - _ => Err(darling::Error::unexpected_lit_type(value)), - } - } -} - -pub struct PathList(pub Vec<Path>); - -impl FromMeta for PathList { - fn from_list(items: &[NestedMeta]) -> darling::Result<Self> { - let mut res = Vec::new(); - for item in items { - match item { - NestedMeta::Meta(Meta::Path(p)) => res.push(p.clone()), - NestedMeta::Lit(Lit::Str(s)) => { - res.push(syn::parse_str::<Path>(&s.value()).map_err(|_| { - darling::Error::custom(format!("Invalid path: {}", s.value())) - })?) - } - _ => return Err(darling::Error::custom("Invalid path list")), - } - } - Ok(PathList(res)) - } -} - -#[derive(Default)] -pub struct GenericParamList(pub Vec<GenericParam>); - -impl FromMeta for GenericParamList { - fn from_list(items: &[NestedMeta]) -> darling::Result<Self> { - let mut res = Vec::new(); - for item in items { - match item { - NestedMeta::Lit(Lit::Str(s)) => { - res.push(syn::parse_str::<GenericParam>(&s.value()).map_err(|_| { - darling::Error::custom(format!("Invalid GenericParam: {}", s.value())) - })?) - } - _ => return Err(darling::Error::custom("Invalid GenericParamList")), - } - } - Ok(GenericParamList(res)) - } -} - -#[derive(FromMeta)] -pub struct ConcreteType { - pub name: String, - #[darling(default)] - pub input_name: Option<String>, - pub params: PathList, - #[darling(default)] - pub bounds: GenericParamList, -} - -#[derive(Debug, Clone, Default)] -pub enum Deprecation { - #[default] - NoDeprecated, - Deprecated { - reason: Option<String>, - }, -} - -impl FromMeta for Deprecation { - fn from_word() -> darling::Result<Self> { - Ok(Deprecation::Deprecated { reason: None }) - } - - fn from_value(value: &Lit) -> darling::Result<Self> { - match value { - Lit::Bool(LitBool { value: true, .. }) => Ok(Deprecation::Deprecated { reason: None }), - Lit::Bool(LitBool { value: false, .. }) => Ok(Deprecation::NoDeprecated), - Lit::Str(str) => Ok(Deprecation::Deprecated { - reason: Some(str.value()), - }), - _ => Err(darling::Error::unexpected_lit_type(value)), - } - } -} - -#[derive(Debug, Clone, Default)] -pub enum Resolvability { - #[default] - Resolvable, - Unresolvable { - key: Option<String>, - }, -} - -impl FromMeta for Resolvability { - fn from_word() -> darling::Result<Self> { - Ok(Resolvability::Unresolvable { key: None }) - } - - fn from_value(value: &Lit) -> darling::Result<Self> { - match value { - Lit::Bool(LitBool { value: true, .. }) => Ok(Resolvability::Unresolvable { key: None }), - Lit::Bool(LitBool { value: false, .. }) => Ok(Resolvability::Resolvable), - Lit::Str(str) => Ok(Resolvability::Unresolvable { - key: Some(str.value()), - }), - _ => Err(darling::Error::unexpected_lit_type(value)), - } - } -} - -#[derive(FromField)] -#[darling(attributes(graphql), forward_attrs(doc))] -pub struct SimpleObjectField { - pub ident: Option<Ident>, - pub ty: Type, - pub vis: Visibility, - pub attrs: Vec<Attribute>, - - #[darling(default)] - pub skip: bool, - #[darling(default)] - pub skip_output: bool, - // for InputObject - #[darling(default)] - pub skip_input: bool, - #[darling(default)] - pub name: Option<String>, - #[darling(default)] - pub deprecation: Deprecation, - #[darling(default)] - pub owned: bool, - #[darling(default)] - pub cache_control: CacheControl, - #[darling(default)] - pub external: bool, - #[darling(default)] - pub provides: Option<String>, - #[darling(default)] - pub requires: Option<String>, - #[darling(default)] - pub shareable: bool, - #[darling(default)] - pub inaccessible: bool, - #[darling(default, multiple, rename = "tag")] - pub tags: Vec<String>, - #[darling(default)] - pub override_from: Option<String>, - #[darling(default)] - pub guard: Option<Expr>, - #[darling(default)] - pub visible: Option<Visible>, - #[darling(default, multiple)] - pub derived: Vec<DerivedField>, - #[darling(default)] - pub process_with: Option<Expr>, - // for InputObject - #[darling(default)] - pub default: Option<DefaultValue>, - #[darling(default)] - pub default_with: Option<LitStr>, - #[darling(default)] - pub validator: Option<Validators>, - #[darling(default)] - pub flatten: bool, - #[darling(default)] - pub secret: bool, - #[darling(default, multiple, rename = "directive")] - pub directives: Vec<Expr>, - pub complexity: Option<Expr>, - #[darling(default, multiple)] - pub requires_scopes: Vec<String>, -} - -#[derive(FromDeriveInput)] -#[darling(attributes(graphql), forward_attrs(doc))] -pub struct SimpleObject { - pub ident: Ident, - pub generics: Generics, - pub attrs: Vec<Attribute>, - pub data: Data<Ignored, SimpleObjectField>, - - #[darling(default)] - pub internal: bool, - #[darling(default)] - pub fake: bool, - #[darling(default)] - pub complex: bool, - #[darling(default)] - pub name: Option<String>, - #[darling(default)] - pub name_type: bool, - #[darling(default)] - pub rename_fields: Option<RenameRule>, - #[darling(default)] - pub rename_args: Option<RenameRule>, - #[darling(default)] - pub cache_control: CacheControl, - #[darling(default)] - pub extends: bool, - #[darling(default)] - pub shareable: bool, - #[darling(default)] - pub inaccessible: bool, - #[darling(default)] - pub interface_object: bool, - #[darling(default, multiple, rename = "tag")] - pub tags: Vec<String>, - #[darling(default)] - pub visible: Option<Visible>, - #[darling(default, multiple, rename = "concrete")] - pub concretes: Vec<ConcreteType>, - #[darling(default)] - pub serial: bool, - #[darling(default, rename = "unresolvable")] - pub resolvability: Resolvability, - // for InputObject - #[darling(default)] - pub input_name: Option<String>, - #[darling(default)] - pub guard: Option<Expr>, - #[darling(default, multiple, rename = "directive")] - pub directives: Vec<Expr>, - #[darling(default, multiple)] - pub requires_scopes: Vec<String>, -} - -#[derive(FromMeta, Default)] -#[darling(default)] -pub struct Argument { - pub name: Option<String>, - pub desc: Option<String>, - pub default: Option<DefaultValue>, - pub default_with: Option<LitStr>, - pub validator: Option<Validators>, - #[darling(default)] - pub process_with: Option<Expr>, - pub key: bool, // for entity - pub visible: Option<Visible>, - pub inaccessible: bool, - #[darling(multiple, rename = "tag")] - pub tags: Vec<String>, - pub secret: bool, - #[darling(default, multiple, rename = "directive")] - pub directives: Vec<Expr>, - pub deprecation: Deprecation, -} - -#[derive(FromMeta, Default)] -#[darling(default)] -pub struct Object { - pub internal: bool, - pub name: Option<String>, - pub name_type: bool, - pub rename_fields: Option<RenameRule>, - pub rename_args: Option<RenameRule>, - pub cache_control: CacheControl, - pub extends: bool, - pub shareable: bool, - pub inaccessible: bool, - pub interface_object: bool, - #[darling(multiple, rename = "tag")] - pub tags: Vec<String>, - pub use_type_description: bool, - pub visible: Option<Visible>, - pub serial: bool, - #[darling(default, rename = "unresolvable")] - pub resolvability: Resolvability, - #[darling(multiple, rename = "concrete")] - pub concretes: Vec<ConcreteType>, - #[darling(default)] - pub guard: Option<Expr>, - #[darling(default, multiple, rename = "directive")] - pub directives: Vec<Expr>, - #[darling(default, multiple)] - pub requires_scopes: Vec<String>, -} - -#[derive(FromMeta, Default)] -#[darling(default)] -pub struct ObjectField { - pub skip: bool, - pub entity: bool, - pub name: Option<String>, - pub deprecation: Deprecation, - pub cache_control: CacheControl, - pub external: bool, - pub provides: Option<String>, - pub requires: Option<String>, - pub shareable: bool, - pub inaccessible: bool, - #[darling(multiple, rename = "tag")] - pub tags: Vec<String>, - pub override_from: Option<String>, - pub guard: Option<Expr>, - pub visible: Option<Visible>, - pub complexity: Option<Expr>, - #[darling(default, multiple)] - pub derived: Vec<DerivedField>, - pub flatten: bool, - #[darling(default, multiple, rename = "directive")] - pub directives: Vec<Expr>, - #[darling(default, multiple)] - pub requires_scopes: Vec<String>, -} - -#[derive(FromMeta, Default, Clone)] -#[darling(default)] -/// Derivied fields arguments: are used to generate derivied fields. -pub struct DerivedField { - pub name: Option<Ident>, - pub into: Option<String>, - pub with: Option<Path>, - #[darling(default)] - pub owned: Option<bool>, -} - -#[derive(FromDeriveInput)] -#[darling(attributes(graphql), forward_attrs(doc))] -pub struct Enum { - pub ident: Ident, - pub generics: Generics, - pub attrs: Vec<Attribute>, - pub data: Data<EnumItem, Ignored>, - - #[darling(default)] - pub internal: bool, - #[darling(default)] - pub name: Option<String>, - #[darling(default)] - pub display: bool, - #[darling(default)] - pub name_type: bool, - #[darling(default)] - pub rename_items: Option<RenameRule>, - #[darling(default)] - pub remote: Option<Type>, - #[darling(default)] - pub visible: Option<Visible>, - #[darling(default)] - pub inaccessible: bool, - #[darling(default, multiple, rename = "tag")] - pub tags: Vec<String>, - #[darling(default, multiple, rename = "directive")] - pub directives: Vec<Expr>, - #[darling(default, multiple)] - pub requires_scopes: Vec<String>, -} - -#[derive(FromVariant)] -#[darling(attributes(graphql), forward_attrs(doc))] -pub struct EnumItem { - pub ident: Ident, - pub attrs: Vec<Attribute>, - pub fields: Fields<Ignored>, - - #[darling(default)] - pub name: Option<String>, - #[darling(default)] - pub deprecation: Deprecation, - #[darling(default)] - pub visible: Option<Visible>, - #[darling(default)] - pub inaccessible: bool, - #[darling(default, multiple, rename = "tag")] - pub tags: Vec<String>, - #[darling(default, multiple, rename = "directive")] - pub directives: Vec<Expr>, -} - -#[derive(FromDeriveInput)] -#[darling(attributes(graphql), forward_attrs(doc))] -pub struct Union { - pub ident: Ident, - pub generics: Generics, - pub attrs: Vec<Attribute>, - pub data: Data<UnionItem, Ignored>, - - #[darling(default)] - pub internal: bool, - #[darling(default)] - pub name: Option<String>, - #[darling(default)] - pub name_type: bool, - #[darling(default)] - pub visible: Option<Visible>, - #[darling(default)] - pub inaccessible: bool, - #[darling(default, multiple, rename = "tag")] - pub tags: Vec<String>, - - #[darling(default, multiple, rename = "concrete")] - pub concretes: Vec<ConcreteType>, - - // for OneofObject - #[darling(default)] - pub input_name: Option<String>, -} - -#[derive(FromVariant)] -#[darling(attributes(graphql))] -pub struct UnionItem { - pub ident: Ident, - pub fields: Fields<syn::Type>, - - #[darling(default)] - pub flatten: bool, -} - -#[derive(FromField)] -#[darling(attributes(graphql), forward_attrs(doc))] -pub struct InputObjectField { - pub ident: Option<Ident>, - pub ty: Type, - pub vis: Visibility, - pub attrs: Vec<Attribute>, - - #[darling(default)] - pub name: Option<String>, - #[darling(default)] - pub default: Option<DefaultValue>, - #[darling(default)] - pub default_with: Option<LitStr>, - #[darling(default)] - pub validator: Option<Validators>, - #[darling(default)] - pub flatten: bool, - #[darling(default)] - pub skip: bool, - #[darling(default)] - pub skip_input: bool, - #[darling(default)] - pub process_with: Option<Expr>, - // for SimpleObject - #[darling(default)] - pub skip_output: bool, - #[darling(default)] - pub visible: Option<Visible>, - #[darling(default)] - pub inaccessible: bool, - #[darling(default, multiple, rename = "tag")] - pub tags: Vec<String>, - #[darling(default)] - pub secret: bool, - #[darling(default)] - pub shareable: bool, - #[darling(default, multiple, rename = "directive")] - pub directives: Vec<Expr>, - #[darling(default)] - pub deprecation: Deprecation, -} - -#[derive(FromDeriveInput)] -#[darling(attributes(graphql), forward_attrs(doc))] -pub struct InputObject { - pub ident: Ident, - pub generics: Generics, - pub attrs: Vec<Attribute>, - pub data: Data<Ignored, InputObjectField>, - - #[darling(default)] - pub internal: bool, - #[darling(default)] - pub name: Option<String>, - #[darling(default)] - pub name_type: bool, - #[darling(default)] - pub input_name: Option<String>, - #[darling(default)] - pub rename_fields: Option<RenameRule>, - #[darling(default)] - pub visible: Option<Visible>, - #[darling(default)] - pub inaccessible: bool, - #[darling(default, multiple, rename = "tag")] - pub tags: Vec<String>, - #[darling(default, multiple, rename = "concrete")] - pub concretes: Vec<ConcreteType>, - #[darling(default)] - pub validator: Option<Expr>, - // for SimpleObject - #[darling(default)] - pub complex: bool, - #[darling(default)] - pub shareable: bool, - #[darling(default, multiple, rename = "directive")] - pub directives: Vec<Expr>, -} - -#[derive(FromVariant)] -#[darling(attributes(graphql), forward_attrs(doc))] -pub struct OneofObjectField { - pub ident: Ident, - pub attrs: Vec<Attribute>, - pub fields: Fields<syn::Type>, - - #[darling(default)] - pub name: Option<String>, - #[darling(default)] - pub validator: Option<Validators>, - #[darling(default)] - pub visible: Option<Visible>, - #[darling(default)] - pub inaccessible: bool, - #[darling(default, multiple, rename = "tag")] - pub tags: Vec<String>, - #[darling(default)] - pub secret: bool, - #[darling(default, multiple, rename = "directive")] - pub directives: Vec<Expr>, - #[darling(default)] - pub deprecation: Deprecation, -} - -#[derive(FromDeriveInput)] -#[darling(attributes(graphql), forward_attrs(doc))] -pub struct OneofObject { - pub ident: Ident, - pub generics: Generics, - pub attrs: Vec<Attribute>, - pub data: Data<OneofObjectField, Ignored>, - - #[darling(default)] - pub internal: bool, - #[darling(default)] - pub name: Option<String>, - #[darling(default)] - pub input_name: Option<String>, - #[darling(default)] - pub name_type: bool, - #[darling(default)] - pub rename_fields: Option<RenameRule>, - #[darling(default)] - pub visible: Option<Visible>, - #[darling(default)] - pub inaccessible: bool, - #[darling(default, multiple, rename = "tag")] - pub tags: Vec<String>, - #[darling(default, multiple, rename = "concrete")] - pub concretes: Vec<ConcreteType>, - #[darling(default, multiple, rename = "directive")] - pub directives: Vec<Expr>, - // for Interface - #[darling(default, multiple, rename = "field")] - pub fields: Vec<InterfaceField>, -} - -#[derive(FromMeta)] -pub struct InterfaceFieldArgument { - pub name: String, - #[darling(default)] - pub desc: Option<String>, - pub ty: Type, - #[darling(default)] - pub default: Option<DefaultValue>, - #[darling(default)] - pub default_with: Option<LitStr>, - #[darling(default)] - pub visible: Option<Visible>, - #[darling(default)] - pub inaccessible: bool, - #[darling(default, multiple, rename = "tag")] - pub tags: Vec<String>, - #[darling(default)] - pub secret: bool, - #[darling(default, multiple, rename = "directive")] - pub directives: Vec<Expr>, - #[darling(default)] - pub deprecation: Deprecation, -} - -#[derive(FromMeta)] -pub struct InterfaceField { - pub name: SpannedValue<String>, - pub ty: Type, - #[darling(default)] - pub method: Option<String>, - #[darling(default)] - pub desc: Option<String>, - #[darling(default, multiple, rename = "arg")] - pub args: Vec<InterfaceFieldArgument>, - #[darling(default)] - pub deprecation: Deprecation, - #[darling(default)] - pub external: bool, - #[darling(default)] - pub provides: Option<String>, - #[darling(default)] - pub requires: Option<String>, - #[darling(default)] - pub visible: Option<Visible>, - #[darling(default)] - pub inaccessible: bool, - #[darling(default, multiple, rename = "tag")] - pub tags: Vec<String>, - #[darling(default)] - pub shareable: bool, - #[darling(default)] - pub override_from: Option<String>, - #[darling(default, multiple, rename = "directive")] - pub directives: Vec<Expr>, - #[darling(default, multiple)] - pub requires_scopes: Vec<String>, -} - -#[derive(FromVariant)] -pub struct InterfaceMember { - pub ident: Ident, - pub fields: Fields<syn::Type>, -} - -#[derive(FromDeriveInput)] -#[darling(attributes(graphql), forward_attrs(doc))] -pub struct Interface { - pub ident: Ident, - pub generics: Generics, - pub attrs: Vec<Attribute>, - pub data: Data<InterfaceMember, Ignored>, - - #[darling(default)] - pub internal: bool, - #[darling(default)] - pub name: Option<String>, - #[darling(default)] - pub name_type: bool, - #[darling(default)] - pub rename_fields: Option<RenameRule>, - #[darling(default)] - pub rename_args: Option<RenameRule>, - #[darling(default, multiple, rename = "field")] - pub fields: Vec<InterfaceField>, - #[darling(default)] - pub extends: bool, - #[darling(default)] - pub visible: Option<Visible>, - #[darling(default)] - pub inaccessible: bool, - #[darling(default, multiple, rename = "tag")] - pub tags: Vec<String>, - #[darling(default, multiple, rename = "directive")] - pub directives: Vec<Expr>, - // for OneofObject - #[darling(default)] - pub input_name: Option<String>, - #[darling(default, multiple)] - pub requires_scopes: Vec<String>, -} - -#[derive(FromMeta, Default)] -#[darling(default)] -pub struct Scalar { - pub internal: bool, - pub name: Option<String>, - #[darling(default)] - pub name_type: bool, - pub use_type_description: bool, - pub visible: Option<Visible>, - pub inaccessible: bool, - #[darling(multiple, rename = "tag")] - pub tags: Vec<String>, - pub specified_by_url: Option<String>, - #[darling(default, multiple)] - pub requires_scopes: Vec<String>, -} - -#[derive(FromMeta, Default)] -#[darling(default)] -pub struct Subscription { - pub internal: bool, - pub name: Option<String>, - #[darling(default)] - pub name_type: bool, - pub rename_fields: Option<RenameRule>, - pub rename_args: Option<RenameRule>, - pub use_type_description: bool, - pub extends: bool, - pub visible: Option<Visible>, - #[darling(default)] - pub guard: Option<Expr>, - #[darling(default, multiple, rename = "directive")] - pub directives: Vec<Expr>, -} - -#[derive(FromMeta, Default)] -#[darling(default)] -pub struct SubscriptionFieldArgument { - pub name: Option<String>, - pub desc: Option<String>, - pub default: Option<DefaultValue>, - pub default_with: Option<LitStr>, - pub validator: Option<Validators>, - #[darling(default)] - pub process_with: Option<Expr>, - pub visible: Option<Visible>, - pub secret: bool, - pub deprecation: Deprecation, -} - -#[derive(FromMeta, Default)] -#[darling(default)] -pub struct SubscriptionField { - pub skip: bool, - pub name: Option<String>, - pub deprecation: Deprecation, - pub guard: Option<Expr>, - pub visible: Option<Visible>, - pub complexity: Option<Expr>, - #[darling(default, multiple, rename = "directive")] - pub directives: Vec<Expr>, -} - -#[derive(FromField)] -pub struct MergedObjectField { - pub ty: Type, -} - -#[derive(FromDeriveInput)] -#[darling(attributes(graphql), forward_attrs(doc))] -pub struct MergedObject { - pub ident: Ident, - pub generics: Generics, - pub attrs: Vec<Attribute>, - pub data: Data<Ignored, MergedObjectField>, - - #[darling(default)] - pub internal: bool, - #[darling(default)] - pub name: Option<String>, - #[darling(default)] - pub name_type: bool, - #[darling(default)] - pub cache_control: CacheControl, - #[darling(default)] - pub extends: bool, - #[darling(default)] - pub shareable: bool, - #[darling(default)] - pub inaccessible: bool, - #[darling(default)] - pub interface_object: bool, - #[darling(default, multiple, rename = "tag")] - pub tags: Vec<String>, - #[darling(default)] - pub visible: Option<Visible>, - #[darling(default)] - pub serial: bool, - #[darling(default, multiple, rename = "directive")] - pub directives: Vec<Expr>, -} - -#[derive(FromField)] -pub struct MergedSubscriptionField { - pub ty: Type, -} - -#[derive(FromDeriveInput)] -#[darling(attributes(graphql), forward_attrs(doc))] -pub struct MergedSubscription { - pub ident: Ident, - pub generics: Generics, - pub attrs: Vec<Attribute>, - pub data: Data<Ignored, MergedSubscriptionField>, - - #[darling(default)] - pub internal: bool, - #[darling(default)] - pub name: Option<String>, - #[darling(default)] - pub name_type: bool, - #[darling(default)] - pub visible: Option<Visible>, - #[darling(default)] - pub extends: bool, -} - -#[derive(Debug, Copy, Clone, FromMeta)] -pub enum RenameRule { - #[darling(rename = "lowercase")] - Lower, - #[darling(rename = "UPPERCASE")] - Upper, - #[darling(rename = "PascalCase")] - Pascal, - #[darling(rename = "camelCase")] - Camel, - #[darling(rename = "snake_case")] - Snake, - #[darling(rename = "SCREAMING_SNAKE_CASE")] - ScreamingSnake, -} - -impl RenameRule { - fn rename(&self, name: impl AsRef<str>) -> String { - match self { - Self::Lower => name.as_ref().to_lowercase(), - Self::Upper => name.as_ref().to_uppercase(), - Self::Pascal => name.as_ref().to_pascal_case(), - Self::Camel => name.as_ref().to_camel_case(), - Self::Snake => name.as_ref().to_snake_case(), - Self::ScreamingSnake => name.as_ref().to_screaming_snake_case(), - } - } -} - -#[derive(Debug, Copy, Clone)] -pub enum RenameTarget { - Type, - EnumItem, - Field, - Argument, -} - -impl RenameTarget { - fn rule(&self) -> RenameRule { - match self { - RenameTarget::Type => RenameRule::Pascal, - RenameTarget::EnumItem => RenameRule::ScreamingSnake, - RenameTarget::Field => RenameRule::Camel, - RenameTarget::Argument => RenameRule::Camel, - } - } - - pub fn rename(&self, name: impl AsRef<str>) -> String { - self.rule().rename(name) - } -} - -pub trait RenameRuleExt { - fn rename(&self, name: impl AsRef<str>, target: RenameTarget) -> String; -} - -impl RenameRuleExt for Option<RenameRule> { - fn rename(&self, name: impl AsRef<str>, target: RenameTarget) -> String { - self.unwrap_or(target.rule()).rename(name) - } -} - -#[derive(FromDeriveInput)] -#[darling(forward_attrs(doc))] -pub struct Description { - pub ident: Ident, - pub generics: Generics, - pub attrs: Vec<Attribute>, - - #[darling(default)] - pub internal: bool, -} - -#[derive(Debug)] -pub enum NewTypeName { - New(String), - Rust, - Original, -} - -impl Default for NewTypeName { - fn default() -> Self { - Self::Original - } -} - -impl FromMeta for NewTypeName { - fn from_word() -> darling::Result<Self> { - Ok(Self::Rust) - } - - fn from_string(value: &str) -> darling::Result<Self> { - Ok(Self::New(value.to_string())) - } - - fn from_bool(value: bool) -> darling::Result<Self> { - if value { - Ok(Self::Rust) - } else { - Ok(Self::Original) - } - } -} - -#[derive(FromDeriveInput)] -#[darling(attributes(graphql), forward_attrs(doc))] -pub struct NewType { - pub ident: Ident, - pub generics: Generics, - pub attrs: Vec<Attribute>, - pub data: Data<Ignored, syn::Type>, - - #[darling(default)] - pub internal: bool, - #[darling(default)] - pub name: NewTypeName, - #[darling(default)] - pub visible: Option<Visible>, - #[darling(default)] - pub inaccessible: bool, - #[darling(default, multiple, rename = "tag")] - pub tags: Vec<String>, - #[darling(default)] - pub specified_by_url: Option<String>, -} - -#[derive(FromMeta, Default)] -#[darling(default)] -pub struct ComplexObject { - pub internal: bool, - pub rename_fields: Option<RenameRule>, - pub rename_args: Option<RenameRule>, - pub guard: Option<Expr>, -} - -#[derive(FromMeta, Default)] -#[darling(default)] -pub struct ComplexObjectField { - pub skip: bool, - pub name: Option<String>, - pub deprecation: Deprecation, - pub cache_control: CacheControl, - pub external: bool, - pub provides: Option<String>, - pub requires: Option<String>, - pub shareable: bool, - pub inaccessible: bool, - #[darling(multiple, rename = "tag")] - pub tags: Vec<String>, - pub override_from: Option<String>, - pub guard: Option<Expr>, - pub visible: Option<Visible>, - pub complexity: Option<Expr>, - #[darling(multiple)] - pub derived: Vec<DerivedField>, - pub flatten: bool, - #[darling(default, multiple, rename = "directive")] - pub directives: Vec<Expr>, - #[darling(default, multiple)] - pub requires_scopes: Vec<String>, -} - -#[derive(FromMeta, Default)] -#[darling(default)] -pub struct Directive { - pub internal: bool, - pub name: Option<String>, - #[darling(default)] - pub name_type: bool, - pub visible: Option<Visible>, - pub repeatable: bool, - pub rename_args: Option<RenameRule>, - #[darling(multiple, rename = "location")] - pub locations: Vec<DirectiveLocation>, -} - -#[derive(Debug, Copy, Clone, FromMeta, strum::Display)] -#[darling(rename_all = "PascalCase")] -#[strum(serialize_all = "SCREAMING_SNAKE_CASE")] -pub enum DirectiveLocation { - Field, -} - -#[derive(FromMeta, Default)] -#[darling(default)] -pub struct TypeDirective { - pub internal: bool, - pub name: Option<String>, - #[darling(default)] - pub name_type: bool, - pub visible: Option<Visible>, - pub repeatable: bool, - pub rename_args: Option<RenameRule>, - #[darling(multiple, rename = "location")] - pub locations: Vec<TypeDirectiveLocation>, - #[darling(default)] - pub composable: Option<String>, -} - -#[derive(Debug, Copy, Clone, FromMeta, strum::Display)] -#[darling(rename_all = "PascalCase")] -#[strum(serialize_all = "SCREAMING_SNAKE_CASE")] -pub enum TypeDirectiveLocation { - ArgumentDefinition, - Enum, - EnumValue, - FieldDefinition, - InputFieldDefinition, - Object, - InputObject, - Interface, -} - -impl TypeDirectiveLocation { - pub fn location_trait_identifier(&self) -> Ident { - format_ident!("Directive_At_{}", self.to_string()) - } -} diff --git a/derive/src/complex_object.rs b/derive/src/complex_object.rs deleted file mode 100644 index 12805ccb5..000000000 --- a/derive/src/complex_object.rs +++ /dev/null @@ -1,483 +0,0 @@ -use std::str::FromStr; - -use proc_macro::TokenStream; -use proc_macro2::Ident; -use quote::quote; -use syn::{ - Block, Error, FnArg, ImplItem, ItemImpl, Pat, ReturnType, Token, Type, TypeReference, - ext::IdentExt, punctuated::Punctuated, -}; - -use crate::{ - args::{self, RenameRuleExt, RenameTarget, TypeDirectiveLocation}, - output_type::OutputType, - utils::{ - GeneratorResult, extract_input_args, gen_boxed_trait, gen_deprecation, gen_directive_calls, - generate_default, generate_guards, get_cfg_attrs, get_crate_name, get_rustdoc, - get_type_path_and_name, parse_complexity_expr, parse_graphql_attrs, remove_graphql_attrs, - visible_fn, - }, -}; - -pub fn generate( - object_args: &args::ComplexObject, - item_impl: &mut ItemImpl, -) -> GeneratorResult<TokenStream> { - let crate_name = get_crate_name(object_args.internal); - let boxed_trait = gen_boxed_trait(&crate_name); - let (self_ty, _) = get_type_path_and_name(item_impl.self_ty.as_ref())?; - let generics = &item_impl.generics; - let where_clause = &item_impl.generics.where_clause; - - let mut resolvers = Vec::new(); - let mut schema_fields = Vec::new(); - - // Computation of the derived fields - let mut derived_impls = vec![]; - for item in &mut item_impl.items { - if let ImplItem::Fn(method) = item { - let method_args: args::ComplexObjectField = - parse_graphql_attrs(&method.attrs)?.unwrap_or_default(); - - for derived in method_args.derived { - if derived.name.is_some() && derived.into.is_some() { - let base_function_name = &method.sig.ident; - let name = derived.name.unwrap(); - let with = derived.with; - let into = Type::Verbatim( - proc_macro2::TokenStream::from_str(&derived.into.unwrap()).unwrap(), - ); - - let mut new_impl = method.clone(); - new_impl.sig.ident = name; - new_impl.sig.output = - syn::parse2::<ReturnType>(quote! { -> #crate_name::Result<#into> }) - .expect("invalid result type"); - - let should_create_context = new_impl - .sig - .inputs - .iter() - .nth(1) - .map(|x| { - if let FnArg::Typed(pat) = x { - if let Type::Reference(TypeReference { elem, .. }) = &*pat.ty { - if let Type::Path(path) = elem.as_ref() { - return path.path.segments.last().unwrap().ident - != "Context"; - } - } - }; - true - }) - .unwrap_or(true); - - if should_create_context { - let arg_ctx = syn::parse2::<FnArg>(quote! { ctx: &Context<'_> }) - .expect("invalid arg type"); - new_impl.sig.inputs.insert(1, arg_ctx); - } - - let other_atts: Punctuated<Ident, Token![,]> = Punctuated::from_iter( - new_impl - .sig - .inputs - .iter() - .filter_map(|x| match x { - FnArg::Typed(pat) => match &*pat.pat { - Pat::Ident(ident) => Some(Ok(ident.ident.clone())), - _ => Some(Err(Error::new_spanned( - pat, - "Must be a simple argument", - ))), - }, - FnArg::Receiver(_) => None, - }) - .collect::<Result<Vec<Ident>, Error>>()? - .into_iter(), - ); - - let new_block = match with { - Some(with) => quote!({ - ::std::result::Result::Ok(#with(#self_ty::#base_function_name(&self, #other_atts).await?)) - }), - None => quote!({ - { - ::std::result::Result::Ok(#self_ty::#base_function_name(&self, #other_atts).await?.into()) - } - }), - }; - - new_impl.block = syn::parse2::<Block>(new_block).expect("invalid block"); - - derived_impls.push(ImplItem::Fn(new_impl)); - } - } - } - } - item_impl.items.append(&mut derived_impls); - - for item in &mut item_impl.items { - if let ImplItem::Fn(method) = item { - let method_args: args::ComplexObjectField = - parse_graphql_attrs(&method.attrs)?.unwrap_or_default(); - if method_args.skip { - remove_graphql_attrs(&mut method.attrs); - continue; - } - let cfg_attrs = get_cfg_attrs(&method.attrs); - - if method_args.flatten { - // Only used to inject the context placeholder if required. - extract_input_args::<args::Argument>(&crate_name, method)?; - - let ty = match &method.sig.output { - ReturnType::Type(_, ty) => OutputType::parse(ty)?, - ReturnType::Default => { - return Err(Error::new_spanned( - &method.sig.output, - "Flatten resolver must have a return type", - ) - .into()); - } - }; - let ty = ty.value_type(); - let ident = &method.sig.ident; - - schema_fields.push(quote! { - #crate_name::static_assertions_next::assert_impl_one!(#ty: #crate_name::ObjectType); - <#ty>::create_type_info(registry); - if let #crate_name::registry::MetaType::Object { fields: obj_fields, .. } = - registry.create_fake_output_type::<#ty>() { - fields.extend(obj_fields); - } - }); - - resolvers.push(quote! { - #(#cfg_attrs)* - if let ::std::option::Option::Some(value) = #crate_name::ContainerType::resolve_field(&self.#ident(ctx).await, ctx).await? { - return ::std::result::Result::Ok(std::option::Option::Some(value)); - } - }); - - remove_graphql_attrs(&mut method.attrs); - continue; - } - - let field_name = method_args.name.clone().unwrap_or_else(|| { - object_args - .rename_fields - .rename(method.sig.ident.unraw().to_string(), RenameTarget::Field) - }); - let field_desc = get_rustdoc(&method.attrs)? - .map(|s| quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#s)) }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - let field_deprecation = gen_deprecation(&method_args.deprecation, &crate_name); - let external = method_args.external; - let shareable = method_args.shareable; - let directives = gen_directive_calls( - &method_args.directives, - TypeDirectiveLocation::FieldDefinition, - ); - let override_from = match &method_args.override_from { - Some(from) => { - quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#from)) } - } - None => quote! { ::std::option::Option::None }, - }; - let inaccessible = method_args.inaccessible; - let tags = method_args - .tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - let requires_scopes = method_args - .requires_scopes - .iter() - .map(|scopes| quote!(::std::string::ToString::to_string(#scopes))) - .collect::<Vec<_>>(); - let requires = match &method_args.requires { - Some(requires) => { - quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#requires)) } - } - None => quote! { ::std::option::Option::None }, - }; - let provides = match &method_args.provides { - Some(provides) => { - quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#provides)) } - } - None => quote! { ::std::option::Option::None }, - }; - let cache_control = { - let public = method_args.cache_control.is_public(); - let max_age = if method_args.cache_control.no_cache { - -1 - } else { - method_args.cache_control.max_age as i32 - }; - quote! { - #crate_name::CacheControl { - public: #public, - max_age: #max_age, - } - } - }; - - let args = extract_input_args::<args::Argument>(&crate_name, method)?; - let mut schema_args = Vec::new(); - let mut use_params = Vec::new(); - let mut get_params = Vec::new(); - - for ( - ident, - ty, - args::Argument { - name, - desc, - default, - default_with, - validator, - process_with, - visible, - inaccessible, - tags, - secret, - directives, - deprecation, - .. - }, - ) in &args - { - let name = name.clone().unwrap_or_else(|| { - object_args - .rename_args - .rename(ident.ident.unraw().to_string(), RenameTarget::Argument) - }); - let desc = desc - .as_ref() - .map(|s| quote! {::std::option::Option::Some(::std::string::ToString::to_string(#s))}) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - let default = generate_default(default, default_with)?; - let schema_default = default - .as_ref() - .map(|value| { - quote! { - ::std::option::Option::Some(::std::string::ToString::to_string( - &<#ty as #crate_name::InputType>::to_value(&#value) - )) - } - }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - - let visible = visible_fn(visible); - let tags = tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - let directives = - gen_directive_calls(directives, TypeDirectiveLocation::ArgumentDefinition); - let deprecation = gen_deprecation(deprecation, &crate_name); - - schema_args.push(quote! { - args.insert(::std::borrow::ToOwned::to_owned(#name), #crate_name::registry::MetaInputValue { - name: ::std::string::ToString::to_string(#name), - description: #desc, - ty: <#ty as #crate_name::InputType>::create_type_info(registry), - deprecation: #deprecation, - default_value: #schema_default, - visible: #visible, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - is_secret: #secret, - directive_invocations: ::std::vec![ #(#directives),* ], - }); - }); - - let param_ident = &ident.ident; - use_params.push(quote! { #param_ident }); - - let default = match default { - Some(default) => { - quote! { ::std::option::Option::Some(|| -> #ty { #default }) } - } - None => quote! { ::std::option::Option::None }, - }; - - let param_ident = &ident.ident; - let process_with = match process_with.as_ref() { - Some(fn_path) => quote! { #fn_path(&mut #param_ident); }, - None => Default::default(), - }; - - let validators = validator.clone().unwrap_or_default().create_validators( - &crate_name, - quote!(&#ident), - Some(quote!(.map_err(|err| err.into_server_error(__pos)))), - )?; - - let mut non_mut_ident = ident.clone(); - non_mut_ident.mutability = None; - get_params.push(quote! { - #[allow(non_snake_case, unused_mut)] - let (__pos, mut #non_mut_ident) = ctx.param_value::<#ty>(#name, #default)?; - #process_with - #validators - #[allow(non_snake_case)] - let #ident = #non_mut_ident; - }); - } - - let ty = match &method.sig.output { - ReturnType::Type(_, ty) => OutputType::parse(ty)?, - ReturnType::Default => { - return Err(Error::new_spanned( - &method.sig.output, - "Resolver must have a return type", - ) - .into()); - } - }; - let schema_ty = ty.value_type(); - let visible = visible_fn(&method_args.visible); - - let complexity = if let Some(complexity) = &method_args.complexity { - let (variables, expr) = parse_complexity_expr(complexity.clone())?; - let mut parse_args = Vec::new(); - for variable in variables { - if let Some(( - ident, - ty, - args::Argument { - name, - default, - default_with, - .. - }, - )) = args - .iter() - .find(|(pat_ident, _, _)| pat_ident.ident == variable) - { - let default = match generate_default(default, default_with)? { - Some(default) => { - quote! { ::std::option::Option::Some(|| -> #ty { #default }) } - } - None => quote! { ::std::option::Option::None }, - }; - let name = name.clone().unwrap_or_else(|| { - object_args - .rename_args - .rename(ident.ident.unraw().to_string(), RenameTarget::Argument) - }); - parse_args.push(quote! { - let #ident: #ty = __ctx.param_value(__variables_definition, __field, #name, #default)?; - }); - } - } - quote! { - Some(|__ctx, __variables_definition, __field, child_complexity| { - #(#parse_args)* - Ok(#expr) - }) - } - } else { - quote! { ::std::option::Option::None } - }; - - schema_fields.push(quote! { - #(#cfg_attrs)* - fields.push((#field_name.to_string(), #crate_name::registry::MetaField { - name: ::std::borrow::ToOwned::to_owned(#field_name), - description: #field_desc, - args: { - let mut args = #crate_name::indexmap::IndexMap::new(); - #(#schema_args)* - args - }, - ty: <#schema_ty as #crate_name::OutputType>::create_type_info(registry), - deprecation: #field_deprecation, - cache_control: #cache_control, - external: #external, - provides: #provides, - requires: #requires, - shareable: #shareable, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - override_from: #override_from, - visible: #visible, - compute_complexity: #complexity, - directive_invocations: ::std::vec![ #(#directives),* ], - requires_scopes: ::std::vec![ #(#requires_scopes),* ], - })); - }); - - let field_ident = &method.sig.ident; - if let OutputType::Value(inner_ty) = &ty { - let block = &method.block; - let new_block = quote!({ - { - ::std::result::Result::Ok(async move { - let value:#inner_ty = #block; - value - }.await) - } - }); - method.block = syn::parse2::<Block>(new_block).expect("invalid block"); - method.sig.output = - syn::parse2::<ReturnType>(quote! { -> #crate_name::Result<#inner_ty> }) - .expect("invalid result type"); - } - - let resolve_obj = quote! { - { - let res = self.#field_ident(ctx, #(#use_params),*).await; - res.map_err(|err| ::std::convert::Into::<#crate_name::Error>::into(err).into_server_error(ctx.item.pos)) - } - }; - - let guard_map_err = quote! { - .map_err(|err| err.into_server_error(ctx.item.pos)) - }; - let guard = match method_args.guard.as_ref().or(object_args.guard.as_ref()) { - Some(code) => Some(generate_guards(&crate_name, code, guard_map_err)?), - None => None, - }; - - resolvers.push(quote! { - #(#cfg_attrs)* - if ctx.item.node.name.node == #field_name { - let f = async move { - #(#get_params)* - #guard - #resolve_obj - }; - let obj = f.await.map_err(|err| ctx.set_error_path(err))?; - let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set); - return #crate_name::OutputType::resolve(&obj, &ctx_obj, ctx.item).await.map(::std::option::Option::Some); - } - }); - - remove_graphql_attrs(&mut method.attrs); - } - } - - let expanded = quote! { - #item_impl - - #[allow(clippy::all, clippy::pedantic)] - #boxed_trait - impl #generics #crate_name::ComplexObject for #self_ty #where_clause { - fn fields(registry: &mut #crate_name::registry::Registry) -> ::std::vec::Vec<(::std::string::String, #crate_name::registry::MetaField)> { - let mut fields = ::std::vec::Vec::new(); - #(#schema_fields)* - fields - } - - async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> { - #(#resolvers)* - ::std::result::Result::Ok(::std::option::Option::None) - } - } - }; - - Ok(expanded.into()) -} diff --git a/derive/src/description.rs b/derive/src/description.rs deleted file mode 100644 index 050ece0a3..000000000 --- a/derive/src/description.rs +++ /dev/null @@ -1,22 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; - -use crate::{ - args, - utils::{GeneratorResult, get_crate_name, get_rustdoc}, -}; - -pub fn generate(desc_args: &args::Description) -> GeneratorResult<TokenStream> { - let crate_name = get_crate_name(desc_args.internal); - let ident = &desc_args.ident; - let (impl_generics, ty_generics, where_clause) = desc_args.generics.split_for_impl(); - let doc = get_rustdoc(&desc_args.attrs)?.unwrap_or_default(); - let expanded = quote! { - impl #impl_generics #crate_name::Description for #ident #ty_generics #where_clause { - fn description() -> &'static str { - #doc - } - } - }; - Ok(expanded.into()) -} diff --git a/derive/src/directive.rs b/derive/src/directive.rs deleted file mode 100644 index 80f6e7056..000000000 --- a/derive/src/directive.rs +++ /dev/null @@ -1,185 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; -use syn::{Error, FnArg, ItemFn, Pat, ext::IdentExt}; - -use crate::{ - args::{self, Argument, RenameRuleExt, RenameTarget}, - utils::{ - GeneratorResult, gen_deprecation, generate_default, get_crate_name, get_rustdoc, - parse_graphql_attrs, remove_graphql_attrs, visible_fn, - }, -}; - -pub fn generate( - directive_args: &args::Directive, - item_fn: &mut ItemFn, -) -> GeneratorResult<TokenStream> { - let crate_name = get_crate_name(directive_args.internal); - let ident = &item_fn.sig.ident; - let vis = &item_fn.vis; - let directive_name = if !directive_args.name_type { - let name = directive_args - .name - .clone() - .unwrap_or_else(|| item_fn.sig.ident.to_string()); - quote!(::std::borrow::Cow::Borrowed(#name)) - } else { - quote!(<Self as #crate_name::TypeName>::type_name()) - }; - let desc = get_rustdoc(&item_fn.attrs)? - .map(|s| quote!(::std::option::Option::Some(::std::string::ToString::to_string(#s)))) - .unwrap_or_else(|| quote!(::std::option::Option::None)); - let visible = visible_fn(&directive_args.visible); - let repeatable = directive_args.repeatable; - - let mut get_params = Vec::new(); - let mut use_params = Vec::new(); - let mut schema_args = Vec::new(); - - for arg in item_fn.sig.inputs.iter_mut() { - let mut arg_info = None; - - if let FnArg::Typed(pat) = arg { - if let Pat::Ident(ident) = &*pat.pat { - arg_info = Some((ident.clone(), pat.ty.clone(), pat.attrs.clone())); - remove_graphql_attrs(&mut pat.attrs); - } - } - - let (arg_ident, arg_ty, arg_attrs) = match arg_info { - Some(info) => info, - None => { - return Err(Error::new_spanned(arg, "Invalid argument type.").into()); - } - }; - - let Argument { - name, - desc, - default, - default_with, - validator, - visible, - secret, - directives, - deprecation, - .. - } = parse_graphql_attrs::<args::Argument>(&arg_attrs)?.unwrap_or_default(); - - let name = name.clone().unwrap_or_else(|| { - directive_args - .rename_args - .rename(arg_ident.ident.unraw().to_string(), RenameTarget::Argument) - }); - let desc = desc - .as_ref() - .map(|s| quote! {::std::option::Option::Some(::std::string::ToString::to_string(#s))}) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - let default = generate_default(&default, &default_with)?; - let schema_default = default - .as_ref() - .map(|value| { - quote! { - ::std::option::Option::Some(::std::string::ToString::to_string( - &<#arg_ty as #crate_name::InputType>::to_value(&#value) - )) - } - }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - let visible = visible_fn(&visible); - let deprecation = gen_deprecation(&deprecation, &crate_name); - - schema_args.push(quote! { - args.insert(::std::borrow::ToOwned::to_owned(#name), #crate_name::registry::MetaInputValue { - name: ::std::string::ToString::to_string(#name), - description: #desc, - ty: <#arg_ty as #crate_name::InputType>::create_type_info(registry), - deprecation: #deprecation, - default_value: #schema_default, - visible: #visible, - inaccessible: false, - tags: ::std::default::Default::default(), - is_secret: #secret, - directive_invocations: ::std::vec![ #(#directives),* ], - }); - }); - - let validators = validator.clone().unwrap_or_default().create_validators( - &crate_name, - quote!(&#arg_ident), - Some(quote!(.map_err(|err| err.into_server_error(__pos)))), - )?; - - let default = match default { - Some(default) => { - quote! { ::std::option::Option::Some(|| -> #arg_ty { #default }) } - } - None => quote! { ::std::option::Option::None }, - }; - get_params.push(quote! { - let (__pos, #arg_ident) = ctx.param_value::<#arg_ty>(#name, #default)?; - #validators - }); - - use_params.push(quote! { #arg_ident }); - } - - let locations = directive_args - .locations - .iter() - .map(|loc| { - let loc = quote::format_ident!("{}", loc.to_string()); - quote!(#crate_name::registry::__DirectiveLocation::#loc) - }) - .collect::<Vec<_>>(); - - if locations.is_empty() { - return Err(Error::new( - ident.span(), - "At least one location is required for the directive.", - ) - .into()); - } - - let expanded = quote! { - #[allow(non_camel_case_types)] - #vis struct #ident; - - impl #crate_name::CustomDirectiveFactory for #ident { - fn name(&self) -> ::std::borrow::Cow<'static, ::std::primitive::str> { - #directive_name - } - - fn register(&self, registry: &mut #crate_name::registry::Registry) { - let meta = #crate_name::registry::MetaDirective { - name: ::std::borrow::Cow::into_owned(#directive_name), - description: #desc, - locations: vec![#(#locations),*], - args: { - #[allow(unused_mut)] - let mut args = #crate_name::indexmap::IndexMap::new(); - #(#schema_args)* - args - }, - is_repeatable: #repeatable, - visible: #visible, - composable: None, - }; - registry.add_directive(meta); - } - - fn create( - &self, - ctx: &#crate_name::ContextDirective<'_>, - directive: &#crate_name::parser::types::Directive, - ) -> #crate_name::ServerResult<::std::boxed::Box<dyn #crate_name::CustomDirective>> { - #item_fn - - #(#get_params)* - Ok(::std::boxed::Box::new(#ident(#(#use_params),*))) - } - } - }; - - Ok(expanded.into()) -} diff --git a/derive/src/enum.rs b/derive/src/enum.rs deleted file mode 100644 index 861550fc5..000000000 --- a/derive/src/enum.rs +++ /dev/null @@ -1,252 +0,0 @@ -use darling::ast::Data; -use proc_macro::TokenStream; -use quote::quote; -use syn::{Error, ext::IdentExt}; - -use crate::{ - args::{self, RenameRuleExt, RenameTarget, TypeDirectiveLocation}, - utils::{ - GeneratorResult, gen_boxed_trait, gen_deprecation, gen_directive_calls, get_crate_name, - get_rustdoc, visible_fn, - }, -}; - -pub fn generate(enum_args: &args::Enum) -> GeneratorResult<TokenStream> { - let crate_name = get_crate_name(enum_args.internal); - let boxed_trait = gen_boxed_trait(&crate_name); - let ident = &enum_args.ident; - let e = match &enum_args.data { - Data::Enum(e) => e, - _ => return Err(Error::new_spanned(ident, "Enum can only be applied to an enum.").into()), - }; - - let gql_typename = if !enum_args.name_type { - let name = enum_args - .name - .clone() - .unwrap_or_else(|| RenameTarget::Type.rename(ident.to_string())); - quote!(::std::borrow::Cow::Borrowed(#name)) - } else { - quote!(<Self as #crate_name::TypeName>::type_name()) - }; - - let inaccessible = enum_args.inaccessible; - let tags = enum_args - .tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - let requires_scopes = enum_args - .requires_scopes - .iter() - .map(|scopes| quote!(::std::string::ToString::to_string(#scopes))) - .collect::<Vec<_>>(); - let directives = gen_directive_calls(&enum_args.directives, TypeDirectiveLocation::Enum); - let desc = get_rustdoc(&enum_args.attrs)? - .map(|s| quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#s)) }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - - let mut enum_items = Vec::new(); - let mut enum_names = Vec::new(); - let mut items = Vec::new(); - let mut schema_enum_items = Vec::new(); - - for variant in e { - if !variant.fields.is_empty() { - return Err(Error::new_spanned( - &variant.ident, - format!( - "Invalid enum variant {}.\nGraphQL enums may only contain unit variants.", - variant.ident - ), - ) - .into()); - } - - let item_ident = &variant.ident; - let gql_item_name = variant.name.clone().unwrap_or_else(|| { - enum_args - .rename_items - .rename(variant.ident.unraw().to_string(), RenameTarget::EnumItem) - }); - let inaccessible = variant.inaccessible; - let tags = variant - .tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - let directives = gen_directive_calls(&variant.directives, TypeDirectiveLocation::EnumValue); - let item_deprecation = gen_deprecation(&variant.deprecation, &crate_name); - let item_desc = get_rustdoc(&variant.attrs)? - .map(|s| quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#s)) }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - - enum_items.push(item_ident); - enum_names.push(gql_item_name.clone()); - items.push(quote! { - #crate_name::resolver_utils::EnumItem { - name: #gql_item_name, - value: #ident::#item_ident, - } - }); - - let visible = visible_fn(&variant.visible); - schema_enum_items.push(quote! { - enum_items.insert(::std::string::ToString::to_string(#gql_item_name), #crate_name::registry::MetaEnumValue { - name: ::std::string::ToString::to_string(#gql_item_name), - description: #item_desc, - deprecation: #item_deprecation, - visible: #visible, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - directive_invocations: ::std::vec![ #(#directives),* ] - }); - }); - } - - let remote_conversion = if let Some(remote_ty) = &enum_args.remote { - let local_to_remote_items = enum_items.iter().map(|item| { - quote! { - #ident::#item => #remote_ty::#item, - } - }); - let remote_to_local_items = enum_items.iter().map(|item| { - quote! { - #remote_ty::#item => #ident::#item, - } - }); - Some(quote! { - impl ::std::convert::From<#ident> for #remote_ty { - fn from(value: #ident) -> Self { - match value { - #(#local_to_remote_items)* - } - } - } - - impl ::std::convert::From<#remote_ty> for #ident { - fn from(value: #remote_ty) -> Self { - match value { - #(#remote_to_local_items)* - } - } - } - }) - } else { - None - }; - - if schema_enum_items.is_empty() { - return Err(Error::new_spanned( - ident, - "A GraphQL Enum type must define one or more unique enum values.", - ) - .into()); - } - - let display = if enum_args.display { - let items = enum_items.iter().zip(&enum_names).map(|(item, name)| { - quote! { - #ident::#item => #name, - } - }); - Some(quote! { - impl ::std::fmt::Display for #ident { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - f.write_str(match self { - #(#items)* - }) - } - } - }) - } else { - None - }; - - let visible = visible_fn(&enum_args.visible); - let expanded = quote! { - #[allow(clippy::all, clippy::pedantic)] - impl #crate_name::resolver_utils::EnumType for #ident { - fn items() -> &'static [#crate_name::resolver_utils::EnumItem<#ident>] { - &[#(#items),*] - } - } - - #[allow(clippy::all, clippy::pedantic)] - impl #ident { - fn __type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - #gql_typename - } - - fn __create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - registry.create_input_type::<Self, _>(#crate_name::registry::MetaTypeId::Enum, |registry| { - #crate_name::registry::MetaType::Enum { - name: ::std::borrow::Cow::into_owned(#gql_typename), - description: #desc, - enum_values: { - let mut enum_items = #crate_name::indexmap::IndexMap::new(); - #(#schema_enum_items)* - enum_items - }, - visible: #visible, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - rust_typename: ::std::option::Option::Some(::std::any::type_name::<Self>()), - directive_invocations: ::std::vec![ #(#directives),* ], - requires_scopes: ::std::vec![ #(#requires_scopes),* ], - } - }) - } - } - - #[allow(clippy::all, clippy::pedantic)] - impl #crate_name::InputType for #ident { - type RawValueType = Self; - - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - Self::__type_name() - } - - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - Self::__create_type_info(registry) - } - - fn parse(value: ::std::option::Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> { - #crate_name::resolver_utils::parse_enum(value.unwrap_or_default()) - } - - fn to_value(&self) -> #crate_name::Value { - #crate_name::resolver_utils::enum_value(*self) - } - - fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> { - ::std::option::Option::Some(self) - } - } - - #boxed_trait - impl #crate_name::OutputType for #ident { - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - Self::__type_name() - } - - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - Self::__create_type_info(registry) - } - - async fn resolve(&self, _: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> { - ::std::result::Result::Ok(#crate_name::resolver_utils::enum_value(*self)) - } - } - - impl ::std::convert::From<#ident> for #crate_name::Value { - fn from(value: #ident) -> #crate_name::Value { - #crate_name::resolver_utils::enum_value(value) - } - } - - #remote_conversion - #display - }; - Ok(expanded.into()) -} diff --git a/derive/src/input_object.rs b/derive/src/input_object.rs deleted file mode 100644 index 9bd94d949..000000000 --- a/derive/src/input_object.rs +++ /dev/null @@ -1,397 +0,0 @@ -use darling::ast::Data; -use proc_macro::TokenStream; -use quote::quote; -use syn::{Error, ext::IdentExt}; - -use crate::{ - args::{self, RenameRuleExt, RenameTarget, TypeDirectiveLocation}, - utils::{ - GeneratorResult, gen_deprecation, gen_directive_calls, generate_default, get_crate_name, - get_rustdoc, visible_fn, - }, -}; - -pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream> { - let crate_name = get_crate_name(object_args.internal); - let (impl_generics, ty_generics, where_clause) = object_args.generics.split_for_impl(); - let ident = &object_args.ident; - let inaccessible = object_args.inaccessible; - let tags = object_args - .tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - let directives = - gen_directive_calls(&object_args.directives, TypeDirectiveLocation::InputObject); - let s = match &object_args.data { - Data::Struct(s) => s, - _ => { - return Err( - Error::new_spanned(ident, "InputObject can only be applied to an struct.").into(), - ); - } - }; - - for field in &s.fields { - if field.ident.is_none() { - return Err(Error::new_spanned(ident, "All fields must be named.").into()); - } - } - - let gql_typename = if !object_args.name_type { - let name = object_args - .input_name - .clone() - .or_else(|| object_args.name.clone()) - .unwrap_or_else(|| RenameTarget::Type.rename(ident.to_string())); - - quote!(::std::borrow::Cow::Borrowed(#name)) - } else { - quote!(<Self as #crate_name::TypeName>::type_name()) - }; - - let desc = get_rustdoc(&object_args.attrs)? - .map(|s| quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#s)) }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - - let mut get_fields = Vec::new(); - let mut put_fields = Vec::new(); - let mut fields = Vec::new(); - let mut schema_fields = Vec::new(); - let mut flatten_fields = Vec::new(); - let mut federation_fields = Vec::new(); - - for field in &s.fields { - let ident = field.ident.as_ref().unwrap(); - let ty = &field.ty; - let name = field.name.clone().unwrap_or_else(|| { - object_args - .rename_fields - .rename(ident.unraw().to_string(), RenameTarget::Field) - }); - let inaccessible = field.inaccessible; - let tags = field - .tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - - let directive_invocations = gen_directive_calls( - &field.directives, - TypeDirectiveLocation::InputFieldDefinition, - ); - - if field.skip || field.skip_input { - get_fields.push(quote! { - let #ident: #ty = ::std::default::Default::default(); - }); - fields.push(ident); - continue; - } - - federation_fields.push((ty, name.clone())); - - let process_with = match field.process_with.as_ref() { - Some(fn_path) => quote! { #fn_path(&mut #ident); }, - None => Default::default(), - }; - - let validators = field - .validator - .clone() - .unwrap_or_default() - .create_validators( - &crate_name, - quote!(&#ident), - Some(quote!(.map_err(#crate_name::InputValueError::propagate))), - )?; - - if field.flatten { - flatten_fields.push((ident, ty)); - - schema_fields.push(quote! { - #crate_name::static_assertions_next::assert_impl_one!(#ty: #crate_name::InputObjectType); - <#ty as #crate_name::InputType>::create_type_info(registry); - if let #crate_name::registry::MetaType::InputObject { input_fields, .. } = - registry.create_fake_input_type::<#ty>() { - fields.extend(input_fields); - } - }); - - get_fields.push(quote! { - #[allow(unused_mut)] - let mut #ident: #ty = #crate_name::InputType::parse( - ::std::option::Option::Some(#crate_name::Value::Object(::std::clone::Clone::clone(&obj))) - ).map_err(#crate_name::InputValueError::propagate)?; - #process_with - #validators - }); - - fields.push(ident); - - put_fields.push(quote! { - if let #crate_name::Value::Object(values) = #crate_name::InputType::to_value(&self.#ident) { - map.extend(values); - } - }); - continue; - } - - let desc = get_rustdoc(&field.attrs)? - .map(|s| quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#s)) }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - let default = generate_default(&field.default, &field.default_with)?; - let schema_default = default - .as_ref() - .map(|value| { - quote! { - ::std::option::Option::Some(::std::string::ToString::to_string( - &<#ty as #crate_name::InputType>::to_value(&#value) - )) - } - }) - .unwrap_or_else(|| quote!(::std::option::Option::None)); - let secret = field.secret; - - if let Some(default) = default { - get_fields.push(quote! { - #[allow(non_snake_case)] - let #ident: #ty = { - match obj.get(#name) { - ::std::option::Option::Some(value) => { - #[allow(unused_mut)] - let mut #ident = #crate_name::InputType::parse(::std::option::Option::Some(::std::clone::Clone::clone(&value))) - .map_err(#crate_name::InputValueError::propagate)?; - #process_with - #ident - - }, - ::std::option::Option::None => #default, - } - }; - #validators - }); - } else { - get_fields.push(quote! { - #[allow(non_snake_case, unused_mut)] - let mut #ident: #ty = #crate_name::InputType::parse(obj.get(#name).cloned()) - .map_err(#crate_name::InputValueError::propagate)?; - #process_with - #validators - }); - } - - put_fields.push(quote! { - map.insert( - #crate_name::Name::new(#name), - #crate_name::InputType::to_value(&self.#ident) - ); - }); - - fields.push(ident); - - let visible = visible_fn(&field.visible); - let deprecation = gen_deprecation(&field.deprecation, &crate_name); - - schema_fields.push(quote! { - fields.insert(::std::borrow::ToOwned::to_owned(#name), #crate_name::registry::MetaInputValue { - name: ::std::string::ToString::to_string(#name), - description: #desc, - ty: <#ty as #crate_name::InputType>::create_type_info(registry), - deprecation: #deprecation, - default_value: #schema_default, - visible: #visible, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - is_secret: #secret, - directive_invocations: ::std::vec![ #(#directive_invocations),* ], - }); - }) - } - - if get_fields.is_empty() { - return Err(Error::new_spanned( - ident, - "A GraphQL Input Object type must define one or more input fields.", - ) - .into()); - } - - let visible = visible_fn(&object_args.visible); - - let get_federation_fields = { - let fields = federation_fields.into_iter().map(|(ty, name)| { - quote! { - if let ::std::option::Option::Some(fields) = <#ty as #crate_name::InputType>::federation_fields() { - res.push(::std::format!("{} {}", #name, fields)); - } else { - res.push(::std::string::ToString::to_string(#name)); - } - } - }); - quote! { - let mut res = ::std::vec::Vec::new(); - #(#fields)* - ::std::option::Option::Some(::std::format!("{{ {} }}", res.join(" "))) - } - }; - - let obj_validator = object_args - .validator - .as_ref() - .map(|expr| quote! { #crate_name::CustomValidator::check(&#expr, &obj)?; }); - - let expanded = if object_args.concretes.is_empty() { - quote! { - #[allow(clippy::all, clippy::pedantic)] - impl #crate_name::InputType for #ident { - type RawValueType = Self; - - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - #gql_typename - } - - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - registry.create_input_type::<Self, _>(#crate_name::registry::MetaTypeId::InputObject, |registry| #crate_name::registry::MetaType::InputObject { - name: ::std::borrow::Cow::into_owned(#gql_typename), - description: #desc, - input_fields: { - let mut fields = #crate_name::indexmap::IndexMap::new(); - #(#schema_fields)* - fields - }, - visible: #visible, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - rust_typename: ::std::option::Option::Some(::std::any::type_name::<Self>()), - oneof: false, - directive_invocations: ::std::vec![ #(#directives),* ], - }) - } - - fn parse(value: ::std::option::Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> { - if let ::std::option::Option::Some(#crate_name::Value::Object(obj)) = value { - #(#get_fields)* - let obj = Self { #(#fields),* }; - #obj_validator - ::std::result::Result::Ok(obj) - } else { - ::std::result::Result::Err(#crate_name::InputValueError::expected_type(value.unwrap_or_default())) - } - } - - fn to_value(&self) -> #crate_name::Value { - let mut map = #crate_name::indexmap::IndexMap::new(); - #(#put_fields)* - #crate_name::Value::Object(map) - } - - fn federation_fields() -> ::std::option::Option<::std::string::String> { - #get_federation_fields - } - - fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> { - ::std::option::Option::Some(self) - } - } - - impl #crate_name::InputObjectType for #ident {} - } - } else { - let mut code = Vec::new(); - - code.push(quote! { - #[allow(clippy::all, clippy::pedantic)] - impl #impl_generics #ident #ty_generics #where_clause { - fn __internal_create_type_info_input_object(registry: &mut #crate_name::registry::Registry, name: &str) -> ::std::string::String where Self: #crate_name::InputType { - registry.create_input_type::<Self, _>(#crate_name::registry::MetaTypeId::InputObject, |registry| #crate_name::registry::MetaType::InputObject { - name: ::std::borrow::ToOwned::to_owned(name), - description: #desc, - input_fields: { - let mut fields = #crate_name::indexmap::IndexMap::new(); - #(#schema_fields)* - fields - }, - visible: #visible, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - rust_typename: ::std::option::Option::Some(::std::any::type_name::<Self>()), - oneof: false, - directive_invocations: ::std::vec![ #(#directives),* ], - }) - } - - fn __internal_parse(value: ::std::option::Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> where Self: #crate_name::InputType { - if let ::std::option::Option::Some(#crate_name::Value::Object(obj)) = value { - #(#get_fields)* - let obj = Self { #(#fields),* }; - #obj_validator - ::std::result::Result::Ok(obj) - } else { - ::std::result::Result::Err(#crate_name::InputValueError::expected_type(value.unwrap_or_default())) - } - } - - fn __internal_to_value(&self) -> #crate_name::Value where Self: #crate_name::InputType { - let mut map = #crate_name::indexmap::IndexMap::new(); - #(#put_fields)* - #crate_name::Value::Object(map) - } - - fn __internal_federation_fields() -> ::std::option::Option<::std::string::String> where Self: #crate_name::InputType { - #get_federation_fields - } - } - }); - - for concrete in &object_args.concretes { - let gql_typename = concrete.input_name.as_ref().unwrap_or(&concrete.name); - let params = &concrete.params.0; - let concrete_type = quote! { #ident<#(#params),*> }; - - let def_bounds = if !concrete.bounds.0.is_empty() { - let bounds = concrete.bounds.0.iter().map(|b| quote!(#b)); - Some(quote!(<#(#bounds),*>)) - } else { - None - }; - - let expanded = quote! { - #[allow(clippy::all, clippy::pedantic)] - impl #def_bounds #crate_name::InputType for #concrete_type { - type RawValueType = Self; - - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - ::std::borrow::Cow::Borrowed(#gql_typename) - } - - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - Self::__internal_create_type_info_input_object(registry, #gql_typename) - } - - fn parse(value: ::std::option::Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> { - Self::__internal_parse(value) - } - - fn to_value(&self) -> #crate_name::Value { - self.__internal_to_value() - } - - fn federation_fields() -> ::std::option::Option<::std::string::String> { - Self::__internal_federation_fields() - } - - fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> { - ::std::option::Option::Some(self) - } - } - - impl #def_bounds #crate_name::InputObjectType for #concrete_type {} - }; - code.push(expanded); - } - quote!(#(#code)*) - }; - - Ok(expanded.into()) -} diff --git a/derive/src/interface.rs b/derive/src/interface.rs deleted file mode 100644 index 13bc5bc12..000000000 --- a/derive/src/interface.rs +++ /dev/null @@ -1,449 +0,0 @@ -use std::collections::HashSet; - -use darling::ast::{Data, Style}; -use proc_macro::TokenStream; -use proc_macro2::{Ident, Span}; -use quote::quote; -use syn::{Error, Type, visit_mut::VisitMut}; - -use crate::{ - args::{ - self, InterfaceField, InterfaceFieldArgument, RenameRuleExt, RenameTarget, - TypeDirectiveLocation, - }, - output_type::OutputType, - utils::{ - GeneratorResult, RemoveLifetime, gen_boxed_trait, gen_deprecation, gen_directive_calls, - generate_default, get_crate_name, get_rustdoc, visible_fn, - }, -}; - -pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream> { - let crate_name = get_crate_name(interface_args.internal); - let boxed_trait = gen_boxed_trait(&crate_name); - let ident = &interface_args.ident; - let type_params = interface_args.generics.type_params().collect::<Vec<_>>(); - let (impl_generics, ty_generics, where_clause) = interface_args.generics.split_for_impl(); - let s = match &interface_args.data { - Data::Enum(s) => s, - _ => { - return Err( - Error::new_spanned(ident, "Interface can only be applied to an enum.").into(), - ); - } - }; - let extends = interface_args.extends; - let mut enum_names = Vec::new(); - let mut enum_items = HashSet::new(); - let mut type_into_impls = Vec::new(); - let inaccessible = interface_args.inaccessible; - let tags = interface_args - .tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - let requires_scopes = interface_args - .requires_scopes - .iter() - .map(|scopes| quote!(::std::string::ToString::to_string(#scopes))) - .collect::<Vec<_>>(); - let directives = - gen_directive_calls(&interface_args.directives, TypeDirectiveLocation::Interface); - let gql_typename = if !interface_args.name_type { - let name = interface_args - .name - .clone() - .unwrap_or_else(|| RenameTarget::Type.rename(ident.to_string())); - quote!(::std::borrow::Cow::Borrowed(#name)) - } else { - quote!(<Self as #crate_name::TypeName>::type_name()) - }; - - let desc = get_rustdoc(&interface_args.attrs)? - .map(|s| quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#s)) }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - - let mut registry_types = Vec::new(); - let mut possible_types = Vec::new(); - let mut get_introspection_typename = Vec::new(); - let mut collect_all_fields = Vec::new(); - - for variant in s { - let enum_name = &variant.ident; - let ty = match variant.fields.style { - Style::Tuple if variant.fields.fields.len() == 1 => &variant.fields.fields[0], - Style::Tuple => { - return Err(Error::new_spanned( - enum_name, - "Only single value variants are supported", - ) - .into()); - } - Style::Unit => { - return Err( - Error::new_spanned(enum_name, "Empty variants are not supported").into(), - ); - } - Style::Struct => { - return Err(Error::new_spanned( - enum_name, - "Variants with named fields are not supported", - ) - .into()); - } - }; - - if let Type::Path(p) = ty { - // This validates that the field type wasn't already used - if !enum_items.insert(p) { - return Err( - Error::new_spanned(ty, "This type already used in another variant").into(), - ); - } - - let mut assert_ty = p.clone(); - RemoveLifetime.visit_type_path_mut(&mut assert_ty); - - type_into_impls.push(quote! { - #crate_name::static_assertions_next::assert_impl!(for(#(#type_params),*) #assert_ty: (#crate_name::ObjectType) | (#crate_name::InterfaceType)); - - #[allow(clippy::all, clippy::pedantic)] - impl #impl_generics ::std::convert::From<#p> for #ident #ty_generics #where_clause { - fn from(obj: #p) -> Self { - #ident::#enum_name(obj) - } - } - }); - enum_names.push(enum_name); - - registry_types.push(quote! { - <#p as #crate_name::OutputType>::create_type_info(registry); - registry.add_implements(&<#p as #crate_name::OutputType>::type_name(), ::std::convert::AsRef::as_ref(&#gql_typename)); - }); - - possible_types.push(quote! { - possible_types.insert(<#p as #crate_name::OutputType>::type_name().into_owned()); - }); - - get_introspection_typename.push(quote! { - #ident::#enum_name(obj) => <#p as #crate_name::OutputType>::type_name() - }); - - collect_all_fields.push(quote! { - #ident::#enum_name(obj) => obj.collect_all_fields(ctx, fields) - }); - } else { - return Err(Error::new_spanned(ty, "Invalid type").into()); - } - } - - let mut methods = Vec::new(); - let mut schema_fields = Vec::new(); - let mut resolvers = Vec::new(); - - if interface_args.fields.is_empty() { - return Err(Error::new_spanned( - ident, - "A GraphQL Interface type must define one or more fields.", - ) - .into()); - } - - for InterfaceField { - name, - method, - desc, - ty, - args, - deprecation, - external, - provides, - requires, - visible, - shareable, - inaccessible, - tags, - override_from, - directives, - requires_scopes, - } in &interface_args.fields - { - let (name, method_name) = if let Some(method) = method { - (name.to_string(), Ident::new_raw(method, Span::call_site())) - } else { - let method_name = Ident::new_raw(name, Span::call_site()); - ( - interface_args - .rename_fields - .rename(name.as_ref(), RenameTarget::Field), - method_name, - ) - }; - let mut calls = Vec::new(); - let mut use_params = Vec::new(); - let mut decl_params = Vec::new(); - let mut get_params = Vec::new(); - let mut schema_args = Vec::new(); - let requires = match &requires { - Some(requires) => { - quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#requires)) } - } - None => quote! { ::std::option::Option::None }, - }; - let provides = match &provides { - Some(provides) => { - quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#provides)) } - } - None => quote! { ::std::option::Option::None }, - }; - let override_from = match &override_from { - Some(from) => { - quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#from)) } - } - None => quote! { ::std::option::Option::None }, - }; - - decl_params.push(quote! { ctx: &'ctx #crate_name::Context<'ctx> }); - use_params.push(quote! { ctx }); - - for ( - i, - InterfaceFieldArgument { - name, - desc, - ty, - default, - default_with, - visible, - inaccessible, - tags, - secret, - directives, - deprecation, - }, - ) in args.iter().enumerate() - { - let ident = Ident::new(&format!("arg{}", i), Span::call_site()); - let name = interface_args - .rename_args - .rename(name, RenameTarget::Argument); - decl_params.push(quote! { #ident: #ty }); - use_params.push(quote! { #ident }); - - let default = generate_default(default, default_with)?; - let get_default = match &default { - Some(default) => quote! { ::std::option::Option::Some(|| -> #ty { #default }) }, - None => quote! { ::std::option::Option::None }, - }; - get_params.push(quote! { - let (_, #ident) = ctx.param_value::<#ty>(#name, #get_default)?; - }); - - let desc = desc - .as_ref() - .map(|s| quote! {::std::option::Option::Some(::std::string::ToString::to_string(#s))}) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - let schema_default = default - .as_ref() - .map(|value| { - quote! { - ::std::option::Option::Some(::std::string::ToString::to_string( - &<#ty as #crate_name::InputType>::to_value(&#value) - )) - } - }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - let visible = visible_fn(visible); - let tags = tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - let directives = - gen_directive_calls(directives, TypeDirectiveLocation::ArgumentDefinition); - let deprecation = gen_deprecation(deprecation, &crate_name); - - schema_args.push(quote! { - args.insert(::std::borrow::ToOwned::to_owned(#name), #crate_name::registry::MetaInputValue { - name: ::std::string::ToString::to_string(#name), - description: #desc, - ty: <#ty as #crate_name::InputType>::create_type_info(registry), - deprecation: #deprecation, - default_value: #schema_default, - visible: #visible, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - is_secret: #secret, - directive_invocations: ::std::vec![ #(#directives),* ], - }); - }); - } - - for enum_name in &enum_names { - calls.push(quote! { - #ident::#enum_name(obj) => obj.#method_name(#(#use_params),*) - .await.map_err(|err| ::std::convert::Into::<#crate_name::Error>::into(err)) - .map(::std::convert::Into::into) - }); - } - - let desc = desc - .as_ref() - .map(|s| quote! {::std::option::Option::Some(::std::string::ToString::to_string(#s))}) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - let deprecation = gen_deprecation(deprecation, &crate_name); - - let oty = OutputType::parse(ty)?; - let ty = match oty { - OutputType::Value(ty) => ty, - OutputType::Result(ty) => ty, - }; - let schema_ty = oty.value_type(); - - methods.push(quote! { - #[inline] - pub async fn #method_name<'ctx>(&self, #(#decl_params),*) -> #crate_name::Result<#ty> { - match self { - #(#calls,)* - } - } - }); - - let visible = visible_fn(visible); - let tags = tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - let requires_scopes = requires_scopes - .iter() - .map(|scopes| quote!(::std::string::ToString::to_string(#scopes))) - .collect::<Vec<_>>(); - let directives = gen_directive_calls(directives, TypeDirectiveLocation::FieldDefinition); - - schema_fields.push(quote! { - fields.insert(::std::string::ToString::to_string(#name), #crate_name::registry::MetaField { - name: ::std::string::ToString::to_string(#name), - description: #desc, - args: { - let mut args = #crate_name::indexmap::IndexMap::new(); - #(#schema_args)* - args - }, - ty: <#schema_ty as #crate_name::OutputType>::create_type_info(registry), - deprecation: #deprecation, - cache_control: ::std::default::Default::default(), - external: #external, - provides: #provides, - requires: #requires, - shareable: #shareable, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - override_from: #override_from, - visible: #visible, - compute_complexity: ::std::option::Option::None, - directive_invocations: ::std::vec![ #(#directives),* ], - requires_scopes: ::std::vec![ #(#requires_scopes),* ], - }); - }); - - let resolve_obj = quote! { - self.#method_name(#(#use_params),*) - .await - .map_err(|err| ::std::convert::Into::<#crate_name::Error>::into(err).into_server_error(ctx.item.pos))? - }; - - resolvers.push(quote! { - if ctx.item.node.name.node == #name { - #(#get_params)* - let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set); - return #crate_name::OutputType::resolve(&#resolve_obj, &ctx_obj, ctx.item).await.map(::std::option::Option::Some); - } - }); - } - - let introspection_type_name = if get_introspection_typename.is_empty() { - quote! { ::std::unreachable!() } - } else { - quote! { - match self { - #(#get_introspection_typename),* - } - } - }; - - let visible = visible_fn(&interface_args.visible); - let expanded = quote! { - #(#type_into_impls)* - - #[allow(clippy::all, clippy::pedantic)] - impl #impl_generics #ident #ty_generics #where_clause { - #(#methods)* - } - - #[allow(clippy::all, clippy::pedantic)] - #boxed_trait - impl #impl_generics #crate_name::resolver_utils::ContainerType for #ident #ty_generics #where_clause { - async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> { - #(#resolvers)* - ::std::result::Result::Ok(::std::option::Option::None) - } - - fn collect_all_fields<'__life>(&'__life self, ctx: &#crate_name::ContextSelectionSet<'__life>, fields: &mut #crate_name::resolver_utils::Fields<'__life>) -> #crate_name::ServerResult<()> { - match self { - #(#collect_all_fields),* - } - } - } - - #[allow(clippy::all, clippy::pedantic)] - #boxed_trait - impl #impl_generics #crate_name::OutputType for #ident #ty_generics #where_clause { - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - #gql_typename - } - - fn introspection_type_name(&self) -> ::std::borrow::Cow<'static, ::std::primitive::str> { - #introspection_type_name - } - - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - registry.create_output_type::<Self, _>(#crate_name::registry::MetaTypeId::Interface, |registry| { - #(#registry_types)* - - #crate_name::registry::MetaType::Interface { - name: ::std::borrow::Cow::into_owned(#gql_typename), - description: #desc, - fields: { - let mut fields = #crate_name::indexmap::IndexMap::new(); - #(#schema_fields)* - fields - }, - possible_types: { - let mut possible_types = #crate_name::indexmap::IndexSet::new(); - #(#possible_types)* - possible_types - }, - extends: #extends, - keys: ::std::option::Option::None, - visible: #visible, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - rust_typename: ::std::option::Option::Some(::std::any::type_name::<Self>()), - directive_invocations: ::std::vec![ #(#directives),* ], - requires_scopes: ::std::vec![ #(#requires_scopes),* ], - } - }) - } - - async fn resolve( - &self, - ctx: &#crate_name::ContextSelectionSet<'_>, - _field: &#crate_name::Positioned<#crate_name::parser::types::Field>, - ) -> #crate_name::ServerResult<#crate_name::Value> { - #crate_name::resolver_utils::resolve_container(ctx, self).await - } - } - - impl #impl_generics #crate_name::InterfaceType for #ident #ty_generics #where_clause {} - }; - Ok(expanded.into()) -} diff --git a/derive/src/lib.rs b/derive/src/lib.rs deleted file mode 100644 index 125e57312..000000000 --- a/derive/src/lib.rs +++ /dev/null @@ -1,245 +0,0 @@ -#![allow(clippy::cognitive_complexity)] -#![allow(clippy::vec_init_then_push)] -#![allow(clippy::uninlined_format_args)] -#![forbid(unsafe_code)] - -extern crate proc_macro; - -mod args; -mod complex_object; -mod description; -mod directive; -mod r#enum; -mod input_object; -mod interface; -mod merged_object; -mod merged_subscription; -mod newtype; -mod object; -mod oneof_object; -mod output_type; -mod scalar; -mod simple_object; -mod subscription; -mod type_directive; -mod union; -mod utils; -mod validators; - -use darling::{FromDeriveInput, FromMeta}; -use proc_macro::TokenStream; -use syn::{DeriveInput, ItemFn, ItemImpl, parse_macro_input}; - -macro_rules! parse_nested_meta { - ($ty:ty, $args:expr) => {{ - let meta = match darling::ast::NestedMeta::parse_meta_list(proc_macro2::TokenStream::from( - $args, - )) { - Ok(v) => v, - Err(e) => { - return TokenStream::from(darling::Error::from(e).write_errors()); - } - }; - - match <$ty>::from_list(&meta) { - Ok(object_args) => object_args, - Err(err) => return TokenStream::from(err.write_errors()), - } - }}; -} - -#[proc_macro_attribute] -#[allow(non_snake_case)] -pub fn Object(args: TokenStream, input: TokenStream) -> TokenStream { - let object_args = parse_nested_meta!(args::Object, args); - let mut item_impl = parse_macro_input!(input as ItemImpl); - match object::generate(&object_args, &mut item_impl) { - Ok(expanded) => expanded, - Err(err) => err.write_errors().into(), - } -} - -#[proc_macro_derive(SimpleObject, attributes(graphql))] -pub fn derive_simple_object(input: TokenStream) -> TokenStream { - let object_args = - match args::SimpleObject::from_derive_input(&parse_macro_input!(input as DeriveInput)) { - Ok(object_args) => object_args, - Err(err) => return TokenStream::from(err.write_errors()), - }; - match simple_object::generate(&object_args) { - Ok(expanded) => expanded, - Err(err) => err.write_errors().into(), - } -} - -#[proc_macro_attribute] -#[allow(non_snake_case)] -pub fn ComplexObject(args: TokenStream, input: TokenStream) -> TokenStream { - let object_args = parse_nested_meta!(args::ComplexObject, args); - let mut item_impl = parse_macro_input!(input as ItemImpl); - match complex_object::generate(&object_args, &mut item_impl) { - Ok(expanded) => expanded, - Err(err) => err.write_errors().into(), - } -} - -#[proc_macro_derive(Enum, attributes(graphql))] -pub fn derive_enum(input: TokenStream) -> TokenStream { - let enum_args = match args::Enum::from_derive_input(&parse_macro_input!(input as DeriveInput)) { - Ok(enum_args) => enum_args, - Err(err) => return TokenStream::from(err.write_errors()), - }; - match r#enum::generate(&enum_args) { - Ok(expanded) => expanded, - Err(err) => err.write_errors().into(), - } -} - -#[proc_macro_derive(InputObject, attributes(graphql))] -pub fn derive_input_object(input: TokenStream) -> TokenStream { - let object_args = - match args::InputObject::from_derive_input(&parse_macro_input!(input as DeriveInput)) { - Ok(object_args) => object_args, - Err(err) => return TokenStream::from(err.write_errors()), - }; - match input_object::generate(&object_args) { - Ok(expanded) => expanded, - Err(err) => err.write_errors().into(), - } -} - -#[proc_macro_derive(Interface, attributes(graphql))] -pub fn derive_interface(input: TokenStream) -> TokenStream { - let interface_args = - match args::Interface::from_derive_input(&parse_macro_input!(input as DeriveInput)) { - Ok(interface_args) => interface_args, - Err(err) => return TokenStream::from(err.write_errors()), - }; - match interface::generate(&interface_args) { - Ok(expanded) => expanded, - Err(err) => err.write_errors().into(), - } -} - -#[proc_macro_derive(Union, attributes(graphql))] -pub fn derive_union(input: TokenStream) -> TokenStream { - let union_args = match args::Union::from_derive_input(&parse_macro_input!(input as DeriveInput)) - { - Ok(union_args) => union_args, - Err(err) => return TokenStream::from(err.write_errors()), - }; - match union::generate(&union_args) { - Ok(expanded) => expanded, - Err(err) => err.write_errors().into(), - } -} - -#[proc_macro_attribute] -#[allow(non_snake_case)] -pub fn Subscription(args: TokenStream, input: TokenStream) -> TokenStream { - let object_args = parse_nested_meta!(args::Subscription, args); - let mut item_impl = parse_macro_input!(input as ItemImpl); - match subscription::generate(&object_args, &mut item_impl) { - Ok(expanded) => expanded, - Err(err) => err.write_errors().into(), - } -} - -#[proc_macro_attribute] -#[allow(non_snake_case)] -pub fn Scalar(args: TokenStream, input: TokenStream) -> TokenStream { - let scalar_args = parse_nested_meta!(args::Scalar, args); - let mut item_impl = parse_macro_input!(input as ItemImpl); - match scalar::generate(&scalar_args, &mut item_impl) { - Ok(expanded) => expanded, - Err(err) => err.write_errors().into(), - } -} - -#[proc_macro_derive(MergedObject, attributes(graphql))] -pub fn derive_merged_object(input: TokenStream) -> TokenStream { - let object_args = - match args::MergedObject::from_derive_input(&parse_macro_input!(input as DeriveInput)) { - Ok(object_args) => object_args, - Err(err) => return TokenStream::from(err.write_errors()), - }; - match merged_object::generate(&object_args) { - Ok(expanded) => expanded, - Err(err) => err.write_errors().into(), - } -} - -#[proc_macro_derive(MergedSubscription, attributes(graphql))] -pub fn derive_merged_subscription(input: TokenStream) -> TokenStream { - let object_args = match args::MergedSubscription::from_derive_input(&parse_macro_input!( - input as DeriveInput - )) { - Ok(object_args) => object_args, - Err(err) => return TokenStream::from(err.write_errors()), - }; - match merged_subscription::generate(&object_args) { - Ok(expanded) => expanded, - Err(err) => err.write_errors().into(), - } -} - -#[proc_macro_derive(Description, attributes(graphql))] -pub fn derive_description(input: TokenStream) -> TokenStream { - let desc_args = - match args::Description::from_derive_input(&parse_macro_input!(input as DeriveInput)) { - Ok(desc_args) => desc_args, - Err(err) => return TokenStream::from(err.write_errors()), - }; - match description::generate(&desc_args) { - Ok(expanded) => expanded, - Err(err) => err.write_errors().into(), - } -} - -#[proc_macro_derive(NewType, attributes(graphql))] -pub fn derive_newtype(input: TokenStream) -> TokenStream { - let newtype_args = - match args::NewType::from_derive_input(&parse_macro_input!(input as DeriveInput)) { - Ok(newtype_args) => newtype_args, - Err(err) => return TokenStream::from(err.write_errors()), - }; - match newtype::generate(&newtype_args) { - Ok(expanded) => expanded, - Err(err) => err.write_errors().into(), - } -} - -#[proc_macro_attribute] -#[allow(non_snake_case)] -pub fn Directive(args: TokenStream, input: TokenStream) -> TokenStream { - let directive_args = parse_nested_meta!(args::Directive, args); - let mut item_fn = parse_macro_input!(input as ItemFn); - match directive::generate(&directive_args, &mut item_fn) { - Ok(expanded) => expanded, - Err(err) => err.write_errors().into(), - } -} - -#[proc_macro_attribute] -#[allow(non_snake_case)] -pub fn TypeDirective(args: TokenStream, input: TokenStream) -> TokenStream { - let directive_args = parse_nested_meta!(args::TypeDirective, args); - let mut item_fn = parse_macro_input!(input as ItemFn); - match type_directive::generate(&directive_args, &mut item_fn) { - Ok(expanded) => expanded, - Err(err) => err.write_errors().into(), - } -} - -#[proc_macro_derive(OneofObject, attributes(graphql))] -pub fn derive_oneof_object(input: TokenStream) -> TokenStream { - let object_args = - match args::OneofObject::from_derive_input(&parse_macro_input!(input as DeriveInput)) { - Ok(object_args) => object_args, - Err(err) => return TokenStream::from(err.write_errors()), - }; - match oneof_object::generate(&object_args) { - Ok(expanded) => expanded, - Err(err) => err.write_errors().into(), - } -} diff --git a/derive/src/merged_object.rs b/derive/src/merged_object.rs deleted file mode 100644 index 20c39a4ba..000000000 --- a/derive/src/merged_object.rs +++ /dev/null @@ -1,150 +0,0 @@ -use darling::ast::Data; -use proc_macro::TokenStream; -use proc_macro2::Span; -use quote::quote; -use syn::{Error, LitInt}; - -use crate::{ - args::{self, RenameTarget, TypeDirectiveLocation}, - utils::{ - GeneratorResult, gen_boxed_trait, gen_directive_calls, get_crate_name, get_rustdoc, - visible_fn, - }, -}; - -pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream> { - let crate_name = get_crate_name(object_args.internal); - let boxed_trait = gen_boxed_trait(&crate_name); - let ident = &object_args.ident; - let (impl_generics, ty_generics, where_clause) = object_args.generics.split_for_impl(); - let extends = object_args.extends; - let shareable = object_args.shareable; - let inaccessible = object_args.inaccessible; - let interface_object = object_args.interface_object; - let tags = object_args - .tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - let gql_typename = if !object_args.name_type { - let name = object_args - .name - .clone() - .unwrap_or_else(|| RenameTarget::Type.rename(ident.to_string())); - quote!(::std::borrow::Cow::Borrowed(#name)) - } else { - quote!(<Self as #crate_name::TypeName>::type_name()) - }; - - let directives = gen_directive_calls(&object_args.directives, TypeDirectiveLocation::Object); - - let desc = get_rustdoc(&object_args.attrs)? - .map(|s| quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#s)) }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - - let s = match &object_args.data { - Data::Struct(e) => e, - _ => { - return Err(Error::new_spanned( - ident, - "MergedObject can only be applied to an struct.", - ) - .into()); - } - }; - - let mut types = Vec::new(); - for field in &s.fields { - types.push(&field.ty); - } - - let create_merged_obj = { - let mut obj = quote! { #crate_name::MergedObjectTail }; - for i in 0..types.len() { - let n = LitInt::new(&format!("{}", i), Span::call_site()); - obj = quote! { #crate_name::MergedObject(&self.#n, #obj) }; - } - quote! { - #obj - } - }; - - let merged_type = { - let mut obj = quote! { #crate_name::MergedObjectTail }; - for ty in &types { - obj = quote! { #crate_name::MergedObject::<#ty, #obj> }; - } - obj - }; - - let visible = visible_fn(&object_args.visible); - let resolve_container = if object_args.serial { - quote! { #crate_name::resolver_utils::resolve_container_serial(ctx, self).await } - } else { - quote! { #crate_name::resolver_utils::resolve_container(ctx, self).await } - }; - - let expanded = quote! { - #[allow(clippy::all, clippy::pedantic)] - #boxed_trait - impl #impl_generics #crate_name::resolver_utils::ContainerType for #ident #ty_generics #where_clause { - async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> { - #create_merged_obj.resolve_field(ctx).await - } - - async fn find_entity(&self, ctx: &#crate_name::Context<'_>, params: &#crate_name::Value) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> { - #create_merged_obj.find_entity(ctx, params).await - } - } - - #[allow(clippy::all, clippy::pedantic)] - #boxed_trait - impl #impl_generics #crate_name::OutputType for #ident #ty_generics #where_clause { - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - #gql_typename - } - - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - registry.create_output_type::<Self, _>(#crate_name::registry::MetaTypeId::Object, |registry| { - let mut fields = ::std::default::Default::default(); - let mut cache_control = ::std::default::Default::default(); - - if let #crate_name::registry::MetaType::Object { - fields: obj_fields, - cache_control: obj_cache_control, - .. - } = registry.create_fake_output_type::<#merged_type>() { - fields = obj_fields; - cache_control = obj_cache_control; - } - - #crate_name::registry::MetaType::Object { - name: ::std::borrow::Cow::into_owned(#gql_typename), - description: #desc, - fields, - cache_control, - extends: #extends, - shareable: #shareable, - resolvable: true, - inaccessible: #inaccessible, - interface_object: #interface_object, - tags: ::std::vec![ #(#tags),* ], - keys: ::std::option::Option::None, - visible: #visible, - is_subscription: false, - rust_typename: ::std::option::Option::Some(::std::any::type_name::<Self>()), - directive_invocations: ::std::vec![ #(#directives),* ], - requires_scopes: ::std::vec![], - } - }) - } - - async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> { - #resolve_container - } - } - - impl #impl_generics #crate_name::ObjectType for #ident #ty_generics #where_clause {} - }; - Ok(expanded.into()) -} diff --git a/derive/src/merged_subscription.rs b/derive/src/merged_subscription.rs deleted file mode 100644 index 1c5cc92e5..000000000 --- a/derive/src/merged_subscription.rs +++ /dev/null @@ -1,105 +0,0 @@ -use darling::ast::Data; -use proc_macro::TokenStream; -use proc_macro2::Span; -use quote::quote; -use syn::{Error, LitInt}; - -use crate::{ - args::{self, RenameTarget}, - utils::{GeneratorResult, get_crate_name, get_rustdoc, visible_fn}, -}; - -pub fn generate(object_args: &args::MergedSubscription) -> GeneratorResult<TokenStream> { - let crate_name = get_crate_name(object_args.internal); - let ident = &object_args.ident; - let (impl_generics, ty_generics, where_clause) = object_args.generics.split_for_impl(); - let extends = object_args.extends; - let gql_typename = if !object_args.name_type { - let name = object_args - .name - .clone() - .unwrap_or_else(|| RenameTarget::Type.rename(ident.to_string())); - quote!(::std::borrow::Cow::Borrowed(#name)) - } else { - quote!(<Self as #crate_name::TypeName>::type_name()) - }; - - let desc = get_rustdoc(&object_args.attrs)? - .map(|s| quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#s)) }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - - let s = match &object_args.data { - Data::Struct(e) => e, - _ => { - return Err(Error::new_spanned( - ident, - "MergedSubscription can only be applied to an struct.", - ) - .into()); - } - }; - - let types: Vec<_> = s.fields.iter().map(|field| &field.ty).collect(); - - let create_field_stream: proc_macro2::TokenStream = (0..types.len()) - .map(|i| { - let n = LitInt::new(&i.to_string(), Span::call_site()); - quote!(.or_else(|| #crate_name::SubscriptionType::create_field_stream(&self.#n, ctx))) - }) - .collect(); - - let merged_type = types.iter().fold( - quote!(#crate_name::MergedObjectTail), - |obj, ty| quote!(#crate_name::MergedObject::<#ty, #obj>), - ); - - let visible = visible_fn(&object_args.visible); - let expanded = quote! { - #[allow(clippy::all, clippy::pedantic)] - impl #impl_generics #crate_name::SubscriptionType for #ident #ty_generics #where_clause { - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - #gql_typename - } - - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - registry.create_subscription_type::<Self, _>(|registry| { - let mut fields = ::std::default::Default::default(); - - if let #crate_name::registry::MetaType::Object { - fields: obj_fields, - .. - } = registry.create_fake_subscription_type::<#merged_type>() { - fields = obj_fields; - } - - #crate_name::registry::MetaType::Object { - name: ::std::borrow::Cow::into_owned(#gql_typename), - description: #desc, - fields, - cache_control: ::std::default::Default::default(), - extends: #extends, - keys: ::std::option::Option::None, - visible: #visible, - shareable: false, - resolvable: true, - inaccessible: false, - interface_object: false, - tags: ::std::default::Default::default(), - is_subscription: true, - rust_typename: ::std::option::Option::Some(::std::any::type_name::<Self>()), - directive_invocations: ::std::default::Default::default(), - requires_scopes: ::std::default::Default::default(), - } - }) - } - - fn create_field_stream<'__life>( - &'__life self, - ctx: &'__life #crate_name::Context<'__life> - ) -> ::std::option::Option<::std::pin::Pin<::std::boxed::Box<dyn #crate_name::futures_util::stream::Stream<Item = #crate_name::Response> + ::std::marker::Send + '__life>>> { - ::std::option::Option::None #create_field_stream - } - } - }; - Ok(expanded.into()) -} diff --git a/derive/src/newtype.rs b/derive/src/newtype.rs deleted file mode 100644 index 5ff7096b4..000000000 --- a/derive/src/newtype.rs +++ /dev/null @@ -1,144 +0,0 @@ -use darling::ast::{Data, Style}; -use proc_macro::TokenStream; -use quote::quote; -use syn::Error; - -use crate::{ - args::{self, NewTypeName, RenameTarget}, - utils::{GeneratorResult, gen_boxed_trait, get_crate_name, get_rustdoc, visible_fn}, -}; - -pub fn generate(newtype_args: &args::NewType) -> GeneratorResult<TokenStream> { - let crate_name = get_crate_name(newtype_args.internal); - let boxed_trait = gen_boxed_trait(&crate_name); - let ident = &newtype_args.ident; - let (impl_generics, ty_generics, where_clause) = newtype_args.generics.split_for_impl(); - let inaccessible = newtype_args.inaccessible; - let tags = newtype_args - .tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - let gql_typename = match &newtype_args.name { - NewTypeName::New(name) => Some(name.clone()), - NewTypeName::Rust => Some(RenameTarget::Type.rename(ident.to_string())), - NewTypeName::Original => None, - }; - let desc = get_rustdoc(&newtype_args.attrs)? - .map(|s| quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#s)) }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - let visible = visible_fn(&newtype_args.visible); - - let fields = match &newtype_args.data { - Data::Struct(e) => e, - _ => { - return Err( - Error::new_spanned(ident, "NewType can only be applied to an struct.").into(), - ); - } - }; - - if fields.style == Style::Tuple && fields.fields.len() != 1 { - return Err(Error::new_spanned(ident, "Invalid type.").into()); - } - let inner_ty = &fields.fields[0]; - let type_name = match &gql_typename { - Some(name) => quote! { ::std::borrow::Cow::Borrowed(#name) }, - None => quote! { <#inner_ty as #crate_name::InputType>::type_name() }, - }; - let create_type_info = if let Some(name) = &gql_typename { - let specified_by_url = match &newtype_args.specified_by_url { - Some(specified_by_url) => quote! { ::std::option::Option::Some(#specified_by_url) }, - None => quote! { ::std::option::Option::None }, - }; - - quote! { - registry.create_input_type::<#ident, _>(#crate_name::registry::MetaTypeId::Scalar, |_| #crate_name::registry::MetaType::Scalar { - name: ::std::borrow::ToOwned::to_owned(#name), - description: #desc, - is_valid: ::std::option::Option::Some(::std::sync::Arc::new(|value| <#ident as #crate_name::ScalarType>::is_valid(value))), - visible: #visible, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - specified_by_url: #specified_by_url, - directive_invocations: ::std::vec::Vec::new(), - requires_scopes: ::std::vec::Vec::new(), - }) - } - } else { - quote! { <#inner_ty as #crate_name::InputType>::create_type_info(registry) } - }; - - let expanded = quote! { - #[allow(clippy::all, clippy::pedantic)] - impl #impl_generics #crate_name::ScalarType for #ident #ty_generics #where_clause { - fn parse(value: #crate_name::Value) -> #crate_name::InputValueResult<Self> { - <#inner_ty as #crate_name::ScalarType>::parse(value).map(#ident).map_err(#crate_name::InputValueError::propagate) - } - - fn to_value(&self) -> #crate_name::Value { - <#inner_ty as #crate_name::ScalarType>::to_value(&self.0) - } - } - - impl #impl_generics ::std::convert::From<#inner_ty> for #ident #ty_generics #where_clause { - fn from(value: #inner_ty) -> Self { - Self(value) - } - } - - #[allow(clippy::from_over_into)] - impl #impl_generics ::std::convert::Into<#inner_ty> for #ident #ty_generics #where_clause { - fn into(self) -> #inner_ty { - self.0 - } - } - - #[allow(clippy::all, clippy::pedantic)] - impl #impl_generics #crate_name::InputType for #ident #ty_generics #where_clause { - type RawValueType = #inner_ty; - - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - #type_name - } - - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - #create_type_info - } - - fn parse(value: ::std::option::Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> { - <#ident as #crate_name::ScalarType>::parse(value.unwrap_or_default()) - } - - fn to_value(&self) -> #crate_name::Value { - <#ident as #crate_name::ScalarType>::to_value(self) - } - - fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> { - self.0.as_raw_value() - } - } - - #[allow(clippy::all, clippy::pedantic)] - #boxed_trait - impl #impl_generics #crate_name::OutputType for #ident #ty_generics #where_clause { - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - #type_name - } - - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - #create_type_info - } - - async fn resolve( - &self, - _: &#crate_name::ContextSelectionSet<'_>, - _field: &#crate_name::Positioned<#crate_name::parser::types::Field> - ) -> #crate_name::ServerResult<#crate_name::Value> { - Ok(#crate_name::ScalarType::to_value(self)) - } - } - }; - - Ok(expanded.into()) -} diff --git a/derive/src/object.rs b/derive/src/object.rs deleted file mode 100644 index 6d20784fb..000000000 --- a/derive/src/object.rs +++ /dev/null @@ -1,1036 +0,0 @@ -use std::str::FromStr; - -use proc_macro::TokenStream; -use proc_macro2::{Ident, Span}; -use quote::quote; -use syn::{ - Attribute, Block, Error, Expr, FnArg, ImplItem, ItemImpl, Pat, PatIdent, ReturnType, Token, - Type, TypeReference, ext::IdentExt, punctuated::Punctuated, -}; - -use crate::{ - args::{self, RenameRuleExt, RenameTarget, Resolvability, TypeDirectiveLocation}, - output_type::OutputType, - utils::{ - GeneratorResult, extract_input_args, gen_boxed_trait, gen_deprecation, gen_directive_calls, - generate_default, generate_guards, get_cfg_attrs, get_crate_name, get_rustdoc, - get_type_path_and_name, parse_complexity_expr, parse_graphql_attrs, remove_graphql_attrs, - visible_fn, - }, - validators::Validators, -}; - -pub fn generate( - object_args: &args::Object, - item_impl: &mut ItemImpl, -) -> GeneratorResult<TokenStream> { - let crate_name = get_crate_name(object_args.internal); - let boxed_trait = gen_boxed_trait(&crate_name); - let (self_ty, self_name) = get_type_path_and_name(item_impl.self_ty.as_ref())?; - let (impl_generics, _, where_clause) = item_impl.generics.split_for_impl(); - let extends = object_args.extends; - let shareable = object_args.shareable; - let inaccessible = object_args.inaccessible; - let interface_object = object_args.interface_object; - let resolvable = matches!(object_args.resolvability, Resolvability::Resolvable); - let tags = object_args - .tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - let requires_scopes = object_args - .requires_scopes - .iter() - .map(|scopes| quote!(::std::string::ToString::to_string(#scopes))) - .collect::<Vec<_>>(); - let directives = gen_directive_calls(&object_args.directives, TypeDirectiveLocation::Object); - let gql_typename = if !object_args.name_type { - object_args - .name - .as_ref() - .map(|name| quote!(::std::borrow::Cow::Borrowed(#name))) - .unwrap_or_else(|| { - let name = RenameTarget::Type.rename(self_name.clone()); - quote!(::std::borrow::Cow::Borrowed(#name)) - }) - } else { - quote!(<Self as #crate_name::TypeName>::type_name()) - }; - - let desc = if object_args.use_type_description { - quote! { ::std::option::Option::Some(::std::string::ToString::to_string(<Self as #crate_name::Description>::description())) } - } else { - get_rustdoc(&item_impl.attrs)? - .map(|s| quote!(::std::option::Option::Some(::std::string::ToString::to_string(#s)))) - .unwrap_or_else(|| quote!(::std::option::Option::None)) - }; - - let mut flattened_resolvers = Vec::new(); - let mut resolvers = Vec::new(); - let mut resolver_idents = Vec::new(); - let mut resolver_fns = Vec::new(); - let mut schema_fields = Vec::new(); - let mut find_entities = Vec::new(); - let mut add_keys = Vec::new(); - let mut create_entity_types = Vec::new(); - - let mut unresolvable_key = String::new(); - - // Computation of the derived fields - let mut derived_impls = vec![]; - for item in &mut item_impl.items { - if let ImplItem::Fn(method) = item { - let method_args: args::ObjectField = - parse_graphql_attrs(&method.attrs)?.unwrap_or_default(); - - for derived in method_args.derived { - if derived.name.is_some() && derived.into.is_some() { - let base_function_name = &method.sig.ident; - let name = derived.name.unwrap(); - let with = derived.with; - let into = Type::Verbatim( - proc_macro2::TokenStream::from_str(&derived.into.unwrap()).unwrap(), - ); - - let mut new_impl = method.clone(); - new_impl.sig.ident = name; - new_impl.sig.output = - syn::parse2::<ReturnType>(quote! { -> #crate_name::Result<#into> }) - .expect("invalid result type"); - - let should_create_context = new_impl - .sig - .inputs - .iter() - .nth(1) - .map(|x| { - if let FnArg::Typed(pat) = x { - if let Type::Reference(TypeReference { elem, .. }) = &*pat.ty { - if let Type::Path(path) = elem.as_ref() { - return path.path.segments.last().unwrap().ident - != "Context"; - } - } - }; - true - }) - .unwrap_or(true); - - if should_create_context { - let arg_ctx = syn::parse2::<FnArg>(quote! { ctx: &Context<'_> }) - .expect("invalid arg type"); - new_impl.sig.inputs.insert(1, arg_ctx); - } - - let other_atts: Punctuated<Ident, Token![,]> = Punctuated::from_iter( - new_impl - .sig - .inputs - .iter() - .filter_map(|x| match x { - FnArg::Typed(pat) => match &*pat.pat { - Pat::Ident(ident) => Some(Ok(ident.ident.clone())), - _ => Some(Err(Error::new_spanned( - pat, - "Must be a simple argument", - ))), - }, - FnArg::Receiver(_) => None, - }) - .collect::<Result<Vec<Ident>, Error>>()? - .into_iter(), - ); - - let new_block = match with { - Some(with) => quote!({ - ::std::result::Result::Ok(#with(#self_ty::#base_function_name(&self, #other_atts).await?)) - }), - None => quote!({ - { - ::std::result::Result::Ok(#self_ty::#base_function_name(&self, #other_atts).await?.into()) - } - }), - }; - - new_impl.block = syn::parse2::<Block>(new_block).expect("invalid block"); - - derived_impls.push(ImplItem::Fn(new_impl)); - } - } - } - } - item_impl.items.append(&mut derived_impls); - - for item in &mut item_impl.items { - if let ImplItem::Fn(method) = item { - let method_args: args::ObjectField = - parse_graphql_attrs(&method.attrs)?.unwrap_or_default(); - - if method_args.entity { - let cfg_attrs = get_cfg_attrs(&method.attrs); - - if method.sig.asyncness.is_none() { - return Err(Error::new_spanned(method, "Must be asynchronous").into()); - } - - let args = extract_input_args::<args::Argument>(&crate_name, method)?; - - let ty = match &method.sig.output { - ReturnType::Type(_, ty) => OutputType::parse(ty)?, - ReturnType::Default => { - return Err(Error::new_spanned( - &method.sig.output, - "Resolver must have a return type", - ) - .into()); - } - }; - - let entity_type = ty.value_type(); - let mut key_pat = Vec::new(); - let mut key_getter = Vec::new(); - let mut use_keys = Vec::new(); - let mut get_federation_key = Vec::new(); - let mut requires_getter = Vec::new(); - let all_key = args.iter().all(|(_, _, arg)| !arg.key); - - if args.is_empty() { - return Err(Error::new_spanned( - method, - "Entity need to have at least one key.", - ) - .into()); - } - - for (ident, ty, args::Argument { name, key, .. }) in &args { - let is_key = all_key || *key; - let name = name.clone().unwrap_or_else(|| { - object_args - .rename_args - .rename(ident.ident.unraw().to_string(), RenameTarget::Argument) - }); - - if is_key { - get_federation_key.push(quote! { - if let Some(fields) = <#ty as #crate_name::InputType>::federation_fields() { - key_str.push(format!("{} {}", #name, fields)); - } else { - key_str.push(#name.to_string()); - } - }); - - key_pat.push(quote! { - ::std::option::Option::Some(#ident) - }); - key_getter.push(quote! { - params.get(#name).and_then(|value| { - let value: ::std::option::Option<#ty> = #crate_name::InputType::parse(::std::option::Option::Some(::std::clone::Clone::clone(&value))).ok(); - value - }) - }); - } else { - // requires - requires_getter.push(quote! { - let #ident: #ty = #crate_name::InputType::parse(params.get(#name).cloned()). - map_err(|err| err.into_server_error(ctx.item.pos))?; - }); - } - use_keys.push(ident); - } - - add_keys.push(quote! { - { - let mut key_str = Vec::new(); - #(#get_federation_key)* - registry.add_keys(&<#entity_type as #crate_name::OutputType>::type_name(), &key_str.join(" ")); - } - }); - create_entity_types.push( - quote! { <#entity_type as #crate_name::OutputType>::create_type_info(registry); }, - ); - - let field_ident = &method.sig.ident; - if let OutputType::Value(inner_ty) = &ty { - let block = &method.block; - let new_block = quote!({ - { - let value:#inner_ty = async move #block.await; - ::std::result::Result::Ok(value) - } - }); - method.block = syn::parse2::<Block>(new_block).expect("invalid block"); - method.sig.output = - syn::parse2::<ReturnType>(quote! { -> #crate_name::Result<#inner_ty> }) - .expect("invalid result type"); - } - let do_find = quote! { - self.#field_ident(ctx, #(#use_keys),*) - .await.map_err(|err| ::std::convert::Into::<#crate_name::Error>::into(err) - .into_server_error(ctx.item.pos)) - }; - - find_entities.push(( - args.len(), - quote! { - #(#cfg_attrs)* - if typename == &<#entity_type as #crate_name::OutputType>::type_name() { - if let (#(#key_pat),*) = (#(#key_getter),*) { - let f = async move { - #(#requires_getter)* - #do_find - }; - let obj = f.await.map_err(|err| ctx.set_error_path(err))?; - let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set); - return #crate_name::OutputType::resolve(&obj, &ctx_obj, ctx.item).await.map(::std::option::Option::Some); - } - } - }, - )); - } else if !method_args.skip { - if method.sig.asyncness.is_none() { - return Err(Error::new_spanned(method, "Must be asynchronous").into()); - } - let cfg_attrs = get_cfg_attrs(&method.attrs); - - if method_args.flatten { - // Only used to inject the context placeholder if required. - extract_input_args::<args::Argument>(&crate_name, method)?; - - let ty = match &method.sig.output { - ReturnType::Type(_, ty) => OutputType::parse(ty)?, - ReturnType::Default => { - return Err(Error::new_spanned( - &method.sig.output, - "Flatten resolver must have a return type", - ) - .into()); - } - }; - let ty = ty.value_type(); - let ident = &method.sig.ident; - - schema_fields.push(quote! { - <#ty>::create_type_info(registry); - if let #crate_name::registry::MetaType::Object { fields: obj_fields, .. } = - registry.create_fake_output_type::<#ty>() { - fields.extend(obj_fields); - } - }); - - flattened_resolvers.push(quote! { - #(#cfg_attrs)* - if let ::std::option::Option::Some(value) = #crate_name::ContainerType::resolve_field(&self.#ident(ctx).await, ctx).await? { - return ::std::result::Result::Ok(std::option::Option::Some(value)); - } - }); - - remove_graphql_attrs(&mut method.attrs); - continue; - } - - let field_name = method_args.name.clone().unwrap_or_else(|| { - object_args - .rename_fields - .rename(method.sig.ident.unraw().to_string(), RenameTarget::Field) - }); - let field_desc = get_rustdoc(&method.attrs)? - .map(|s| quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#s)) }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - let field_deprecation = gen_deprecation(&method_args.deprecation, &crate_name); - let external = method_args.external; - let shareable = method_args.shareable; - let inaccessible = method_args.inaccessible; - let tags = method_args - .tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - let requires_scopes = method_args - .requires_scopes - .iter() - .map(|scopes| quote!(::std::string::ToString::to_string(#scopes))) - .collect::<Vec<_>>(); - - unresolvable_key.push_str(&field_name); - unresolvable_key.push(' '); - - let directives = gen_directive_calls( - &method_args.directives, - TypeDirectiveLocation::FieldDefinition, - ); - - let override_from = match &method_args.override_from { - Some(from) => { - quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#from)) } - } - None => quote! { ::std::option::Option::None }, - }; - let requires = match &method_args.requires { - Some(requires) => { - quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#requires)) } - } - None => quote! { ::std::option::Option::None }, - }; - let provides = match &method_args.provides { - Some(provides) => { - quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#provides)) } - } - None => quote! { ::std::option::Option::None }, - }; - let cache_control = { - let public = method_args.cache_control.is_public(); - let max_age = if method_args.cache_control.no_cache { - -1 - } else { - method_args.cache_control.max_age as i32 - }; - quote! { - #crate_name::CacheControl { - public: #public, - max_age: #max_age, - } - } - }; - - let args = extract_input_args::<args::Argument>(&crate_name, method)?; - let mut schema_args = Vec::new(); - let mut params = Vec::new(); - - for ( - ident, - ty, - args::Argument { - name, - desc, - default, - default_with, - process_with, - validator, - visible, - secret, - inaccessible, - tags, - directives, - deprecation, - .. - }, - ) in &args - { - let name = name.clone().unwrap_or_else(|| { - object_args - .rename_args - .rename(ident.ident.unraw().to_string(), RenameTarget::Argument) - }); - let desc = desc - .as_ref() - .map(|s| quote! {::std::option::Option::Some(::std::string::ToString::to_string(#s))}) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - let default = generate_default(default, default_with)?; - let schema_default = default - .as_ref() - .map(|value| { - quote! { - ::std::option::Option::Some(::std::string::ToString::to_string( - &<#ty as #crate_name::InputType>::to_value(&#value) - )) - } - }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - - let visible = visible_fn(visible); - let tags = tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - let deprecation = gen_deprecation(deprecation, &crate_name); - let directives = - gen_directive_calls(directives, TypeDirectiveLocation::ArgumentDefinition); - - schema_args.push(quote! { - args.insert(::std::borrow::ToOwned::to_owned(#name), #crate_name::registry::MetaInputValue { - name: ::std::string::ToString::to_string(#name), - description: #desc, - ty: <#ty as #crate_name::InputType>::create_type_info(registry), - deprecation: #deprecation, - default_value: #schema_default, - visible: #visible, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - is_secret: #secret, - directive_invocations: ::std::vec![ #(#directives),* ], - }); - }); - - params.push(FieldResolverParameter { - ty, - name, - default, - process_with, - validator, - ident: ident.clone(), - }); - } - - let ty = match &method.sig.output { - ReturnType::Type(_, ty) => OutputType::parse(ty)?, - ReturnType::Default => { - return Err(Error::new_spanned( - &method.sig.output, - "Resolver must have a return type", - ) - .into()); - } - }; - let schema_ty = ty.value_type(); - let visible = visible_fn(&method_args.visible); - - let complexity = if let Some(complexity) = &method_args.complexity { - let (variables, expr) = parse_complexity_expr(complexity.clone())?; - let mut parse_args = Vec::new(); - for variable in variables { - if let Some(( - ident, - ty, - args::Argument { - name, - default, - default_with, - .. - }, - )) = args - .iter() - .find(|(pat_ident, _, _)| pat_ident.ident == variable) - { - let default = match generate_default(default, default_with)? { - Some(default) => { - quote! { ::std::option::Option::Some(|| -> #ty { #default }) } - } - None => quote! { ::std::option::Option::None }, - }; - let name = name.clone().unwrap_or_else(|| { - object_args - .rename_args - .rename(ident.ident.unraw().to_string(), RenameTarget::Argument) - }); - parse_args.push(quote! { - let #ident: #ty = __ctx.param_value(__variables_definition, __field, #name, #default)?; - }); - } - } - quote! { - ::std::option::Option::Some(|__ctx, __variables_definition, __field, child_complexity| { - #(#parse_args)* - ::std::result::Result::Ok(#expr) - }) - } - } else { - quote! { ::std::option::Option::None } - }; - - schema_fields.push(quote! { - #(#cfg_attrs)* - fields.insert(::std::borrow::ToOwned::to_owned(#field_name), #crate_name::registry::MetaField { - name: ::std::borrow::ToOwned::to_owned(#field_name), - description: #field_desc, - args: { - let mut args = #crate_name::indexmap::IndexMap::new(); - #(#schema_args)* - args - }, - ty: <#schema_ty as #crate_name::OutputType>::create_type_info(registry), - deprecation: #field_deprecation, - cache_control: #cache_control, - external: #external, - provides: #provides, - requires: #requires, - shareable: #shareable, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - override_from: #override_from, - visible: #visible, - compute_complexity: #complexity, - directive_invocations: ::std::vec![ #(#directives),* ], - requires_scopes: ::std::vec![ #(#requires_scopes),* ], - }); - }); - - let field_ident = &method.sig.ident; - if let OutputType::Value(inner_ty) = &ty { - let block = &method.block; - let new_block = quote!({ - { - ::std::result::Result::Ok(async move { - let value:#inner_ty = #block; - value - }.await) - } - }); - method.block = syn::parse2::<Block>(new_block).expect("invalid block"); - method.sig.output = - syn::parse2::<ReturnType>(quote! { -> #crate_name::Result<#inner_ty> }) - .expect("invalid result type"); - } - - resolver_idents.push((field_name.clone(), field_ident.clone(), cfg_attrs.clone())); - let (resolver_fn_name, resolver_fn) = generate_field_resolver_method( - &crate_name, - object_args, - &method_args, - &FieldResolver { - resolver_fn_ident: field_ident, - cfg_attrs: &cfg_attrs, - params: ¶ms, - }, - )?; - - resolver_fns.push(resolver_fn); - - resolvers.push(quote! { - #(#cfg_attrs)* - ::std::option::Option::Some(__FieldIdent::#field_ident) => { - return self.#resolver_fn_name(&ctx).await; - } - }); - } - - remove_graphql_attrs(&mut method.attrs); - } - } - - let cache_control = { - let public = object_args.cache_control.is_public(); - let max_age = if object_args.cache_control.no_cache { - -1 - } else { - object_args.cache_control.max_age as i32 - }; - quote! { - #crate_name::CacheControl { - public: #public, - max_age: #max_age, - } - } - }; - - find_entities.sort_by(|(a, _), (b, _)| b.cmp(a)); - let find_entities_iter = find_entities.iter().map(|(_, code)| code); - - if resolvers.is_empty() && create_entity_types.is_empty() { - return Err(Error::new_spanned( - self_ty, - "A GraphQL Object type must define one or more fields.", - ) - .into()); - } - - let keys = match &object_args.resolvability { - Resolvability::Resolvable => quote!(::std::option::Option::None), - Resolvability::Unresolvable { key: Some(key) } => quote!(::std::option::Option::Some( - ::std::vec![ ::std::string::ToString::to_string(#key)] - )), - Resolvability::Unresolvable { key: None } => { - unresolvable_key.pop(); // need to remove the trailing space - - quote!(::std::option::Option::Some( - ::std::vec![ ::std::string::ToString::to_string(#unresolvable_key)] - )) - } - }; - - let visible = visible_fn(&object_args.visible); - let resolve_container = if object_args.serial { - quote! { #crate_name::resolver_utils::resolve_container_serial(ctx, self).await } - } else { - quote! { #crate_name::resolver_utils::resolve_container(ctx, self).await } - }; - - let resolve_field_resolver_match = generate_field_match(resolvers)?; - let field_ident = generate_fields_enum(&crate_name, resolver_idents)?; - - let expanded = if object_args.concretes.is_empty() { - quote! { - #item_impl - - #[doc(hidden)] - #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] - const _: () = { - #field_ident - - impl #impl_generics #self_ty #where_clause { - #(#resolver_fns)* - } - - #[allow(clippy::all, clippy::pedantic, clippy::suspicious_else_formatting)] - #[allow(unused_braces, unused_variables, unused_parens, unused_mut)] - #boxed_trait - impl #impl_generics #crate_name::resolver_utils::ContainerType for #self_ty #where_clause { - async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> { - #resolve_field_resolver_match - #(#flattened_resolvers)* - ::std::result::Result::Ok(::std::option::Option::None) - } - - async fn find_entity(&self, ctx: &#crate_name::Context<'_>, params: &#crate_name::Value) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> { - let params = match params { - #crate_name::Value::Object(params) => params, - _ => return ::std::result::Result::Ok(::std::option::Option::None), - }; - let typename = if let ::std::option::Option::Some(#crate_name::Value::String(typename)) = params.get("__typename") { - typename - } else { - return ::std::result::Result::Err( - #crate_name::ServerError::new(r#""__typename" must be an existing string."#, ::std::option::Option::Some(ctx.item.pos)) - ); - }; - #(#find_entities_iter)* - ::std::result::Result::Ok(::std::option::Option::None) - } - } - - #[allow(clippy::all, clippy::pedantic)] - #boxed_trait - impl #impl_generics #crate_name::OutputType for #self_ty #where_clause { - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - #gql_typename - } - - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - let ty = registry.create_output_type::<Self, _>(#crate_name::registry::MetaTypeId::Object, |registry| #crate_name::registry::MetaType::Object { - name: ::std::borrow::Cow::into_owned(#gql_typename), - description: #desc, - fields: { - let mut fields = #crate_name::indexmap::IndexMap::new(); - #(#schema_fields)* - fields - }, - cache_control: #cache_control, - extends: #extends, - shareable: #shareable, - resolvable: #resolvable, - inaccessible: #inaccessible, - interface_object: #interface_object, - tags: ::std::vec![ #(#tags),* ], - keys: #keys, - visible: #visible, - is_subscription: false, - rust_typename: ::std::option::Option::Some(::std::any::type_name::<Self>()), - directive_invocations: ::std::vec![ #(#directives),* ], - requires_scopes: ::std::vec![ #(#requires_scopes),* ], - }); - #(#create_entity_types)* - #(#add_keys)* - ty - } - - async fn resolve( - &self, - ctx: &#crate_name::ContextSelectionSet<'_>, - _field: &#crate_name::Positioned<#crate_name::parser::types::Field> - ) -> #crate_name::ServerResult<#crate_name::Value> { - #resolve_container - } - } - - impl #impl_generics #crate_name::ObjectType for #self_ty #where_clause {} - }; - } - } else { - let mut codes = Vec::new(); - - codes.push(quote! { - #item_impl - - #[doc(hidden)] - #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] - const _: () = { - #field_ident - - impl #impl_generics #self_ty #where_clause { - #(#resolver_fns)* - - fn __internal_create_type_info(registry: &mut #crate_name::registry::Registry, name: &str) -> ::std::string::String where Self: #crate_name::OutputType { - let ty = registry.create_output_type::<Self, _>(#crate_name::registry::MetaTypeId::Object, |registry| #crate_name::registry::MetaType::Object { - name: ::std::borrow::ToOwned::to_owned(name), - description: #desc, - fields: { - let mut fields = #crate_name::indexmap::IndexMap::new(); - #(#schema_fields)* - fields - }, - cache_control: #cache_control, - extends: #extends, - shareable: #shareable, - resolvable: #resolvable, - inaccessible: #inaccessible, - interface_object: #interface_object, - tags: ::std::vec![ #(#tags),* ], - keys: #keys, - visible: #visible, - is_subscription: false, - rust_typename: ::std::option::Option::Some(::std::any::type_name::<Self>()), - directive_invocations: ::std::vec![ #(#directives),* ], - requires_scopes: ::std::vec![], - }); - #(#create_entity_types)* - #(#add_keys)* - ty - } - - async fn __internal_resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> where Self: #crate_name::ContainerType { - #resolve_field_resolver_match - #(#flattened_resolvers)* - ::std::result::Result::Ok(::std::option::Option::None) - } - - async fn __internal_find_entity(&self, ctx: &#crate_name::Context<'_>, params: &#crate_name::Value) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> { - let params = match params { - #crate_name::Value::Object(params) => params, - _ => return ::std::result::Result::Ok(::std::option::Option::None), - }; - let typename = if let ::std::option::Option::Some(#crate_name::Value::String(typename)) = params.get("__typename") { - typename - } else { - return ::std::result::Result::Err( - #crate_name::ServerError::new(r#""__typename" must be an existing string."#, ::std::option::Option::Some(ctx.item.pos)) - ); - }; - #(#find_entities_iter)* - ::std::result::Result::Ok(::std::option::Option::None) - } - } - }; - }); - - for concrete in &object_args.concretes { - let gql_typename = &concrete.name; - let params = &concrete.params.0; - let ty = { - let s = quote!(#self_ty).to_string(); - match s.rfind('<') { - Some(pos) => syn::parse_str(&s[..pos]).unwrap(), - None => self_ty.clone(), - } - }; - let concrete_type = quote! { #ty<#(#params),*> }; - - let def_bounds = if !concrete.bounds.0.is_empty() { - let bounds = concrete.bounds.0.iter().map(|b| quote!(#b)); - Some(quote!(<#(#bounds),*>)) - } else { - None - }; - - codes.push(quote! { - #boxed_trait - impl #def_bounds #crate_name::resolver_utils::ContainerType for #concrete_type { - async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> { - self.__internal_resolve_field(ctx).await - } - - async fn find_entity(&self, ctx: &#crate_name::Context<'_>, params: &#crate_name::Value) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> { - self.__internal_find_entity(ctx, params).await - } - } - - #boxed_trait - impl #def_bounds #crate_name::OutputType for #concrete_type { - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - ::std::borrow::Cow::Borrowed(#gql_typename) - } - - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - Self::__internal_create_type_info(registry, #gql_typename) - } - - async fn resolve( - &self, - ctx: &#crate_name::ContextSelectionSet<'_>, - _field: &#crate_name::Positioned<#crate_name::parser::types::Field> - ) -> #crate_name::ServerResult<#crate_name::Value> { - #resolve_container - } - } - - impl #def_bounds #crate_name::ObjectType for #concrete_type {} - }); - } - - quote!(#(#codes)*) - }; - - Ok(expanded.into()) -} - -fn generate_field_match( - resolvers: Vec<proc_macro2::TokenStream>, -) -> GeneratorResult<proc_macro2::TokenStream> { - if resolvers.is_empty() { - return Ok(quote!()); - } - - Ok(quote! { - let __field = __FieldIdent::from_name(&ctx.item.node.name.node); - match __field { - #(#resolvers)* - None => {} - } - }) -} - -fn generate_fields_enum( - crate_name: &proc_macro2::TokenStream, - fields: Vec<(String, Ident, Vec<Attribute>)>, -) -> GeneratorResult<proc_macro2::TokenStream> { - // If there are no non-entity/flattened resolvers we can avoid the whole enum - if fields.is_empty() { - return Ok(quote!()); - } - - let enum_variants = fields.iter().map(|(_, field_ident, cfg_attrs)| { - quote! { - #(#cfg_attrs)* - #field_ident - } - }); - - let matches = fields.iter().map(|(field_name, field_ident, cfg_attrs)| { - quote! { - #(#cfg_attrs)* - #field_name => ::std::option::Option::Some(__FieldIdent::#field_ident), - } - }); - - Ok(quote! { - #[allow(non_camel_case_types)] - #[doc(hidden)] - enum __FieldIdent { - #(#enum_variants,)* - } - - impl __FieldIdent { - fn from_name(__name: &#crate_name::Name) -> ::std::option::Option<__FieldIdent> { - match __name.as_str() { - #(#matches)* - _ => ::std::option::Option::None - } - } - } - }) -} - -fn generate_field_resolver_method( - crate_name: &proc_macro2::TokenStream, - object_args: &args::Object, - method_args: &args::ObjectField, - field: &FieldResolver, -) -> GeneratorResult<(Ident, proc_macro2::TokenStream)> { - let FieldResolver { - resolver_fn_ident: resolver_ident, - params, - cfg_attrs, - } = field; - - let extract_params = params - .iter() - .map(|param| generate_parameter_extraction(crate_name, param)) - .collect::<Result<Vec<_>, _>>()?; - let use_params = params.iter().map( - |FieldResolverParameter { - ident: PatIdent { ident, .. }, - .. - }| ident, - ); - - let guard_map_err = quote! { - .map_err(|err| err.into_server_error(ctx.item.pos)) - }; - let guard = match method_args.guard.as_ref().or(object_args.guard.as_ref()) { - Some(code) => Some(generate_guards(crate_name, code, guard_map_err)?), - None => None, - }; - - let mut resolve_fn_name = - syn::parse_str::<Ident>(&format!("__{}_resolver", field.resolver_fn_ident.unraw()))?; - resolve_fn_name.set_span(Span::call_site()); - - let function = quote! { - #[doc(hidden)] - #(#cfg_attrs)* - #[allow(non_snake_case)] - async fn #resolve_fn_name(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> { - let f = async { - #(#extract_params)* - #guard - let res = self.#resolver_ident(ctx, #(#use_params),*).await; - res.map_err(|err| ::std::convert::Into::<#crate_name::Error>::into(err).into_server_error(ctx.item.pos)) - }; - let obj = f.await.map_err(|err| ctx.set_error_path(err))?; - let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set); - return #crate_name::OutputType::resolve(&obj, &ctx_obj, ctx.item).await.map(::std::option::Option::Some); - } - }; - - Ok((resolve_fn_name, function)) -} - -fn generate_parameter_extraction( - crate_name: &proc_macro2::TokenStream, - parameter: &FieldResolverParameter, -) -> GeneratorResult<proc_macro2::TokenStream> { - let FieldResolverParameter { - ty, - name, - default, - process_with, - validator, - ident, - } = parameter; - - let default = match default { - Some(default) => { - quote! { ::std::option::Option::Some(|| -> #ty { #default }) } - } - None => quote! { ::std::option::Option::None }, - }; - - let process_with = match process_with.as_ref() { - Some(fn_path) => quote! { #fn_path(&mut #ident); }, - None => Default::default(), - }; - - let validators = (*validator).clone().unwrap_or_default().create_validators( - crate_name, - quote!(&#ident), - Some(quote!(.map_err(|err| err.into_server_error(__pos)))), - )?; - - let mut non_mut_ident = ident.clone(); - non_mut_ident.mutability = None; - let ident = &ident.ident; - Ok(quote! { - #[allow(non_snake_case, unused_variables, unused_mut)] - // Todo: if there are no processors we can drop the mut. - let (__pos, mut #non_mut_ident) = ctx.param_value::<#ty>(#name, #default)?; - #process_with - #validators - #[allow(non_snake_case, unused_variables)] - let #ident = #non_mut_ident; - }) -} - -/// IR representation of a field resolver. -struct FieldResolver<'a> { - resolver_fn_ident: &'a Ident, - cfg_attrs: &'a [Attribute], - /// Parsed Parameters for this resolver - params: &'a [FieldResolverParameter<'a>], -} - -struct FieldResolverParameter<'a> { - ty: &'a Type, - process_with: &'a Option<Expr>, - validator: &'a Option<Validators>, - ident: PatIdent, - name: String, - default: Option<proc_macro2::TokenStream>, -} diff --git a/derive/src/oneof_object.rs b/derive/src/oneof_object.rs deleted file mode 100644 index 5bcd171fd..000000000 --- a/derive/src/oneof_object.rs +++ /dev/null @@ -1,305 +0,0 @@ -use darling::ast::{Data, Style}; -use proc_macro::TokenStream; -use quote::quote; -use syn::{Error, Type}; - -use crate::{ - args::{self, RenameRuleExt, RenameTarget, TypeDirectiveLocation}, - utils::{ - GeneratorResult, gen_deprecation, gen_directive_calls, get_crate_name, get_rustdoc, - visible_fn, - }, -}; - -pub fn generate(object_args: &args::OneofObject) -> GeneratorResult<TokenStream> { - let crate_name = get_crate_name(object_args.internal); - let (impl_generics, ty_generics, where_clause) = object_args.generics.split_for_impl(); - let ident = &object_args.ident; - let desc = get_rustdoc(&object_args.attrs)? - .map(|s| quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#s)) }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - let inaccessible = object_args.inaccessible; - let tags = object_args - .tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - let directives = - gen_directive_calls(&object_args.directives, TypeDirectiveLocation::InputObject); - let gql_typename = if !object_args.name_type { - let name = object_args - .input_name - .clone() - .or_else(|| object_args.name.clone()) - .unwrap_or_else(|| RenameTarget::Type.rename(ident.to_string())); - quote!(::std::borrow::Cow::Borrowed(#name)) - } else { - quote!(<Self as #crate_name::TypeName>::type_name()) - }; - let s = match &object_args.data { - Data::Enum(s) => s, - _ => { - return Err( - Error::new_spanned(ident, "InputObject can only be applied to an enum.").into(), - ); - } - }; - - let mut enum_names = Vec::new(); - let mut schema_fields = Vec::new(); - let mut parse_item = Vec::new(); - let mut put_fields = Vec::new(); - - for variant in s { - let enum_name = &variant.ident; - let field_name = variant.name.clone().unwrap_or_else(|| { - object_args - .rename_fields - .rename(enum_name.to_string(), RenameTarget::Field) - }); - let inaccessible = variant.inaccessible; - let tags = variant - .tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - let desc = get_rustdoc(&variant.attrs)? - .map(|s| quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#s)) }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - let ty = match variant.fields.style { - Style::Tuple if variant.fields.fields.len() == 1 => &variant.fields.fields[0], - Style::Tuple => { - return Err(Error::new_spanned( - enum_name, - "Only single value variants are supported", - ) - .into()); - } - Style::Unit => { - return Err( - Error::new_spanned(enum_name, "Empty variants are not supported").into(), - ); - } - Style::Struct => { - return Err(Error::new_spanned( - enum_name, - "Variants with named fields are not supported", - ) - .into()); - } - }; - - // Type may be wrapped in `Type::Group` if the type comes from a macro - // substitution, so unwrap it. - let ty = match ty { - Type::Group(tg) => &*tg.elem, - ty => ty, - }; - - let directives = gen_directive_calls( - &variant.directives, - TypeDirectiveLocation::InputFieldDefinition, - ); - - if let Type::Path(_) = ty { - enum_names.push(enum_name); - - let secret = variant.secret; - let visible = visible_fn(&variant.visible); - let deprecation = gen_deprecation(&variant.deprecation, &crate_name); - - schema_fields.push(quote! { - fields.insert(::std::borrow::ToOwned::to_owned(#field_name), #crate_name::registry::MetaInputValue { - name: ::std::string::ToString::to_string(#field_name), - description: #desc, - ty: <::std::option::Option<#ty> as #crate_name::InputType>::create_type_info(registry), - deprecation: #deprecation, - default_value: ::std::option::Option::None, - visible: #visible, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - is_secret: #secret, - directive_invocations: ::std::vec![ #(#directives),* ], - }); - }); - - let validators = variant - .validator - .clone() - .unwrap_or_default() - .create_validators( - &crate_name, - quote!(&value), - Some(quote!(.map_err(#crate_name::InputValueError::propagate))), - )?; - - parse_item.push(quote! { - if obj.contains_key(#field_name) && obj.len() == 1 { - let value = #crate_name::InputType::parse(obj.remove(#field_name)).map_err(#crate_name::InputValueError::propagate)?; - #validators - return ::std::result::Result::Ok(Self::#enum_name(value)); - } - }); - - put_fields.push(quote! { - Self::#enum_name(value) => { - map.insert(#crate_name::Name::new(#field_name), #crate_name::InputType::to_value(value)); - } - }); - } else { - return Err(Error::new_spanned(ty, "Invalid type").into()); - } - } - - let visible = visible_fn(&object_args.visible); - let expanded = if object_args.concretes.is_empty() { - quote! { - impl #crate_name::InputType for #ident { - type RawValueType = Self; - - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - #gql_typename - } - - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - registry.create_input_type::<Self, _>(#crate_name::registry::MetaTypeId::InputObject, |registry| #crate_name::registry::MetaType::InputObject { - name: ::std::borrow::Cow::into_owned(#gql_typename), - description: #desc, - input_fields: { - let mut fields = #crate_name::indexmap::IndexMap::new(); - #(#schema_fields)* - fields - }, - visible: #visible, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - rust_typename: ::std::option::Option::Some(::std::any::type_name::<Self>()), - oneof: true, - directive_invocations: ::std::vec![ #(#directives),* ], - }) - } - - fn parse(value: ::std::option::Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> { - if let ::std::option::Option::Some(#crate_name::Value::Object(mut obj)) = value { - #(#parse_item)* - ::std::result::Result::Err(#crate_name::InputValueError::expected_type(#crate_name::Value::Object(obj))) - } else { - ::std::result::Result::Err(#crate_name::InputValueError::expected_type(value.unwrap_or_default())) - } - } - - fn to_value(&self) -> #crate_name::Value { - let mut map = #crate_name::indexmap::IndexMap::new(); - match self { - #(#put_fields)* - } - #crate_name::Value::Object(map) - } - - fn federation_fields() -> ::std::option::Option<::std::string::String> { - ::std::option::Option::None - } - - fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> { - ::std::option::Option::Some(self) - } - } - - impl #crate_name::InputObjectType for #ident {} - impl #crate_name::OneofObjectType for #ident {} - } - } else { - let mut code = Vec::new(); - - code.push(quote! { - #[allow(clippy::all, clippy::pedantic)] - impl #impl_generics #ident #ty_generics #where_clause { - fn __internal_create_type_info(registry: &mut #crate_name::registry::Registry, name: &str) -> ::std::string::String where Self: #crate_name::InputType { - registry.create_input_type::<Self, _>(#crate_name::registry::MetaTypeId::InputObject, |registry| #crate_name::registry::MetaType::InputObject { - name: ::std::borrow::ToOwned::to_owned(name), - description: #desc, - input_fields: { - let mut fields = #crate_name::indexmap::IndexMap::new(); - #(#schema_fields)* - fields - }, - visible: #visible, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - rust_typename: ::std::option::Option::Some(::std::any::type_name::<Self>()), - oneof: true, - directive_invocations: ::std::vec![ #(#directives),* ], - }) - } - - fn __internal_parse(value: ::std::option::Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> where Self: #crate_name::InputType { - if let ::std::option::Option::Some(#crate_name::Value::Object(mut obj)) = value { - #(#parse_item)* - ::std::result::Result::Err(#crate_name::InputValueError::expected_type(#crate_name::Value::Object(obj))) - } else { - ::std::result::Result::Err(#crate_name::InputValueError::expected_type(value.unwrap_or_default())) - } - } - - fn __internal_to_value(&self) -> #crate_name::Value where Self: #crate_name::InputType { - let mut map = #crate_name::indexmap::IndexMap::new(); - match self { - #(#put_fields)* - } - #crate_name::Value::Object(map) - } - } - }); - - for concrete in &object_args.concretes { - let gql_typename = &concrete.name; - let params = &concrete.params.0; - let concrete_type = quote! { #ident<#(#params),*> }; - - let def_bounds = if !concrete.bounds.0.is_empty() { - let bounds = concrete.bounds.0.iter().map(|b| quote!(#b)); - Some(quote!(<#(#bounds),*>)) - } else { - None - }; - - let expanded = quote! { - #[allow(clippy::all, clippy::pedantic)] - impl #def_bounds #crate_name::InputType for #concrete_type { - type RawValueType = Self; - - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - ::std::borrow::Cow::Borrowed(#gql_typename) - } - - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - Self::__internal_create_type_info(registry, #gql_typename) - } - - fn parse(value: ::std::option::Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> { - Self::__internal_parse(value) - } - - fn to_value(&self) -> #crate_name::Value { - self.__internal_to_value() - } - - fn federation_fields() -> ::std::option::Option<::std::string::String> { - ::std::option::Option::None - } - - fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> { - ::std::option::Option::Some(self) - } - } - - impl #def_bounds #crate_name::InputObjectType for #concrete_type {} - impl #def_bounds #crate_name::OneofObjectType for #concrete_type {} - }; - code.push(expanded); - } - quote!(#(#code)*) - }; - - Ok(expanded.into()) -} diff --git a/derive/src/output_type.rs b/derive/src/output_type.rs deleted file mode 100644 index fcff3f650..000000000 --- a/derive/src/output_type.rs +++ /dev/null @@ -1,81 +0,0 @@ -use proc_macro2::{Ident, Span}; -use quote::quote; -use syn::{Error, GenericArgument, PathArguments, Result, Type}; - -pub enum OutputType<'a> { - Value(&'a Type), - Result(&'a Type), -} - -impl<'a> OutputType<'a> { - pub fn parse(input: &'a Type) -> Result<Self> { - let ty = if let Type::Path(p) = input { - if p.path.segments.last().unwrap().ident == "Result" - || p.path.segments.last().unwrap().ident == "FieldResult" - { - if let PathArguments::AngleBracketed(args) = - &p.path.segments.last().unwrap().arguments - { - if args.args.is_empty() { - return Err(Error::new_spanned(input, "Invalid type")); - } - let mut res = None; - for arg in &args.args { - if let GenericArgument::Type(value_ty) = arg { - res = Some(OutputType::Result(value_ty)); - break; - } - } - if res.is_none() { - return Err(Error::new_spanned(input, "Invalid type")); - } - res.unwrap() - } else { - return Err(Error::new_spanned(input, "Invalid type")); - } - } else { - OutputType::Value(input) - } - } else { - OutputType::Value(input) - }; - Ok(ty) - } - - pub fn value_type(&self) -> Type { - let tokens = match self { - OutputType::Value(ty) => quote! {#ty}, - OutputType::Result(ty) => quote! {#ty}, - }; - let mut ty = syn::parse2::<syn::Type>(tokens).unwrap(); - Self::remove_lifecycle(&mut ty); - ty - } - - fn remove_lifecycle(ty: &mut Type) { - match ty { - Type::Reference(r) => { - r.lifetime = None; - Self::remove_lifecycle(&mut r.elem); - } - Type::Path(r) => { - for s in &mut r.path.segments { - if let PathArguments::AngleBracketed(args) = &mut s.arguments { - for arg in &mut args.args { - match arg { - GenericArgument::Lifetime(lt) => { - lt.ident = Ident::new("_", Span::call_site()); - } - GenericArgument::Type(ty) => { - Self::remove_lifecycle(ty); - } - _ => {} - } - } - } - } - } - _ => {} - } - } -} diff --git a/derive/src/scalar.rs b/derive/src/scalar.rs deleted file mode 100644 index 7ce874d2d..000000000 --- a/derive/src/scalar.rs +++ /dev/null @@ -1,129 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; -use syn::ItemImpl; - -use crate::{ - args::{self, RenameTarget}, - utils::{ - GeneratorResult, gen_boxed_trait, get_crate_name, get_rustdoc, get_type_path_and_name, - visible_fn, - }, -}; - -pub fn generate( - scalar_args: &args::Scalar, - item_impl: &mut ItemImpl, -) -> GeneratorResult<TokenStream> { - let crate_name = get_crate_name(scalar_args.internal); - let boxed_trait = gen_boxed_trait(&crate_name); - let self_name = get_type_path_and_name(item_impl.self_ty.as_ref())?.1; - let gql_typename = if !scalar_args.name_type { - let name = scalar_args - .name - .clone() - .unwrap_or_else(|| RenameTarget::Type.rename(self_name.clone())); - quote!(::std::borrow::Cow::Borrowed(#name)) - } else { - quote!(<Self as #crate_name::TypeName>::type_name()) - }; - - let desc = if scalar_args.use_type_description { - quote! { ::std::option::Option::Some(::std::string::ToString::to_string(<Self as #crate_name::Description>::description())) } - } else { - get_rustdoc(&item_impl.attrs)? - .map(|s| quote!(::std::option::Option::Some(::std::string::ToString::to_string(#s)))) - .unwrap_or_else(|| quote!(::std::option::Option::None)) - }; - - let self_ty = &item_impl.self_ty; - let generic = &item_impl.generics; - let where_clause = &item_impl.generics.where_clause; - let visible = visible_fn(&scalar_args.visible); - let inaccessible = scalar_args.inaccessible; - let tags = scalar_args - .tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - let requires_scopes = scalar_args - .requires_scopes - .iter() - .map(|scopes| quote!(::std::string::ToString::to_string(#scopes))) - .collect::<Vec<_>>(); - let specified_by_url = match &scalar_args.specified_by_url { - Some(specified_by_url) => { - quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#specified_by_url)) } - } - None => quote! { ::std::option::Option::None }, - }; - - let expanded = quote! { - #item_impl - - #[allow(clippy::all, clippy::pedantic)] - impl #generic #crate_name::InputType for #self_ty #where_clause { - type RawValueType = Self; - - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - #gql_typename - } - - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - registry.create_input_type::<#self_ty, _>(#crate_name::registry::MetaTypeId::Scalar, |_| #crate_name::registry::MetaType::Scalar { - name: ::std::borrow::Cow::into_owned(#gql_typename), - description: #desc, - is_valid: ::std::option::Option::Some(::std::sync::Arc::new(|value| <#self_ty as #crate_name::ScalarType>::is_valid(value))), - visible: #visible, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - specified_by_url: #specified_by_url, - directive_invocations: ::std::vec::Vec::new(), - requires_scopes: ::std::vec![ #(#requires_scopes),* ], - }) - } - - fn parse(value: ::std::option::Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> { - <#self_ty as #crate_name::ScalarType>::parse(value.unwrap_or_default()) - } - - fn to_value(&self) -> #crate_name::Value { - <#self_ty as #crate_name::ScalarType>::to_value(self) - } - - fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> { - ::std::option::Option::Some(self) - } - } - - #[allow(clippy::all, clippy::pedantic)] - #boxed_trait - impl #generic #crate_name::OutputType for #self_ty #where_clause { - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - #gql_typename - } - - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - registry.create_output_type::<#self_ty, _>(#crate_name::registry::MetaTypeId::Scalar, |_| #crate_name::registry::MetaType::Scalar { - name: ::std::borrow::Cow::into_owned(#gql_typename), - description: #desc, - is_valid: ::std::option::Option::Some(::std::sync::Arc::new(|value| <#self_ty as #crate_name::ScalarType>::is_valid(value))), - visible: #visible, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - specified_by_url: #specified_by_url, - directive_invocations: ::std::vec::Vec::new(), - requires_scopes: ::std::vec![ #(#requires_scopes),* ], - }) - } - - async fn resolve( - &self, - _: &#crate_name::ContextSelectionSet<'_>, - _field: &#crate_name::Positioned<#crate_name::parser::types::Field> - ) -> #crate_name::ServerResult<#crate_name::Value> { - ::std::result::Result::Ok(#crate_name::ScalarType::to_value(self)) - } - } - }; - Ok(expanded.into()) -} diff --git a/derive/src/simple_object.rs b/derive/src/simple_object.rs deleted file mode 100644 index f778aeb55..000000000 --- a/derive/src/simple_object.rs +++ /dev/null @@ -1,572 +0,0 @@ -use std::str::FromStr; - -use darling::ast::Data; -use proc_macro::TokenStream; -use quote::quote; -use syn::{Error, Ident, LifetimeParam, Path, Type, ext::IdentExt, visit::Visit}; - -use crate::{ - args::{ - self, RenameRuleExt, RenameTarget, Resolvability, SimpleObjectField, TypeDirectiveLocation, - }, - utils::{ - GeneratorResult, gen_boxed_trait, gen_deprecation, gen_directive_calls, generate_guards, - get_crate_name, get_rustdoc, parse_complexity_expr, visible_fn, - }, -}; - -#[derive(Debug)] -struct DerivedFieldMetadata { - ident: Ident, - into: Type, - owned: Option<bool>, - with: Option<Path>, -} - -struct SimpleObjectFieldGenerator<'a> { - field: &'a SimpleObjectField, - derived: Option<DerivedFieldMetadata>, -} - -pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream> { - let crate_name = get_crate_name(object_args.internal); - let boxed_trait = gen_boxed_trait(&crate_name); - let ident = &object_args.ident; - let (impl_generics, ty_generics, where_clause) = object_args.generics.split_for_impl(); - let extends = object_args.extends; - let shareable = object_args.shareable; - let inaccessible = object_args.inaccessible; - let interface_object = object_args.interface_object; - let resolvable = matches!(object_args.resolvability, Resolvability::Resolvable); - let tags = object_args - .tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - let requires_scopes = object_args - .requires_scopes - .iter() - .map(|scopes| quote!(::std::string::ToString::to_string(#scopes))) - .collect::<Vec<_>>(); - let object_directives = - gen_directive_calls(&object_args.directives, TypeDirectiveLocation::Object); - let gql_typename = if !object_args.name_type { - object_args - .name - .as_ref() - .map(|name| quote!(::std::borrow::Cow::Borrowed(#name))) - .unwrap_or_else(|| { - let name = RenameTarget::Type.rename(ident.to_string()); - quote!(::std::borrow::Cow::Borrowed(#name)) - }) - } else { - quote!(<Self as #crate_name::TypeName>::type_name()) - }; - - let desc = get_rustdoc(&object_args.attrs)? - .map(|s| quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#s)) }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - - let s = match &object_args.data { - Data::Struct(e) => e, - _ => { - return Err(Error::new_spanned( - ident, - "SimpleObject can only be applied to an struct.", - ) - .into()); - } - }; - let mut getters = Vec::new(); - let mut resolvers = Vec::new(); - let mut schema_fields = Vec::new(); - - let mut processed_fields: Vec<SimpleObjectFieldGenerator> = vec![]; - - // Before processing the fields, we generate the derived fields - for field in &s.fields { - processed_fields.push(SimpleObjectFieldGenerator { - field, - derived: None, - }); - - for derived in &field.derived { - if derived.name.is_some() && derived.into.is_some() { - let name = derived.name.clone().unwrap(); - let into = match syn::parse2::<Type>( - proc_macro2::TokenStream::from_str(&derived.into.clone().unwrap()).unwrap(), - ) { - Ok(e) => e, - _ => { - return Err(Error::new_spanned( - &name, - "derived into must be a valid type.", - ) - .into()); - } - }; - - let derived = DerivedFieldMetadata { - ident: name, - into, - owned: derived.owned, - with: derived.with.clone(), - }; - - processed_fields.push(SimpleObjectFieldGenerator { - field, - derived: Some(derived), - }) - } - } - } - - for SimpleObjectFieldGenerator { field, derived } in &processed_fields { - if (field.skip || field.skip_output) && derived.is_none() { - continue; - } - - let base_ident = match &field.ident { - Some(ident) => ident, - None => return Err(Error::new_spanned(ident, "All fields must be named.").into()), - }; - - let ident = if let Some(derived) = derived { - &derived.ident - } else { - base_ident - }; - - let field_name = field.name.clone().unwrap_or_else(|| { - object_args - .rename_fields - .rename(ident.unraw().to_string(), RenameTarget::Field) - }); - let field_desc = get_rustdoc(&field.attrs)? - .map(|s| quote! {::std::option::Option::Some(::std::string::ToString::to_string(#s))}) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - let field_deprecation = gen_deprecation(&field.deprecation, &crate_name); - let external = field.external; - let shareable = field.shareable; - let inaccessible = field.inaccessible; - let tags = field - .tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - let requires_scopes = field - .requires_scopes - .iter() - .map(|scopes| quote!(::std::string::ToString::to_string(#scopes))) - .collect::<Vec<_>>(); - let override_from = match &field.override_from { - Some(from) => { - quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#from)) } - } - None => quote! { ::std::option::Option::None }, - }; - let requires = match &field.requires { - Some(requires) => { - quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#requires)) } - } - None => quote! { ::std::option::Option::None }, - }; - let provides = match &field.provides { - Some(provides) => { - quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#provides)) } - } - None => quote! { ::std::option::Option::None }, - }; - let vis = &field.vis; - - let ty = if let Some(derived) = derived { - &derived.into - } else { - &field.ty - }; - - let owned = if let Some(derived) = derived { - derived.owned.unwrap_or(field.owned) - } else { - field.owned - }; - - let cache_control = { - let public = field.cache_control.is_public(); - let max_age = if field.cache_control.no_cache { - -1 - } else { - field.cache_control.max_age as i32 - }; - quote! { - #crate_name::CacheControl { - public: #public, - max_age: #max_age, - } - } - }; - - let visible = visible_fn(&field.visible); - let directives = - gen_directive_calls(&field.directives, TypeDirectiveLocation::FieldDefinition); - - let complexity = if let Some(complexity) = &field.complexity { - let (_, expr) = parse_complexity_expr(complexity.clone())?; - quote! { - ::std::option::Option::Some(|__ctx, __variables_definition, __field, child_complexity| { - ::std::result::Result::Ok(#expr) - }) - } - } else { - quote! { ::std::option::Option::None } - }; - - if !field.flatten { - schema_fields.push(quote! { - fields.insert(::std::borrow::ToOwned::to_owned(#field_name), #crate_name::registry::MetaField { - name: ::std::borrow::ToOwned::to_owned(#field_name), - description: #field_desc, - args: ::std::default::Default::default(), - ty: <#ty as #crate_name::OutputType>::create_type_info(registry), - deprecation: #field_deprecation, - cache_control: #cache_control, - external: #external, - provides: #provides, - requires: #requires, - shareable: #shareable, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - override_from: #override_from, - visible: #visible, - compute_complexity: #complexity, - directive_invocations: ::std::vec![ #(#directives),* ], - requires_scopes: ::std::vec![ #(#requires_scopes),* ], - }); - }); - } else { - schema_fields.push(quote! { - <#ty as #crate_name::OutputType>::create_type_info(registry); - if let #crate_name::registry::MetaType::Object { fields: obj_fields, .. } = - registry.create_fake_output_type::<#ty>() { - fields.extend(obj_fields); - } - }); - } - - let guard_map_err = quote! { - .map_err(|err| err.into_server_error(ctx.item.pos)) - }; - let guard = match field.guard.as_ref().or(object_args.guard.as_ref()) { - Some(code) => Some(generate_guards(&crate_name, code, guard_map_err)?), - None => None, - }; - - let with_function = derived.as_ref().and_then(|x| x.with.as_ref()); - - let mut block = match !owned { - true => quote! { - &self.#base_ident - }, - false => quote! { - ::std::clone::Clone::clone(&self.#base_ident) - }, - }; - - block = match (derived, with_function) { - (Some(_), Some(with)) => quote! { - #with(#block) - }, - (Some(_), None) => quote! { - ::std::convert::Into::into(#block) - }, - (_, _) => block, - }; - - let ty = match !owned { - true => quote! { &#ty }, - false => quote! { #ty }, - }; - - if !field.flatten { - getters.push(quote! { - #[inline] - #[allow(missing_docs)] - #vis async fn #ident(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::Result<#ty> { - ::std::result::Result::Ok(#block) - } - }); - - resolvers.push(quote! { - if ctx.item.node.name.node == #field_name { - let f = async move { - #guard - self.#ident(ctx).await.map_err(|err| err.into_server_error(ctx.item.pos)) - }; - let obj = f.await.map_err(|err| ctx.set_error_path(err))?; - let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set); - return #crate_name::OutputType::resolve(&obj, &ctx_obj, ctx.item).await.map(::std::option::Option::Some); - } - }); - } else { - resolvers.push(quote! { - if let ::std::option::Option::Some(value) = #crate_name::ContainerType::resolve_field(&self.#ident, ctx).await? { - return ::std::result::Result::Ok(std::option::Option::Some(value)); - } - }); - } - } - - if !object_args.fake && resolvers.is_empty() { - return Err(Error::new_spanned( - ident, - "A GraphQL Object type must define one or more fields.", - ) - .into()); - } - - let cache_control = { - let public = object_args.cache_control.is_public(); - let max_age = if object_args.cache_control.no_cache { - -1 - } else { - object_args.cache_control.max_age as i32 - }; - quote! { - #crate_name::CacheControl { - public: #public, - max_age: #max_age, - } - } - }; - - let keys = match &object_args.resolvability { - Resolvability::Resolvable => quote!(::std::option::Option::None), - Resolvability::Unresolvable { key: Some(key) } => quote!(::std::option::Option::Some( - ::std::vec![ ::std::string::ToString::to_string(#key)] - )), - Resolvability::Unresolvable { key: None } => { - let keys = processed_fields - .iter() - .filter(|g| !g.field.skip && !g.field.skip_output) - .map(|generator| { - let ident = if let Some(derived) = &generator.derived { - &derived.ident - } else { - generator.field.ident.as_ref().unwrap() - }; - generator.field.name.clone().unwrap_or_else(|| { - object_args - .rename_fields - .rename(ident.unraw().to_string(), RenameTarget::Field) - }) - }) - .reduce(|mut keys, key| { - keys.push(' '); - keys.push_str(&key); - keys - }) - .unwrap(); - - quote!(::std::option::Option::Some( - ::std::vec![ ::std::string::ToString::to_string(#keys) ] - )) - } - }; - - let visible = visible_fn(&object_args.visible); - - let mut concat_complex_fields = quote!(); - let mut complex_resolver = quote!(); - - if object_args.complex { - concat_complex_fields = quote! { - fields.extend(<Self as #crate_name::ComplexObject>::fields(registry)); - }; - complex_resolver = quote! { - if let Some(value) = <Self as #crate_name::ComplexObject>::resolve_field(self, ctx).await? { - return Ok(Some(value)); - } - }; - } - - let resolve_container = if object_args.serial { - quote! { #crate_name::resolver_utils::resolve_container_serial(ctx, self).await } - } else { - quote! { #crate_name::resolver_utils::resolve_container(ctx, self).await } - }; - - let expanded = if object_args.concretes.is_empty() { - quote! { - #[allow(clippy::all, clippy::pedantic)] - impl #impl_generics #ident #ty_generics #where_clause { - #(#getters)* - } - - #[allow(clippy::all, clippy::pedantic)] - #boxed_trait - impl #impl_generics #crate_name::resolver_utils::ContainerType for #ident #ty_generics #where_clause { - async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> { - #(#resolvers)* - #complex_resolver - ::std::result::Result::Ok(::std::option::Option::None) - } - } - - #[allow(clippy::all, clippy::pedantic)] - #boxed_trait - impl #impl_generics #crate_name::OutputType for #ident #ty_generics #where_clause { - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - #gql_typename - } - - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - registry.create_output_type::<Self, _>(#crate_name::registry::MetaTypeId::Object, |registry| #crate_name::registry::MetaType::Object { - name: ::std::borrow::Cow::into_owned(#gql_typename), - description: #desc, - fields: { - let mut fields = #crate_name::indexmap::IndexMap::new(); - #(#schema_fields)* - #concat_complex_fields - fields - }, - cache_control: #cache_control, - extends: #extends, - shareable: #shareable, - resolvable: #resolvable, - inaccessible: #inaccessible, - interface_object: #interface_object, - tags: ::std::vec![ #(#tags),* ], - keys: #keys, - visible: #visible, - is_subscription: false, - rust_typename: ::std::option::Option::Some(::std::any::type_name::<Self>()), - directive_invocations: ::std::vec![ #(#object_directives),* ], - requires_scopes: ::std::vec![ #(#requires_scopes),* ], - }) - } - - async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> { - #resolve_container - } - } - - impl #impl_generics #crate_name::ObjectType for #ident #ty_generics #where_clause {} - } - } else { - let mut code = Vec::new(); - - #[derive(Default)] - struct GetLifetimes<'a> { - lifetimes: Vec<&'a LifetimeParam>, - } - - impl<'a> Visit<'a> for GetLifetimes<'a> { - fn visit_lifetime_param(&mut self, i: &'a LifetimeParam) { - self.lifetimes.push(i); - } - } - - let mut visitor = GetLifetimes::default(); - visitor.visit_generics(&object_args.generics); - let lifetimes = visitor.lifetimes; - - let type_lifetimes = if !lifetimes.is_empty() { - Some(quote!(#(#lifetimes,)*)) - } else { - None - }; - - code.push(quote! { - impl #impl_generics #ident #ty_generics #where_clause { - #(#getters)* - - fn __internal_create_type_info_simple_object( - registry: &mut #crate_name::registry::Registry, - name: &str, - complex_fields: #crate_name::indexmap::IndexMap<::std::string::String, #crate_name::registry::MetaField>, - ) -> ::std::string::String where Self: #crate_name::OutputType { - registry.create_output_type::<Self, _>(#crate_name::registry::MetaTypeId::Object, |registry| #crate_name::registry::MetaType::Object { - name: ::std::borrow::ToOwned::to_owned(name), - description: #desc, - fields: { - let mut fields = #crate_name::indexmap::IndexMap::new(); - #(#schema_fields)* - ::std::iter::Extend::extend(&mut fields, complex_fields.clone()); - fields - }, - cache_control: #cache_control, - extends: #extends, - shareable: #shareable, - resolvable: #resolvable, - inaccessible: #inaccessible, - interface_object: #interface_object, - tags: ::std::vec![ #(#tags),* ], - keys: ::std::option::Option::None, - visible: #visible, - is_subscription: false, - rust_typename: ::std::option::Option::Some(::std::any::type_name::<Self>()), - directive_invocations: ::std::vec![ #(#object_directives),* ], - requires_scopes: ::std::vec![ #(#requires_scopes),* ], - }) - } - - async fn __internal_resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> where Self: #crate_name::ContainerType { - #(#resolvers)* - ::std::result::Result::Ok(::std::option::Option::None) - } - } - }); - - for concrete in &object_args.concretes { - let gql_typename = &concrete.name; - let params = &concrete.params.0; - let concrete_type = quote! { #ident<#type_lifetimes #(#params),*> }; - - let def_bounds = if !lifetimes.is_empty() || !concrete.bounds.0.is_empty() { - let bounds = lifetimes - .iter() - .map(|l| quote!(#l)) - .chain(concrete.bounds.0.iter().map(|b| quote!(#b))); - Some(quote!(<#(#bounds),*>)) - } else { - None - }; - - let expanded = quote! { - #[allow(clippy::all, clippy::pedantic)] - #boxed_trait - impl #def_bounds #crate_name::resolver_utils::ContainerType for #concrete_type { - async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> { - #complex_resolver - self.__internal_resolve_field(ctx).await - } - } - - #[allow(clippy::all, clippy::pedantic)] - #boxed_trait - impl #def_bounds #crate_name::OutputType for #concrete_type { - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - ::std::borrow::Cow::Borrowed(#gql_typename) - } - - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - let mut fields = #crate_name::indexmap::IndexMap::new(); - #concat_complex_fields - Self::__internal_create_type_info_simple_object(registry, #gql_typename, fields) - } - - async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> { - #resolve_container - } - } - - impl #def_bounds #crate_name::ObjectType for #concrete_type {} - }; - code.push(expanded); - } - - quote!(#(#code)*) - }; - - Ok(expanded.into()) -} diff --git a/derive/src/subscription.rs b/derive/src/subscription.rs deleted file mode 100644 index a3f34ba62..000000000 --- a/derive/src/subscription.rs +++ /dev/null @@ -1,451 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; -use syn::{ - Block, Error, ImplItem, ItemImpl, ReturnType, Type, TypeImplTrait, TypeParamBound, - ext::IdentExt, -}; - -use crate::{ - args::{self, RenameRuleExt, RenameTarget, SubscriptionField, TypeDirectiveLocation}, - output_type::OutputType, - utils::{ - GeneratorResult, extract_input_args, gen_deprecation, gen_directive_calls, - generate_default, generate_guards, get_cfg_attrs, get_crate_name, get_rustdoc, - get_type_path_and_name, parse_complexity_expr, parse_graphql_attrs, remove_graphql_attrs, - visible_fn, - }, -}; - -pub fn generate( - subscription_args: &args::Subscription, - item_impl: &mut ItemImpl, -) -> GeneratorResult<TokenStream> { - let crate_name = get_crate_name(subscription_args.internal); - let (self_ty, self_name) = get_type_path_and_name(item_impl.self_ty.as_ref())?; - let generics = &item_impl.generics; - let where_clause = &item_impl.generics.where_clause; - let extends = subscription_args.extends; - let directives = - gen_directive_calls(&subscription_args.directives, TypeDirectiveLocation::Object); - - let gql_typename = if !subscription_args.name_type { - let name = subscription_args - .name - .clone() - .unwrap_or_else(|| RenameTarget::Type.rename(self_name.clone())); - quote!(::std::borrow::Cow::Borrowed(#name)) - } else { - quote!(<Self as #crate_name::TypeName>::type_name()) - }; - - let desc = if subscription_args.use_type_description { - quote! { ::std::option::Option::Some(::std::string::ToString::to_string(<Self as #crate_name::Description>::description())) } - } else { - get_rustdoc(&item_impl.attrs)? - .map(|s| quote!(::std::option::Option::Some(::std::string::ToString::to_string(#s)))) - .unwrap_or_else(|| quote!(::std::option::Option::None)) - }; - - let mut create_stream = Vec::new(); - let mut schema_fields = Vec::new(); - - for item in &mut item_impl.items { - if let ImplItem::Fn(method) = item { - let field: SubscriptionField = parse_graphql_attrs(&method.attrs)?.unwrap_or_default(); - if field.skip { - remove_graphql_attrs(&mut method.attrs); - continue; - } - - let ident = method.sig.ident.clone(); - let field_name = field.name.clone().unwrap_or_else(|| { - subscription_args - .rename_fields - .rename(method.sig.ident.unraw().to_string(), RenameTarget::Field) - }); - let field_desc = get_rustdoc(&method.attrs)? - .map(|s| quote! {::std::option::Option::Some(::std::string::ToString::to_string(#s))}) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - let field_deprecation = gen_deprecation(&field.deprecation, &crate_name); - let cfg_attrs = get_cfg_attrs(&method.attrs); - - if method.sig.asyncness.is_none() { - return Err(Error::new_spanned( - method, - "The subscription stream function must be asynchronous", - ) - .into()); - } - - let mut schema_args = Vec::new(); - let mut use_params = Vec::new(); - let mut get_params = Vec::new(); - let args = extract_input_args::<args::SubscriptionFieldArgument>(&crate_name, method)?; - - for ( - ident, - ty, - args::SubscriptionFieldArgument { - name, - desc, - default, - default_with, - validator, - process_with, - visible: arg_visible, - secret, - deprecation, - }, - ) in &args - { - let name = name.clone().unwrap_or_else(|| { - subscription_args - .rename_args - .rename(ident.ident.unraw().to_string(), RenameTarget::Argument) - }); - let desc = desc - .as_ref() - .map(|s| quote! {::std::option::Option::Some(::std::string::ToString::to_string(#s))}) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - let default = generate_default(default, default_with)?; - - let schema_default = default - .as_ref() - .map(|value| { - quote! { - ::std::option::Option::Some(::std::string::ToString::to_string( - &<#ty as #crate_name::InputType>::to_value(&#value) - )) - } - }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - - let visible = visible_fn(arg_visible); - let deprecation = gen_deprecation(deprecation, &crate_name); - - schema_args.push(quote! { - args.insert(::std::borrow::ToOwned::to_owned(#name), #crate_name::registry::MetaInputValue { - name: ::std::string::ToString::to_string(#name), - description: #desc, - ty: <#ty as #crate_name::InputType>::create_type_info(registry), - deprecation: #deprecation, - default_value: #schema_default, - visible: #visible, - inaccessible: false, - tags: ::std::default::Default::default(), - is_secret: #secret, - directive_invocations: ::std::vec![], - }); - }); - - use_params.push(quote! { #ident }); - - let default = match default { - Some(default) => { - quote! { ::std::option::Option::Some(|| -> #ty { #default }) } - } - None => quote! { ::std::option::Option::None }, - }; - - let param_ident = &ident.ident; - let process_with = match process_with.as_ref() { - Some(fn_path) => quote! { #fn_path(&mut #param_ident); }, - None => Default::default(), - }; - - let validators = validator.clone().unwrap_or_default().create_validators( - &crate_name, - quote!(&#ident), - Some(quote!(.map_err(|err| err.into_server_error(__pos)))), - )?; - - let mut non_mut_ident = ident.clone(); - non_mut_ident.mutability = None; - get_params.push(quote! { - #[allow(non_snake_case, unused_mut)] - let (__pos, mut #non_mut_ident) = ctx.param_value::<#ty>(#name, #default)?; - #process_with - #validators - #[allow(non_snake_case)] - let #ident = #non_mut_ident; - }); - } - - let ty = match &method.sig.output { - ReturnType::Type(_, ty) => OutputType::parse(ty)?, - ReturnType::Default => { - return Err(Error::new_spanned( - &method.sig.output, - "Resolver must have a return type", - ) - .into()); - } - }; - let res_ty = ty.value_type(); - let stream_ty = if let Type::ImplTrait(TypeImplTrait { bounds, .. }) = &res_ty { - let mut r = None; - for b in bounds { - if let TypeParamBound::Trait(b) = b { - r = Some(quote! { dyn #b }); - } - } - quote! { #r } - } else { - quote! { #res_ty } - }; - - if let OutputType::Value(inner_ty) = &ty { - let block = &method.block; - let new_block = quote!({ - { - let value = (move || { async move #block })().await; - ::std::result::Result::Ok(value) - } - }); - method.block = syn::parse2::<Block>(new_block).expect("invalid block"); - method.sig.output = - syn::parse2::<ReturnType>(quote! { -> #crate_name::Result<#inner_ty> }) - .expect("invalid result type"); - } - - let visible = visible_fn(&field.visible); - let complexity = if let Some(complexity) = &field.complexity { - let (variables, expr) = parse_complexity_expr(complexity.clone())?; - let mut parse_args = Vec::new(); - for variable in variables { - if let Some(( - ident, - ty, - args::SubscriptionFieldArgument { - name, - default, - default_with, - .. - }, - )) = args - .iter() - .find(|(pat_ident, _, _)| pat_ident.ident == variable) - { - let default = match generate_default(default, default_with)? { - Some(default) => { - quote! { ::std::option::Option::Some(|| -> #ty { #default }) } - } - None => quote! { ::std::option::Option::None }, - }; - let name = name.clone().unwrap_or_else(|| { - subscription_args - .rename_args - .rename(ident.ident.unraw().to_string(), RenameTarget::Argument) - }); - parse_args.push(quote! { - let #ident: #ty = __ctx.param_value(__variables_definition, __field, #name, #default)?; - }); - } - } - quote! { - Some(|__ctx, __variables_definition, __field, child_complexity| { - #(#parse_args)* - ::std::result::Result::Ok(#expr) - }) - } - } else { - quote! { ::std::option::Option::None } - }; - - let directives = - gen_directive_calls(&field.directives, TypeDirectiveLocation::FieldDefinition); - - schema_fields.push(quote! { - #(#cfg_attrs)* - fields.insert(::std::borrow::ToOwned::to_owned(#field_name), #crate_name::registry::MetaField { - name: ::std::borrow::ToOwned::to_owned(#field_name), - description: #field_desc, - args: { - let mut args = #crate_name::indexmap::IndexMap::new(); - #(#schema_args)* - args - }, - ty: <<#stream_ty as #crate_name::futures_util::stream::Stream>::Item as #crate_name::OutputType>::create_type_info(registry), - deprecation: #field_deprecation, - cache_control: ::std::default::Default::default(), - external: false, - requires: ::std::option::Option::None, - provides: ::std::option::Option::None, - shareable: false, - override_from: ::std::option::Option::None, - visible: #visible, - inaccessible: false, - tags: ::std::default::Default::default(), - compute_complexity: #complexity, - directive_invocations: ::std::vec![ #(#directives),* ], - requires_scopes: ::std::vec![], - }); - }); - - let create_field_stream = quote! { - self.#ident(ctx, #(#use_params),*) - .await - .map_err(|err| { - ::std::convert::Into::<#crate_name::Error>::into(err).into_server_error(ctx.item.pos) - .with_path(::std::vec![#crate_name::PathSegment::Field(::std::borrow::ToOwned::to_owned(&*field_name))]) - }) - }; - - let guard_map_err = quote! { - .map_err(|err| { - err.into_server_error(ctx.item.pos) - .with_path(::std::vec![#crate_name::PathSegment::Field(::std::borrow::ToOwned::to_owned(&*field_name))]) - }) - }; - let guard = match field.guard.as_ref().or(subscription_args.guard.as_ref()) { - Some(code) => Some(generate_guards(&crate_name, code, guard_map_err)?), - None => None, - }; - let stream_fn = quote! { - let field_name = ::std::clone::Clone::clone(&ctx.item.node.response_key().node); - let field = ::std::sync::Arc::new(::std::clone::Clone::clone(&ctx.item)); - - let f = async { - #(#get_params)* - #guard - #create_field_stream - }; - let stream = f.await.map_err(|err| ctx.set_error_path(err))?; - - let pos = ctx.item.pos; - let schema_env = ::std::clone::Clone::clone(&ctx.schema_env); - let query_env = ::std::clone::Clone::clone(&ctx.query_env); - let stream = #crate_name::futures_util::stream::StreamExt::then(stream, { - let field_name = ::std::clone::Clone::clone(&field_name); - move |msg| { - let schema_env = ::std::clone::Clone::clone(&schema_env); - let query_env = ::std::clone::Clone::clone(&query_env); - let field = ::std::clone::Clone::clone(&field); - let field_name = ::std::clone::Clone::clone(&field_name); - async move { - let f = |execute_data: ::std::option::Option<#crate_name::Data>| { - let schema_env = ::std::clone::Clone::clone(&schema_env); - let query_env = ::std::clone::Clone::clone(&query_env); - async move { - let ctx_selection_set = query_env.create_context( - &schema_env, - ::std::option::Option::Some(#crate_name::QueryPathNode { - parent: ::std::option::Option::None, - segment: #crate_name::QueryPathSegment::Name(&field_name), - }), - &field.node.selection_set, - execute_data.as_ref(), - ); - - let parent_type = #gql_typename; - #[allow(bare_trait_objects)] - let ri = #crate_name::extensions::ResolveInfo { - path_node: ctx_selection_set.path_node.as_ref().unwrap(), - parent_type: &parent_type, - return_type: &<<#stream_ty as #crate_name::futures_util::stream::Stream>::Item as #crate_name::OutputType>::qualified_type_name(), - name: field.node.name.node.as_str(), - alias: field.node.alias.as_ref().map(|alias| alias.node.as_str()), - is_for_introspection: false, - field: &field.node, - }; - let resolve_fut = async { - #crate_name::OutputType::resolve(&msg, &ctx_selection_set, &*field) - .await - .map(::std::option::Option::Some) - }; - #crate_name::futures_util::pin_mut!(resolve_fut); - let mut resp = query_env.extensions.resolve(ri, &mut resolve_fut).await.map(|value| { - let mut map = #crate_name::indexmap::IndexMap::new(); - map.insert(::std::clone::Clone::clone(&field_name), value.unwrap_or_default()); - #crate_name::Response::new(#crate_name::Value::Object(map)) - }) - .unwrap_or_else(|err| #crate_name::Response::from_errors(::std::vec![err])); - - use ::std::iter::Extend; - resp.errors.extend(::std::mem::take(&mut *query_env.errors.lock().unwrap())); - resp - } - }; - ::std::result::Result::Ok(query_env.extensions.execute(query_env.operation_name.as_deref(), f).await) - } - } - }); - #crate_name::ServerResult::Ok(stream) - }; - - create_stream.push(quote! { - #(#cfg_attrs)* - if ctx.item.node.name.node == #field_name { - let stream = #crate_name::futures_util::stream::TryStreamExt::try_flatten( - #crate_name::futures_util::stream::once((move || async move { #stream_fn })()) - ); - let stream = #crate_name::futures_util::StreamExt::map(stream, |res| match res { - ::std::result::Result::Ok(resp) => resp, - ::std::result::Result::Err(err) => #crate_name::Response::from_errors(::std::vec![err]), - }); - return ::std::option::Option::Some(::std::boxed::Box::pin(stream)); - } - }); - - remove_graphql_attrs(&mut method.attrs); - } - } - - if create_stream.is_empty() { - return Err(Error::new_spanned( - self_ty, - "A GraphQL Object type must define one or more fields.", - ) - .into()); - } - - let visible = visible_fn(&subscription_args.visible); - - let expanded = quote! { - #item_impl - - #[allow(clippy::all, clippy::pedantic)] - #[allow(unused_braces, unused_variables)] - impl #generics #crate_name::SubscriptionType for #self_ty #where_clause { - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - #gql_typename - } - - #[allow(bare_trait_objects)] - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - registry.create_subscription_type::<Self, _>(|registry| #crate_name::registry::MetaType::Object { - name: ::std::borrow::Cow::into_owned(#gql_typename), - description: #desc, - fields: { - let mut fields = #crate_name::indexmap::IndexMap::new(); - #(#schema_fields)* - fields - }, - cache_control: ::std::default::Default::default(), - extends: #extends, - keys: ::std::option::Option::None, - visible: #visible, - shareable: false, - resolvable: true, - inaccessible: false, - interface_object: false, - tags: ::std::default::Default::default(), - is_subscription: true, - rust_typename: ::std::option::Option::Some(::std::any::type_name::<Self>()), - directive_invocations: ::std::vec![ #(#directives),* ], - requires_scopes: ::std::vec![], - }) - } - - fn create_field_stream<'__life>( - &'__life self, - ctx: &'__life #crate_name::Context<'_>, - ) -> ::std::option::Option<::std::pin::Pin<::std::boxed::Box<dyn #crate_name::futures_util::stream::Stream<Item = #crate_name::Response> + ::std::marker::Send + '__life>>> { - #(#create_stream)* - ::std::option::Option::None - } - } - }; - - Ok(expanded.into()) -} diff --git a/derive/src/type_directive.rs b/derive/src/type_directive.rs deleted file mode 100644 index c479c130e..000000000 --- a/derive/src/type_directive.rs +++ /dev/null @@ -1,188 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; -use syn::{FnArg, ItemFn, Pat, ext::IdentExt}; - -use crate::{ - args::{self, Argument, RenameRuleExt, RenameTarget}, - utils::{ - GeneratorResult, gen_deprecation, generate_default, get_crate_name, get_rustdoc, - parse_graphql_attrs, remove_graphql_attrs, visible_fn, - }, -}; - -pub fn generate( - directive_args: &args::TypeDirective, - item_fn: &mut ItemFn, -) -> GeneratorResult<TokenStream> { - let crate_name = get_crate_name(directive_args.internal); - let ident = &item_fn.sig.ident; - let vis = &item_fn.vis; - let directive_name = if !directive_args.name_type { - let name = directive_args - .name - .clone() - .unwrap_or_else(|| item_fn.sig.ident.to_string()); - quote!(::std::borrow::Cow::Borrowed(#name)) - } else { - quote!(<Self as #crate_name::TypeName>::type_name()) - }; - let desc = get_rustdoc(&item_fn.attrs)? - .map(|s| quote!(::std::option::Option::Some(::std::string::ToString::to_string(#s)))) - .unwrap_or_else(|| quote!(::std::option::Option::None)); - let visible = visible_fn(&directive_args.visible); - let repeatable = directive_args.repeatable; - - let composable = match directive_args.composable.as_ref() { - Some(url) => quote!(::std::option::Option::Some(::std::string::ToString::to_string(#url))), - None => quote!(::std::option::Option::None), - }; - - let mut schema_args = Vec::new(); - let mut input_args = Vec::new(); - let mut directive_input_args = Vec::new(); - - for arg in item_fn.sig.inputs.iter_mut() { - let mut arg_info = None; - - if let FnArg::Typed(pat) = arg { - if let Pat::Ident(ident) = &*pat.pat { - arg_info = Some((ident.clone(), pat.ty.clone(), pat.attrs.clone())); - remove_graphql_attrs(&mut pat.attrs); - } - } - - let (arg_ident, arg_ty, arg_attrs) = match arg_info { - Some(info) => info, - None => { - return Err(syn::Error::new_spanned(arg, "Invalid argument type.").into()); - } - }; - - let Argument { - name, - desc, - default, - default_with, - visible, - secret, - directives, - deprecation, - .. - } = parse_graphql_attrs::<args::Argument>(&arg_attrs)?.unwrap_or_default(); - - let name = name.clone().unwrap_or_else(|| { - directive_args - .rename_args - .rename(arg_ident.ident.unraw().to_string(), RenameTarget::Argument) - }); - let desc = desc - .as_ref() - .map(|s| quote! {::std::option::Option::Some(::std::string::ToString::to_string(#s))}) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - let default = generate_default(&default, &default_with)?; - let schema_default = default - .as_ref() - .map(|value| { - quote! { - ::std::option::Option::Some(::std::string::ToString::to_string( - &<#arg_ty as #crate_name::InputType>::to_value(&#value) - )) - } - }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - let visible = visible_fn(&visible); - let deprecation = gen_deprecation(&deprecation, &crate_name); - - schema_args.push(quote! { - args.insert(::std::borrow::ToOwned::to_owned(#name), #crate_name::registry::MetaInputValue { - name: ::std::string::ToString::to_string(#name), - description: #desc, - ty: <#arg_ty as #crate_name::InputType>::create_type_info(registry), - deprecation: #deprecation, - default_value: #schema_default, - visible: #visible, - inaccessible: false, - tags: ::std::default::Default::default(), - is_secret: #secret, - directive_invocations: ::std::vec![ #(#directives),* ], - }); - }); - - input_args.push(quote! { #arg }); - - directive_input_args.push(quote! { - if let Some(val) = #crate_name::InputType::as_raw_value(&#arg_ident) { - args.insert(::std::string::ToString::to_string(#name), #crate_name::InputType::to_value(val)); - }; - }); - } - - let locations = directive_args - .locations - .iter() - .map(|loc| { - let loc = quote::format_ident!("{}", loc.to_string()); - quote!(#crate_name::registry::__DirectiveLocation::#loc) - }) - .collect::<Vec<_>>(); - - if locations.is_empty() { - return Err(syn::Error::new( - ident.span(), - "At least one location is required for the directive.", - ) - .into()); - } - - let location_traits = directive_args - .locations - .iter() - .map(|loc| loc.location_trait_identifier()) - .collect::<Vec<_>>(); - - let expanded = quote! { - #[allow(non_camel_case_types)] - #vis struct #ident; - - impl #crate_name::TypeDirective for #ident { - fn name(&self) -> ::std::borrow::Cow<'static, ::std::primitive::str> { - #directive_name - } - - fn register(&self, registry: &mut #crate_name::registry::Registry) { - let meta = #crate_name::registry::MetaDirective { - name: ::std::borrow::Cow::into_owned(#directive_name), - description: #desc, - locations: vec![#(#locations),*], - args: { - #[allow(unused_mut)] - let mut args = #crate_name::indexmap::IndexMap::new(); - #(#schema_args)* - args - }, - is_repeatable: #repeatable, - visible: #visible, - composable: #composable, - }; - registry.add_directive(meta); - } - - } - - #(impl #crate_name::registry::location_traits::#location_traits for #ident {})* - - impl #ident { - pub fn apply(#(#input_args),*) -> #crate_name::registry::MetaDirectiveInvocation { - let directive = ::std::borrow::Cow::into_owned(#directive_name); - let mut args = #crate_name::indexmap::IndexMap::new(); - #(#directive_input_args)*; - #crate_name::registry::MetaDirectiveInvocation { - name: directive, - args, - } - } - } - }; - - Ok(expanded.into()) -} diff --git a/derive/src/union.rs b/derive/src/union.rs deleted file mode 100644 index 192367af5..000000000 --- a/derive/src/union.rs +++ /dev/null @@ -1,419 +0,0 @@ -use core::panic; -use std::collections::HashSet; - -use darling::ast::{Data, Style}; -use proc_macro::TokenStream; -use quote::quote; -use syn::{Error, LifetimeParam, Type, visit::Visit, visit_mut::VisitMut}; - -use crate::{ - args::{self, RenameTarget}, - utils::{ - GeneratorResult, RemoveLifetime, gen_boxed_trait, get_crate_name, get_rustdoc, visible_fn, - }, -}; - -pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> { - let crate_name = get_crate_name(union_args.internal); - let boxed_trait = gen_boxed_trait(&crate_name); - let ident = &union_args.ident; - let type_params = union_args.generics.type_params().collect::<Vec<_>>(); - let (impl_generics, ty_generics, where_clause) = union_args.generics.split_for_impl(); - let s = match &union_args.data { - Data::Enum(s) => s, - _ => return Err(Error::new_spanned(ident, "Union can only be applied to an enum.").into()), - }; - let mut enum_names = Vec::new(); - let mut enum_items = HashSet::new(); - let mut type_into_impls = Vec::new(); - let gql_typename = if !union_args.name_type { - let name = union_args - .name - .clone() - .unwrap_or_else(|| RenameTarget::Type.rename(ident.to_string())); - quote!(::std::borrow::Cow::Borrowed(#name)) - } else { - quote!(<Self as #crate_name::TypeName>::type_name()) - }; - - let inaccessible = union_args.inaccessible; - let tags = union_args - .tags - .iter() - .map(|tag| quote!(::std::string::ToString::to_string(#tag))) - .collect::<Vec<_>>(); - let desc = get_rustdoc(&union_args.attrs)? - .map(|s| quote! { ::std::option::Option::Some(::std::string::ToString::to_string(#s)) }) - .unwrap_or_else(|| quote! {::std::option::Option::None}); - - let mut lazy_types = Vec::new(); - - #[derive(Clone)] - struct LazyType { - ty: syn::Type, - enum_name: syn::Ident, - flatten: bool, - } - - let mut collect_all_fields = Vec::new(); - - for variant in s { - let enum_name = &variant.ident; - let ty = match variant.fields.style { - Style::Tuple if variant.fields.fields.len() == 1 => &variant.fields.fields[0], - Style::Tuple => { - return Err(Error::new_spanned( - enum_name, - "Only single value variants are supported", - ) - .into()); - } - Style::Unit => { - return Err( - Error::new_spanned(enum_name, "Empty variants are not supported").into(), - ); - } - Style::Struct => { - return Err(Error::new_spanned( - enum_name, - "Variants with named fields are not supported", - ) - .into()); - } - }; - - let mut ty = ty; - while let Type::Group(group) = ty { - ty = &*group.elem; - } - - if matches!(ty, Type::Path(_) | Type::Macro(_)) { - // This validates that the field type wasn't already used - if !enum_items.insert(ty) { - return Err( - Error::new_spanned(ty, "This type is already used in another variant").into(), - ); - } - - enum_names.push(enum_name); - - let mut assert_ty = ty.clone(); - RemoveLifetime.visit_type_mut(&mut assert_ty); - - if !variant.flatten { - type_into_impls.push(quote! { - #crate_name::static_assertions_next::assert_impl!(for(#(#type_params),*) #assert_ty: #crate_name::ObjectType); - - #[allow(clippy::all, clippy::pedantic)] - impl #impl_generics ::std::convert::From<#ty> for #ident #ty_generics #where_clause { - fn from(obj: #ty) -> Self { - #ident::#enum_name(obj) - } - } - }); - } else { - type_into_impls.push(quote! { - #crate_name::static_assertions_next::assert_impl!(for(#(#type_params),*) #assert_ty: #crate_name::UnionType); - - #[allow(clippy::all, clippy::pedantic)] - impl #impl_generics ::std::convert::From<#ty> for #ident #ty_generics #where_clause { - fn from(obj: #ty) -> Self { - #ident::#enum_name(obj) - } - } - }); - } - - lazy_types.push(LazyType { - ty: ty.clone(), - enum_name: enum_name.clone(), - flatten: variant.flatten, - }); - - collect_all_fields.push(quote! { - #ident::#enum_name(obj) => obj.collect_all_fields(ctx, fields) - }); - } else { - return Err(Error::new_spanned(ty, "Invalid type").into()); - } - } - - if lazy_types.is_empty() { - return Err(Error::new_spanned( - ident, - "A GraphQL Union type must include one or more unique member types.", - ) - .into()); - } - - let visible = visible_fn(&union_args.visible); - - let get_introspection_typename = |lazy_types: Vec<LazyType>| { - lazy_types.into_iter().map(|lazy| { - let ty = lazy.ty; - let enum_name = &lazy.enum_name; - if !lazy.flatten { - quote! { - #ident::#enum_name(obj) => <#ty as #crate_name::OutputType>::type_name() - } - } else { - quote! { - #ident::#enum_name(obj) => <#ty as #crate_name::OutputType>::introspection_type_name(obj) - } - } - }) - }; - - let registry_types = |lazy_types: Vec<LazyType>| { - lazy_types.into_iter().filter_map(|lazy| { - let ty = lazy.ty; - if !lazy.flatten { - Some(quote! { - <#ty as #crate_name::OutputType>::create_type_info(registry); - }) - } else { - None - } - }) - }; - - let possible_types = |lazy_types: Vec<LazyType>| { - lazy_types.into_iter().map(|lazy| { - let ty = lazy.ty; - if !lazy.flatten { - quote! { - possible_types.insert(<#ty as #crate_name::OutputType>::type_name().into_owned()); - } - } else { - quote! { - if let #crate_name::registry::MetaType::Union { possible_types: possible_types2, .. } = - registry.create_fake_output_type::<#ty>() { - possible_types.extend(possible_types2); - } - } - } - }) - }; - - let expanded = if union_args.concretes.is_empty() { - let get_introspection_typename = get_introspection_typename(lazy_types.clone()); - let registry_types = registry_types(lazy_types.clone()); - let possible_types = possible_types(lazy_types.clone()); - - quote! { - #(#type_into_impls)* - - #[allow(clippy::all, clippy::pedantic)] - #boxed_trait - impl #impl_generics #crate_name::resolver_utils::ContainerType for #ident #ty_generics #where_clause { - async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> { - ::std::result::Result::Ok(::std::option::Option::None) - } - - fn collect_all_fields<'__life>(&'__life self, ctx: &#crate_name::ContextSelectionSet<'__life>, fields: &mut #crate_name::resolver_utils::Fields<'__life>) -> #crate_name::ServerResult<()> { - match self { - #(#collect_all_fields),* - } - } - } - - #[allow(clippy::all, clippy::pedantic)] - #boxed_trait - impl #impl_generics #crate_name::OutputType for #ident #ty_generics #where_clause { - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - #gql_typename - } - - fn introspection_type_name(&self) -> ::std::borrow::Cow<'static, ::std::primitive::str> { - match self { - #(#get_introspection_typename),* - } - } - - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - registry.create_output_type::<Self, _>(#crate_name::registry::MetaTypeId::Union, |registry| { - #(#registry_types)* - - #crate_name::registry::MetaType::Union { - name: ::std::borrow::Cow::into_owned(#gql_typename), - description: #desc, - possible_types: { - let mut possible_types = #crate_name::indexmap::IndexSet::new(); - #(#possible_types)* - possible_types - }, - visible: #visible, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - rust_typename: ::std::option::Option::Some(::std::any::type_name::<Self>()), - directive_invocations: ::std::vec::Vec::new(), - } - }) - } - - async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> { - #crate_name::resolver_utils::resolve_container(ctx, self).await - } - } - - impl #impl_generics #crate_name::UnionType for #ident #ty_generics #where_clause {} - } - } else { - let mut code = Vec::new(); - - #[derive(Default)] - struct GetLifetimes<'a> { - lifetimes: Vec<&'a LifetimeParam>, - } - - impl<'a> Visit<'a> for GetLifetimes<'a> { - fn visit_lifetime_param(&mut self, i: &'a LifetimeParam) { - self.lifetimes.push(i); - } - } - - let mut visitor = GetLifetimes::default(); - visitor.visit_generics(&union_args.generics); - let lifetimes = visitor.lifetimes; - - let type_lifetimes = if !lifetimes.is_empty() { - Some(quote!(#(#lifetimes,)*)) - } else { - None - }; - - for concrete in &union_args.concretes { - let gql_typename = &concrete.name; - let params = &concrete.params.0; - let concrete_type = quote! { #ident<#type_lifetimes #(#params),*> }; - - let def_bounds = if !lifetimes.is_empty() || !concrete.bounds.0.is_empty() { - let bounds = lifetimes - .iter() - .map(|l| quote!(#l)) - .chain(concrete.bounds.0.iter().map(|b| quote!(#b))); - Some(quote!(<#(#bounds),*>)) - } else { - None - }; - - let lazy_types = lazy_types - .clone() - .into_iter() - .map(|mut l| { - match &mut l.ty { - syn::Type::Path(p) => { - let last_segment = p.path.segments.last_mut().unwrap(); - - match last_segment.arguments { - syn::PathArguments::None => { - if let Some(idx) = type_params - .iter() - .position(|p| p.ident == last_segment.ident) - { - let param = ¶ms[idx]; - l.ty = syn::parse2::<syn::Type>(quote!(#param)).unwrap(); - } - } - syn::PathArguments::AngleBracketed(ref mut inner) => { - for arg in &mut inner.args { - let ty = match arg { - syn::GenericArgument::Type(t) => t, - syn::GenericArgument::AssocType(a) => &mut a.ty, - _ => continue, - }; - - // Check if the type is a generic parameter which we should - // convert to a concrete type - if let syn::Type::Path(ty_path) = ty { - if let Some(idx) = type_params.iter().position(|p| { - p.ident == ty_path.path.segments[0].ident - }) { - let param = ¶ms[idx]; - *ty = syn::parse2::<syn::Type>(quote!(#param)) - .unwrap(); - } - } - } - } - _ => unreachable!(), - } - } - syn::Type::Macro(_) => { - panic!("Macro types with generics are not supported yet") - } - _ => unreachable!(), - }; - - l - }) - .collect::<Vec<_>>(); - - let get_introspection_typename = get_introspection_typename(lazy_types.clone()); - let registry_types = registry_types(lazy_types.clone()); - let possible_types = possible_types(lazy_types.clone()); - - let expanded = quote! { - #[allow(clippy::all, clippy::pedantic)] - #boxed_trait - impl #def_bounds #crate_name::resolver_utils::ContainerType for #concrete_type { - async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> { - ::std::result::Result::Ok(::std::option::Option::None) - } - - fn collect_all_fields<'__life>(&'__life self, ctx: &#crate_name::ContextSelectionSet<'__life>, fields: &mut #crate_name::resolver_utils::Fields<'__life>) -> #crate_name::ServerResult<()> { - match self { - #(#collect_all_fields),* - } - } - } - - #[allow(clippy::all, clippy::pedantic)] - #boxed_trait - impl #def_bounds #crate_name::OutputType for #concrete_type { - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - ::std::borrow::Cow::Borrowed(#gql_typename) - } - - fn introspection_type_name(&self) -> ::std::borrow::Cow<'static, ::std::primitive::str> { - match self { - #(#get_introspection_typename),* - } - } - - fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { - registry.create_output_type::<Self, _>(#crate_name::registry::MetaTypeId::Union, |registry| { - #(#registry_types)* - - #crate_name::registry::MetaType::Union { - name: ::std::borrow::ToOwned::to_owned(#gql_typename), - description: #desc, - possible_types: { - let mut possible_types = #crate_name::indexmap::IndexSet::new(); - #(#possible_types)* - possible_types - }, - visible: #visible, - inaccessible: #inaccessible, - tags: ::std::vec![ #(#tags),* ], - rust_typename: ::std::option::Option::Some(::std::any::type_name::<Self>()), - directive_invocations: ::std::vec::Vec::new() - } - }) - } - - async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> { - #crate_name::resolver_utils::resolve_container(ctx, self).await - } - } - - impl #def_bounds #crate_name::ObjectType for #concrete_type {} - }; - code.push(expanded); - } - - quote!(#(#code)*) - }; - - Ok(expanded.into()) -} diff --git a/derive/src/utils.rs b/derive/src/utils.rs deleted file mode 100644 index 01983997d..000000000 --- a/derive/src/utils.rs +++ /dev/null @@ -1,382 +0,0 @@ -use std::collections::HashSet; - -use darling::FromMeta; -use proc_macro_crate::{FoundCrate, crate_name}; -use proc_macro2::{Span, TokenStream, TokenTree}; -use quote::quote; -use syn::{ - Attribute, Error, Expr, ExprLit, ExprPath, FnArg, Ident, ImplItemFn, Lifetime, Lit, LitStr, - Meta, Pat, PatIdent, Type, TypeGroup, TypeParamBound, TypeReference, visit::Visit, visit_mut, - visit_mut::VisitMut, -}; -use thiserror::Error; - -use crate::args::{self, Deprecation, TypeDirectiveLocation, Visible}; - -#[derive(Error, Debug)] -pub enum GeneratorError { - #[error("{0}")] - Syn(#[from] syn::Error), - - #[error("{0}")] - Darling(#[from] darling::Error), -} - -impl GeneratorError { - pub fn write_errors(self) -> TokenStream { - match self { - GeneratorError::Syn(err) => err.to_compile_error(), - GeneratorError::Darling(err) => err.write_errors(), - } - } -} - -pub type GeneratorResult<T> = std::result::Result<T, GeneratorError>; - -pub fn get_crate_name(internal: bool) -> TokenStream { - if internal { - quote! { crate } - } else { - let name = match crate_name("async-graphql") { - Ok(FoundCrate::Name(name)) => name, - Ok(FoundCrate::Itself) | Err(_) => "async_graphql".to_string(), - }; - TokenTree::from(Ident::new(&name, Span::call_site())).into() - } -} - -pub fn generate_guards( - crate_name: &TokenStream, - expr: &Expr, - map_err: TokenStream, -) -> GeneratorResult<TokenStream> { - let code = quote! {{ - use #crate_name::GuardExt; - #expr - }}; - Ok(quote! { - #crate_name::Guard::check(&#code, &ctx).await #map_err ?; - }) -} - -pub fn get_rustdoc(attrs: &[Attribute]) -> GeneratorResult<Option<TokenStream>> { - let mut full_docs: Vec<TokenStream> = vec![]; - let mut combined_docs_literal = String::new(); - for attr in attrs { - if let Meta::NameValue(nv) = &attr.meta { - if nv.path.is_ident("doc") { - match &nv.value { - Expr::Lit(ExprLit { - lit: Lit::Str(doc), .. - }) => { - let doc = doc.value(); - let doc_str = doc.trim(); - combined_docs_literal += "\n"; - combined_docs_literal += doc_str; - } - Expr::Macro(include_macro) => { - if !combined_docs_literal.is_empty() { - combined_docs_literal += "\n"; - let lit = LitStr::new(&combined_docs_literal, Span::call_site()); - full_docs.push(quote!( #lit )); - combined_docs_literal.clear(); - } - full_docs.push(quote!( #include_macro )); - } - _ => (), - } - } - } - } - - if !combined_docs_literal.is_empty() { - let lit = LitStr::new(&combined_docs_literal, Span::call_site()); - full_docs.push(quote!( #lit )); - combined_docs_literal.clear(); - } - - Ok(if full_docs.is_empty() { - None - } else { - Some(quote!(::core::primitive::str::trim( - ::std::concat!( #( #full_docs ),* ) - ))) - }) -} - -fn generate_default_value(lit: &Lit) -> GeneratorResult<TokenStream> { - match lit { - Lit::Str(value) =>{ - let value = value.value(); - Ok(quote!({ ::std::borrow::ToOwned::to_owned(#value) })) - } - Lit::Int(value) => { - let value = value.base10_parse::<i32>()?; - Ok(quote!({ ::std::convert::TryInto::try_into(#value).unwrap_or_default() })) - } - Lit::Float(value) => { - let value = value.base10_parse::<f64>()?; - Ok(quote!({ ::std::convert::TryInto::try_into(#value).unwrap_or_default() })) - } - Lit::Bool(value) => { - let value = value.value; - Ok(quote!({ #value })) - } - _ => Err(Error::new_spanned( - lit, - "The default value type only be string, integer, float and boolean, other types should use default_with", - ).into()), - } -} - -fn generate_default_with(lit: &LitStr) -> GeneratorResult<TokenStream> { - let str = lit.value(); - let tokens: TokenStream = str - .parse() - .map_err(|err| GeneratorError::Syn(syn::Error::from(err)))?; - Ok(quote! { (#tokens) }) -} - -pub fn generate_default( - default: &Option<args::DefaultValue>, - default_with: &Option<LitStr>, -) -> GeneratorResult<Option<TokenStream>> { - match (default, default_with) { - (Some(args::DefaultValue::Default), _) => { - Ok(Some(quote! { ::std::default::Default::default() })) - } - (Some(args::DefaultValue::Value(lit)), _) => Ok(Some(generate_default_value(lit)?)), - (None, Some(lit)) => Ok(Some(generate_default_with(lit)?)), - (None, None) => Ok(None), - } -} - -pub fn get_cfg_attrs(attrs: &[Attribute]) -> Vec<Attribute> { - attrs - .iter() - .filter(|attr| !attr.path().segments.is_empty() && attr.path().segments[0].ident == "cfg") - .cloned() - .collect() -} - -pub fn parse_graphql_attrs<T: FromMeta + Default>( - attrs: &[Attribute], -) -> GeneratorResult<Option<T>> { - for attr in attrs { - if attr.path().is_ident("graphql") { - return Ok(Some(T::from_meta(&attr.meta)?)); - } - } - Ok(None) -} - -pub fn remove_graphql_attrs(attrs: &mut Vec<Attribute>) { - if let Some((idx, _)) = attrs - .iter() - .enumerate() - .find(|(_, a)| a.path().is_ident("graphql")) - { - attrs.remove(idx); - } -} - -pub fn get_type_path_and_name(ty: &Type) -> GeneratorResult<(&Type, String)> { - match ty { - Type::Path(path) => Ok(( - ty, - path.path - .segments - .last() - .map(|s| s.ident.to_string()) - .unwrap(), - )), - Type::Group(TypeGroup { elem, .. }) => get_type_path_and_name(elem), - Type::TraitObject(trait_object) => Ok(( - ty, - trait_object - .bounds - .iter() - .find_map(|bound| match bound { - TypeParamBound::Trait(t) => { - Some(t.path.segments.last().map(|s| s.ident.to_string()).unwrap()) - } - _ => None, - }) - .unwrap(), - )), - _ => Err(Error::new_spanned(ty, "Invalid type").into()), - } -} - -pub fn visible_fn(visible: &Option<Visible>) -> TokenStream { - match visible { - None | Some(Visible::None) => quote! { ::std::option::Option::None }, - Some(Visible::HiddenAlways) => quote! { ::std::option::Option::Some(|_| false) }, - Some(Visible::FnName(name)) => { - quote! { ::std::option::Option::Some(#name) } - } - } -} - -pub fn parse_complexity_expr(expr: Expr) -> GeneratorResult<(HashSet<String>, Expr)> { - #[derive(Default)] - struct VisitComplexityExpr { - variables: HashSet<String>, - } - - impl<'a> Visit<'a> for VisitComplexityExpr { - fn visit_expr_path(&mut self, i: &'a ExprPath) { - if let Some(ident) = i.path.get_ident() { - if ident != "child_complexity" { - self.variables.insert(ident.to_string()); - } - } - } - } - - let mut visit = VisitComplexityExpr::default(); - visit.visit_expr(&expr); - Ok((visit.variables, expr)) -} - -pub fn gen_deprecation(deprecation: &Deprecation, crate_name: &TokenStream) -> TokenStream { - match deprecation { - Deprecation::NoDeprecated => { - quote! { #crate_name::registry::Deprecation::NoDeprecated } - } - Deprecation::Deprecated { - reason: Some(reason), - } => { - quote! { #crate_name::registry::Deprecation::Deprecated { reason: ::std::option::Option::Some(::std::string::ToString::to_string(#reason)) } } - } - Deprecation::Deprecated { reason: None } => { - quote! { #crate_name::registry::Deprecation::Deprecated { reason: ::std::option::Option::None } } - } - } -} - -pub fn extract_input_args<T: FromMeta + Default>( - crate_name: &proc_macro2::TokenStream, - method: &mut ImplItemFn, -) -> GeneratorResult<Vec<(PatIdent, Type, T)>> { - let mut args = Vec::new(); - let mut create_ctx = true; - - if method.sig.inputs.is_empty() { - return Err(Error::new_spanned( - &method.sig, - "The self receiver must be the first parameter.", - ) - .into()); - } - - for (idx, arg) in method.sig.inputs.iter_mut().enumerate() { - if let FnArg::Receiver(receiver) = arg { - if idx != 0 { - return Err(Error::new_spanned( - receiver, - "The self receiver must be the first parameter.", - ) - .into()); - } - } else if let FnArg::Typed(pat) = arg { - if idx == 0 { - return Err(Error::new_spanned( - pat, - "The self receiver must be the first parameter.", - ) - .into()); - } - - match (&*pat.pat, &*pat.ty) { - (Pat::Ident(arg_ident), Type::Reference(TypeReference { elem, .. })) => { - if let Type::Path(path) = elem.as_ref() { - if idx != 1 || path.path.segments.last().unwrap().ident != "Context" { - args.push(( - arg_ident.clone(), - pat.ty.as_ref().clone(), - parse_graphql_attrs::<T>(&pat.attrs)?.unwrap_or_default(), - )); - } else { - create_ctx = false; - } - } - } - (Pat::Ident(arg_ident), ty) => { - args.push(( - arg_ident.clone(), - ty.clone(), - parse_graphql_attrs::<T>(&pat.attrs)?.unwrap_or_default(), - )); - remove_graphql_attrs(&mut pat.attrs); - } - _ => { - return Err(Error::new_spanned(arg, "Invalid argument type.").into()); - } - } - } - } - - if create_ctx { - let arg = syn::parse2::<FnArg>(quote! { _: &#crate_name::Context<'_> }).unwrap(); - method.sig.inputs.insert(1, arg); - } - - Ok(args) -} - -pub struct RemoveLifetime; - -impl VisitMut for RemoveLifetime { - fn visit_lifetime_mut(&mut self, i: &mut Lifetime) { - i.ident = Ident::new("_", Span::call_site()); - visit_mut::visit_lifetime_mut(self, i); - } -} - -pub fn gen_directive_calls( - directive_calls: &[Expr], - location: TypeDirectiveLocation, -) -> Vec<TokenStream> { - directive_calls - .iter() - .map(|directive| { - let directive_path = extract_directive_call_path(directive).expect( - "Directive invocation expression format must be [<directive_path>::]<directive_name>::apply(<args>)", - ); - let identifier = location.location_trait_identifier(); - quote!({ - <#directive_path as async_graphql::registry::location_traits::#identifier>::check(); - <#directive_path as async_graphql::TypeDirective>::register(&#directive_path, registry); - #directive - }) - }) - .collect::<Vec<_>>() -} - -fn extract_directive_call_path(directive: &Expr) -> Option<syn::Path> { - if let Expr::Call(expr) = directive { - if let Expr::Path(ref expr) = *expr.func { - let mut path = expr.path.clone(); - if path.segments.pop()?.value().ident != "apply" { - return None; - } - - path.segments.pop_punct()?; - - return Some(path); - } - } - - None -} - -pub fn gen_boxed_trait(crate_name: &TokenStream) -> TokenStream { - if cfg!(feature = "boxed-trait") { - quote! { - #[#crate_name::async_trait::async_trait] - } - } else { - quote! {} - } -} diff --git a/derive/src/validators.rs b/derive/src/validators.rs deleted file mode 100644 index d776e14a4..000000000 --- a/derive/src/validators.rs +++ /dev/null @@ -1,249 +0,0 @@ -use darling::FromMeta; -use proc_macro2::TokenStream; -use quote::{ToTokens, quote}; -use syn::{Expr, Lit, Result}; - -#[derive(Clone)] -pub enum Number { - F64(f64), - I64(i64), -} - -impl FromMeta for Number { - fn from_value(value: &Lit) -> darling::Result<Self> { - match value { - Lit::Int(n) => Ok(Number::I64(n.base10_parse::<i64>()?)), - Lit::Float(n) => Ok(Number::F64(n.base10_parse::<f64>()?)), - _ => Err(darling::Error::unexpected_type("number")), - } - } -} - -impl ToTokens for Number { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - Number::F64(n) => tokens.extend(quote!(#n as f64)), - Number::I64(n) => tokens.extend(quote!(#n as i64)), - } - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum UuidVersionValidation { - None, - Value(Lit), -} - -impl FromMeta for UuidVersionValidation { - fn from_word() -> darling::Result<Self> { - Ok(UuidVersionValidation::None) - } - - fn from_value(value: &Lit) -> darling::Result<Self> { - Ok(UuidVersionValidation::Value(value.clone())) - } -} - -#[derive(FromMeta, Default, Clone)] -pub struct Validators { - #[darling(default)] - multiple_of: Option<Number>, - #[darling(default)] - min_password_strength: Option<u8>, - #[darling(default)] - maximum: Option<Number>, - #[darling(default)] - minimum: Option<Number>, - #[darling(default)] - max_length: Option<usize>, - #[darling(default)] - min_length: Option<usize>, - #[darling(default)] - max_items: Option<usize>, - #[darling(default)] - min_items: Option<usize>, - #[darling(default)] - chars_max_length: Option<usize>, - #[darling(default)] - chars_min_length: Option<usize>, - #[darling(default)] - email: bool, - #[darling(default)] - url: bool, - #[darling(default)] - ip: bool, - #[darling(default)] - regex: Option<String>, - #[darling(default)] - uuid: Option<UuidVersionValidation>, - #[darling(default, multiple)] - custom: Vec<Expr>, - #[darling(default)] - list: bool, -} - -impl Validators { - pub fn create_validators( - &self, - crate_name: &TokenStream, - value: TokenStream, - map_err: Option<TokenStream>, - ) -> Result<TokenStream> { - let mut list_validators = Vec::new(); - let mut elem_validators = Vec::new(); - let mut codes = Vec::new(); - - if let Some(n) = &self.max_items { - list_validators.push(quote! { - #crate_name::validators::max_items(__raw_value, #n) - }); - } - - if let Some(n) = &self.min_items { - list_validators.push(quote! { - #crate_name::validators::min_items(__raw_value, #n) - }); - } - - if let Some(n) = &self.multiple_of { - elem_validators.push(quote! { - #crate_name::validators::multiple_of(__raw_value, #n) - }); - } - - if let Some(n) = &self.min_password_strength { - elem_validators.push(quote! { - #crate_name::validators::min_password_strength(__raw_value, #n) - }); - } - - if let Some(n) = &self.maximum { - elem_validators.push(quote! { - #crate_name::validators::maximum(__raw_value, #n) - }); - } - - if let Some(n) = &self.minimum { - elem_validators.push(quote! { - #crate_name::validators::minimum(__raw_value, #n) - }); - } - - if let Some(n) = &self.max_length { - elem_validators.push(quote! { - #crate_name::validators::max_length(__raw_value, #n) - }); - } - - if let Some(n) = &self.min_length { - elem_validators.push(quote! { - #crate_name::validators::min_length(__raw_value, #n) - }); - } - - if let Some(n) = &self.chars_max_length { - elem_validators.push(quote! { - #crate_name::validators::chars_max_length(__raw_value, #n) - }); - } - - if let Some(n) = &self.chars_min_length { - elem_validators.push(quote! { - #crate_name::validators::chars_min_length(__raw_value, #n) - }); - } - - if self.email { - elem_validators.push(quote! { - #crate_name::validators::email(__raw_value) - }); - } - - if self.url { - elem_validators.push(quote! { - #crate_name::validators::url(__raw_value) - }); - } - - if self.ip { - elem_validators.push(quote! { - #crate_name::validators::ip(__raw_value) - }); - } - - if let Some(re) = &self.regex { - elem_validators.push(quote! { - #crate_name::validators::regex(__raw_value, #re) - }); - } - - if let Some(version_validation) = &self.uuid { - match version_validation { - UuidVersionValidation::None => { - elem_validators.push(quote! { - #crate_name::validators::uuid(__raw_value, None) - }); - } - UuidVersionValidation::Value(version) => { - elem_validators.push(quote! { - #crate_name::validators::uuid(__raw_value, Some(#version)) - }); - } - } - } - - if !list_validators.is_empty() { - codes.push(quote! { - if let ::std::option::Option::Some(__raw_value) = #crate_name::InputType::as_raw_value(#value) { - #(#list_validators #map_err ?;)* - } - }); - } - - if !elem_validators.is_empty() { - if self.list { - codes.push(quote! { - if let ::std::option::Option::Some(value) = #crate_name::InputType::as_raw_value(#value) { - for __item in value { - if let ::std::option::Option::Some(__raw_value) = #crate_name::InputType::as_raw_value(__item) { - #(#elem_validators #map_err ?;)* - } - } - } - }); - } else { - codes.push(quote! { - if let ::std::option::Option::Some(__raw_value) = #crate_name::InputType::as_raw_value(#value) { - #(#elem_validators #map_err ?;)* - } - }); - } - } - - for expr in &self.custom { - if self.list { - codes.push(quote! { - if let ::std::option::Option::Some(value) = #crate_name::InputType::as_raw_value(#value) { - for __item in value { - if let ::std::option::Option::Some(__raw_value) = #crate_name::InputType::as_raw_value(__item) { - #crate_name::CustomValidator::check(&(#expr), __raw_value) #map_err ?; - } - } - } - }); - } else { - codes.push(quote! { - if let ::std::option::Option::Some(__raw_value) = #crate_name::InputType::as_raw_value(#value) { - #crate_name::CustomValidator::check(&(#expr), __raw_value) #map_err ?; - } - }); - } - } - - if codes.is_empty() { - return Ok(quote!()); - } - - Ok(quote!(#(#codes)*)) - } -} diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 52a6b8054..000000000 --- a/docs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -book diff --git a/docs/en/book.toml b/docs/en/book.toml deleted file mode 100644 index 7659ff22d..000000000 --- a/docs/en/book.toml +++ /dev/null @@ -1,9 +0,0 @@ -[book] -authors = ["sunli"] -description = "Async-graphql Book" -src = "src" -language = "en" -title = "Async-graphql Book" - -[rust] -edition = "2024" diff --git a/docs/en/src/SUMMARY.md b/docs/en/src/SUMMARY.md deleted file mode 100644 index ac2b659a9..000000000 --- a/docs/en/src/SUMMARY.md +++ /dev/null @@ -1,43 +0,0 @@ -# Async-graphql Book - -- [Introduction](introduction.md) -- [Quickstart](quickstart.md) -- [Type System](typesystem.md) - - [SimpleObject](define_simple_object.md) - - [Object](define_complex_object.md) - - [Context](context.md) - - [Error handling](error_handling.md) - - [Merging Objects / Subscriptions](merging_objects.md) - - [Derived fields](derived_fields.md) - - [Enum](define_enum.md) - - [Interface](define_interface.md) - - [Union](define_union.md) - - [InputObject](define_input_object.md) - - [OneofObject](define_one_of_object.md) - - [Default value](default_value.md) - - [Generics](generic.md) -- [Schema](define_schema.md) - - [Query and Mutation](query_and_mutation.md) - - [Subscription](subscription.md) - - [SDL Export](sdl_export.md) -- [Utilities](utilities.md) - - [Field guard](field_guard.md) - - [Input value validators](input_value_validators.md) - - [Cache control](cache_control.md) - - [Cursor connections](cursor_connections.md) - - [Error extensions](error_extensions.md) - - [Apollo Tracing](apollo_tracing.md) - - [Query complexity and depth](depth_and_complexity.md) - - [Hide content in introspection](visibility.md) -- [Extensions](extensions.md) - - [How extensions are working](extensions_inner_working.md) - - [Available extensions](extensions_available.md) -- [Integrations](integrations.md) - - [Poem](integrations_to_poem.md) - - [Warp](integrations_to_warp.md) - - [Actix-web](integrations_to_actix_web.md) -- [Advanced topics](advanced_topics.md) - - [Custom scalars](custom_scalars.md) - - [Optimizing N+1 queries](dataloader.md) - - [Custom directive](custom_directive.md) - - [Apollo Federation](apollo_federation.md) diff --git a/docs/en/src/advanced_topics.md b/docs/en/src/advanced_topics.md deleted file mode 100644 index 5d0c11672..000000000 --- a/docs/en/src/advanced_topics.md +++ /dev/null @@ -1 +0,0 @@ -# Advanced topics diff --git a/docs/en/src/apollo_federation.md b/docs/en/src/apollo_federation.md deleted file mode 100644 index c9133e3ad..000000000 --- a/docs/en/src/apollo_federation.md +++ /dev/null @@ -1,520 +0,0 @@ -# Apollo Federation - -Apollo Federation is a GraphQL architecture for combining multiple GraphQL services, or subgraphs, into a single supergraph. You can read more in the [official documentation](https://www.apollographql.com/docs/apollo-server/federation/). - -> To see a complete example of federation, check out the [federation example](https://github.com/async-graphql/examples/tree/master/federation). - -## Enabling federation support - -`async-graphql` supports all the functionality of Apollo Federation v2. Support will be enabled automatically if any `#[graphql(entity)]` resolvers are found in the schema. To enable it manually, use the `enable_federation` method on the `SchemaBuilder`. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# struct Query; -# #[Object] -# impl Query { -# async fn hello(&self) -> String { "Hello".to_string() } -# } -fn main() { - let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .enable_federation() - .finish(); - // ... Start your server of choice -} -``` - -This will define the [`@link` directive](https://www.apollographql.com/docs/federation/federated-types/federated-directives#link) on your schema to enable Federation v2. - -## Entities and `@key` - -[Entities](https://www.apollographql.com/docs/federation/entities) are a core feature of federation, they allow multiple subgraphs to contribute fields to the same type. An entity is a GraphQL `type` with at least one [`@key` directive][`@key`]. To create a [`@key`] for a type, create a reference resolver using the `#[graphql(entity)]` attribute. This resolver should be defined on the `Query` struct, but will not appear as a field in the schema. - -> Even though a reference resolver looks up an individual entity, it is **crucial that you use a [dataloader](dataloader.md)** in the implementation. The federation router will look up entities in batches, which can quickly lead the N+1 performance issues. - -### Example - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(SimpleObject)] -# struct User { id: ID } -struct Query; - -#[Object] -impl Query { - #[graphql(entity)] - async fn find_user_by_id(&self, id: ID) -> User { - User { id } - } - - #[graphql(entity)] - async fn find_user_by_id_with_username(&self, #[graphql(key)] id: ID, username: String) -> User { - User { id } - } - - #[graphql(entity)] - async fn find_user_by_id_and_username(&self, id: ID, username: String) -> User { - User { id } - } -} -``` - -**Notice the difference between these three lookup functions, which are all looking for the `User` object.** - -- `find_user_by_id`: Use `id` to find a `User` object, the key for `User` is `id`. - -- `find_user_by_id_with_username`: Use `id` to find an `User` object, the key for `User` is `id`, and the `username` field value of the `User` object is requested (e.g., via `@external` and `@requires`). - -- `find_user_by_id_and_username`: Use `id` and `username` to find an `User` object, the keys for `User` are `id` and `username`. - -The resulting schema will look like this: - -```graphql -type Query { - # These fields will not be exposed to users, they are only used by the router to resolve entities - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type User @key(fields: "id") @key(fields: "id username") { - id: ID! -} -``` - -### Defining a compound primary key - -A single primary key can consist of multiple fields, and even nested fields, you can use `InputObject` to implements a nested primary key. - -In the following example, the primary key of the `User` object is `key { a b }`. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(SimpleObject)] -# struct User { key: Key } -# #[derive(SimpleObject)] -# struct Key { a: i32, b: i32 } -#[derive(InputObject)] -struct NestedKey { - a: i32, - b: i32, -} - -struct Query; - -#[Object] -impl Query { - #[graphql(entity)] - async fn find_user_by_key(&self, key: NestedKey) -> User { - let NestedKey { a, b } = key; - User { key: Key{a, b} } - } -} -``` - -The resulting schema will look like this: - -```graphql -type Query { - # These fields will not be exposed to users, they are only used by the router to resolve entities - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service! -} - -type User @key(fields: "key { a b }") { - key: Key! -} - -type Key { - a: Int! - b: Int! -} -``` - -### Creating unresolvable entities - -There are certain times when you need to reference an entity, but not add any fields to it. This is particularly useful when you want to link data from separate subgraphs together, but neither subgraph has all the data. - -If you wanted to implement the [products and reviews subgraphs example](https://www.apollographql.com/docs/federation/entities/#referencing-an-entity-without-contributing-fields) from the Apollo Docs, you would create the following types for the reviews subgraph: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(SimpleObject)] -struct Review { - product: Product, - score: u64, -} - -#[derive(SimpleObject)] -#[graphql(unresolvable)] -struct Product { - id: u64, -} -``` - -This will add the `@key(fields: "id", resolvable: false)` directive to the `Product` type in the reviews subgraph. - -For more complex entity keys, such as ones with nested fields in compound keys, you can override the fields in the directive as so: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(SimpleObject)] -#[graphql(unresolvable = "id organization { id }")] -struct User { - id: u64, - organization: Organization, -} - -#[derive(SimpleObject)] -struct Organization { - id: u64, -} -``` - -However, it is important to note that no validation will be done to check that these fields exist. - -## `@shareable` - -Apply the [`@shareable` directive](https://www.apollographql.com/docs/federation/federated-types/federated-directives#shareable) to a type or field to indicate that multiple subgraphs can resolve it. - -### `@shareable` fields -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(SimpleObject)] -#[graphql(complex)] -struct Position { - #[graphql(shareable)] - x: u64, -} - -#[ComplexObject] -impl Position { - #[graphql(shareable)] - async fn y(&self) -> u64 { - 0 - } -} -``` - -The resulting schema will look like this: - -```graphql -type Position { - x: Int! @shareable - y: Int! @shareable -} -``` - - -### `@shareable` type - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(SimpleObject)] -#[graphql(shareable)] -struct Position { - x: u64, - y: u64, -} -``` - -The resulting schema will look like this: - -```graphql -type Position @shareable { - x: Int! - y: Int! -} -``` - -## `@inaccessible` - -The [`@inaccessible` directive](https://www.apollographql.com/docs/federation/federated-types/federated-directives#inaccessible) is used to omit something from the supergraph schema (e.g., if it's not yet added to all subgraphs which share a `@shareable` type). - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(SimpleObject)] -#[graphql(shareable)] -struct Position { - x: u32, - y: u32, - #[graphql(inaccessible)] - z: u32, -} -``` - -Results in: - -```graphql -type Position @shareable { - x: Int! - y: Int! - z: Int! @inaccessible -} -``` - -## `@override` - -The [`@override` directive](https://www.apollographql.com/docs/federation/federated-types/federated-directives#override) is used to take ownership of a field from another subgraph. This is useful for migrating a field from one subgraph to another. - -For example, if you add a new "Inventory" subgraph which should take over responsibility for the `inStock` field currently provided by the "Products" subgraph, you might have something like this: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(SimpleObject)] -struct Product { - id: ID, - #[graphql(override_from = "Products")] - in_stock: bool, -} -``` - -Which results in: - -```graphql -type Product @key(fields: "id") { - id: ID! - inStock: Boolean! @override(from: "Products") -} -``` - -## `@external` - -The [`@external` directive](https://www.apollographql.com/docs/federation/federated-types/federated-directives#external) is used to indicate that a field is usually provided by another subgraph, but is sometimes required by this subgraph (when combined with `@requires`) or provided by this subgraph (when combined with `@provides`). - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(SimpleObject)] -struct Product { - id: ID, - #[graphql(external)] - name: String, - in_stock: bool, -} -``` - -Results in: - -```graphql -type Product { - id: ID! - name: String! @external - inStock: Boolean! -} -``` - -## `@provides` - -The [`@provides` directive](https://www.apollographql.com/docs/federation/federated-types/federated-directives#provides) is used to indicate that a field is provided by this subgraph, but only sometimes. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(SimpleObject)] -struct Product { - id: ID, - #[graphql(external)] - human_name: String, - in_stock: bool, -} - -struct Query; - -#[Object] -impl Query { - /// This operation will provide the `humanName` field on `Product - #[graphql(provides = "humanName")] - async fn out_of_stock_products(&self) -> Vec<Product> { - vec![Product { - id: "1".into(), - human_name: "My Product".to_string(), - in_stock: false, - }] - } - async fn discontinued_products(&self) -> Vec<Product> { - vec![Product { - id: "2".into(), - human_name: String::new(), // This is ignored by the router - in_stock: false, - }] - } - #[graphql(entity)] - async fn find_product_by_id(&self, id: ID) -> Product { - Product { - id, - human_name: String::new(), // This is ignored by the router - in_stock: true, - } - } -} -``` - -Note that the `#[graphql(provides)]` attribute takes the field name as it appears in the schema, not the Rust field name. - -The resulting schema will look like this: - -```graphql -type Product @key(fields: "id") { - id: ID! - humanName: String! @external - inStock: Boolean! -} - -type Query { - outOfStockProducts: [Product!]! @provides(fields: "humanName") - discontinuedProducts: [Product!]! -} -``` - -## `@requires` - -The [`@requires` directive](https://www.apollographql.com/docs/federation/federated-types/federated-directives#requires) is used to indicate that an `@external` field is required for this subgraph to resolve some other field(s). If our `shippingEstimate` field requires the `size` and `weightInPounts` fields, then we might want a subgraph entity which looks like this: - -```graphql -type Product @key(fields: "id") { - id: ID! - size: Int! @external - weightInPounds: Int! @external - shippingEstimate: String! @requires(fields: "size weightInPounds") -} -``` - -In order to implement this in Rust, we can use the `#[graphql(requires)]` attribute: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(SimpleObject)] -#[graphql(complex)] -struct Product { - id: ID, - #[graphql(external)] - size: u32, - #[graphql(external)] - weight_in_pounds: u32, -} - -#[ComplexObject] -impl Product { - #[graphql(requires = "size weightInPounds")] - async fn shipping_estimate(&self) -> String { - let price = self.size * self.weight_in_pounds; - format!("${}", price) - } -} -``` - -Note that we use the GraphQL field name `weightInPounds`, not the Rust field name `weight_in_pounds` in `requires`. To populate those external fields, we add them as arguments in the entity resolver: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(SimpleObject)] -# struct Product { -# id: ID, -# #[graphql(external)] -# size: u32, -# #[graphql(external)] -# weight_in_pounds: u32, -# } -# struct Query; -#[Object] -impl Query { - #[graphql(entity)] - async fn find_product_by_id( - &self, - #[graphql(key)] id: ID, - size: Option<u32>, - weight_in_pounds: Option<u32> - ) -> Product { - Product { - id, - size: size.unwrap_or_default(), - weight_in_pounds: weight_in_pounds.unwrap_or_default(), - } - } -} -``` - -The inputs are `Option<>` even though the fields are required. This is because the external fields are _only_ passed to the subgraph when the field(s) that require them are being selected. If the `shippingEstimate` field is not selected, then the `size` and `weightInPounds` fields will not be passed to the subgraph. **Always use optional types for external fields.** - -We have to put _something_ in place for `size` and `weight_in_pounds` as they are still required fields on the type, so we use `unwrap_or_default()` to provide a default value. This looks a little funny, as we're populating the fields with nonsense values, but we have confidence that they will not be needed if they were not provided. **Make sure to use `@requires` if you are consuming `@external` fields, or your code will be wrong.** - -### Nested `@requires` - -A case where the `@requires` directive can be confusing is when there are nested entities. For example, if we had an `Order` type which contained a `Product`, then we would need an entity resolver like this: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(SimpleObject)] -# pub struct Order { id: ID } -# struct Query; -#[Object] -impl Query { - #[graphql(entity)] - async fn find_order_by_id(&self, id: ID) -> Option<Order> { - Some(Order { id }) - } -} -``` - -There are no inputs on this entity resolver, so how do we populate the `size` and `weight_in_pounds` fields on `Product` if a user has a query like `order { product { shippingEstimate } }`? The supergraph implementation will solve this for us by calling the `find_product_by_id` separately for any fields which have a `@requires` directive, so the subgraph code does not need to worry about how entities relate. - -## `@tag` - -The [`@tag` directive](https://www.apollographql.com/docs/federation/federated-types/federated-directives#tag) is used to add metadata to a schema location for features like [contracts](https://www.apollographql.com/docs/studio/contracts/). To add a tag like this: - -```graphql -type User @tag(name: "team-accounts") { - id: String! - name: String! -} -``` - -You can write code like this: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(SimpleObject)] -#[graphql(tag = "team-accounts")] -struct User { - id: ID, - name: String, -} -``` - -## `@composeDirective` - -The [`@composeDirective` directive](https://www.apollographql.com/docs/federation/federation-spec/#composedirective) is used to add a custom type system directive to the supergraph schema. Without `@composeDirective`, and [custom type system directives](./custom_directive#type-system-directives) are omitted from the composed supergraph schema. To include a custom type system directive as a composed directive, just add the `composable` attribute to the `#[TypeDirective]` macro: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[TypeDirective( - location = "Object", - composable = "https://custom.spec.dev/extension/v1.0", -)] -fn custom() {} -``` - -In addition to the [normal type system directive behavior](./custom_directive#type-system-directives), this will add the following bits to the output schema: - -```graphql -extend schema @link( - url: "https://custom.spec.dev/extension/v1.0" - import: ["@custom"] -) - @composeDirective(name: "@custom") -``` - -[`@key`]: https://www.apollographql.com/docs/federation/entities#1-define-a-key diff --git a/docs/en/src/apollo_tracing.md b/docs/en/src/apollo_tracing.md deleted file mode 100644 index 67a4e7cc9..000000000 --- a/docs/en/src/apollo_tracing.md +++ /dev/null @@ -1,19 +0,0 @@ -# Apollo Tracing - -Apollo Tracing provides performance analysis results for each step of query. This is an extension to `Schema`, and the performance analysis results are stored in `QueryResponse`. - -To enable the Apollo Tracing extension, add the extension when the `Schema` is created. - -```rust -# extern crate async_graphql; -use async_graphql::*; -use async_graphql::extensions::ApolloTracing; - -# struct Query; -# #[Object] -# impl Query { async fn version(&self) -> &str { "1.0" } } - -let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .extension(ApolloTracing) // Enable ApolloTracing extension - .finish(); -``` diff --git a/docs/en/src/cache_control.md b/docs/en/src/cache_control.md deleted file mode 100644 index f75d20f34..000000000 --- a/docs/en/src/cache_control.md +++ /dev/null @@ -1,54 +0,0 @@ -# Cache control - -Production environments often rely on caching to improve performance. - -A GraphQL query will call multiple resolver functions and each resolver can have a different cache definition. Some may cache for a few seconds, some may cache for a few hours, some may be the same for all users, and some may be different for each session. - -`Async-graphql` provides a mechanism that allows you to define the cache time and scope for each resolver. - -You can define cache parameters on the object or on its fields. The following example shows two uses of cache control parameters. - -You can use `max_age` parameters to control the age of the cache (in seconds), and you can also use `public` and `private` to control the scope of the cache. When you do not specify it, the scope will default to `public`. - -when querying multiple resolvers, the results of all cache control parameters will be combined and the `max_age` minimum value will be taken. If the scope of any object or field is `private`, the result will be `private`. - -We can use `QueryResponse` to get a merged cache control result from a query result, and call `CacheControl::value` to get the corresponding HTTP header. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# struct Query; -#[Object(cache_control(max_age = 60))] -impl Query { - #[graphql(cache_control(max_age = 30))] - async fn value1(&self) -> i32 { - 1 - } - - #[graphql(cache_control(private))] - async fn value2(&self) -> i32 { - 2 - } - - async fn value3(&self) -> i32 { - 3 - } -} -``` - -The following are different queries corresponding to different cache control results: - -```graphql -# max_age=30 -{ value1 } -``` - -```graphql -# max_age=30, private -{ value1 value2 } -``` - -```graphql -# max_age=60 -{ value3 } -``` diff --git a/docs/en/src/context.md b/docs/en/src/context.md deleted file mode 100644 index 0b338f13e..000000000 --- a/docs/en/src/context.md +++ /dev/null @@ -1,160 +0,0 @@ -# Context - -The main goal of `Context` is to acquire global data attached to Schema and also data related to the actual query being processed. - -## Store Data - -Inside the `Context` you can put global data, like environment variables, db connection pool, whatever you may need in every query. - -The data must implement `Send` and `Sync`. - -You can request the data inside a query by just calling `ctx.data::<TypeOfYourData>()`. - -**Note that if the return value of resolver function is borrowed from `Context`, you will need to explicitly state the lifetime of the argument.** - -The following example shows how to borrow data in `Context`. - -```rust -# extern crate async_graphql; -use async_graphql::*; - -struct Query; - -#[Object] -impl Query { - async fn borrow_from_context_data<'ctx>( - &self, - ctx: &Context<'ctx> - ) -> Result<&'ctx String> { - ctx.data::<String>() - } -} -``` - -### Schema data - -You can put data inside the context at the creation of the schema, it's useful for data that do not change, like a connection pool. - -An instance of how it would be written inside an application: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(Default,SimpleObject)] -# struct Query { version: i32} -# struct EnvStruct; -# let env_struct = EnvStruct; -# struct S3Object; -# let s3_storage = S3Object; -# struct DBConnection; -# let db_core = DBConnection; -let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription) - .data(env_struct) - .data(s3_storage) - .data(db_core) - .finish(); -``` - -### Request data - -You can put data inside the context at the execution of the request, it's useful for authentication data for instance. - -A little example with a `warp` route: - -```rust -# extern crate async_graphql; -# extern crate async_graphql_warp; -# extern crate warp; -# use async_graphql::*; -# use warp::{Filter, Reply}; -# use std::convert::Infallible; -# #[derive(Default, SimpleObject)] -# struct Query { name: String } -# struct AuthInfo { pub token: Option<String> } -# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish(); -# let schema_filter = async_graphql_warp::graphql(schema); -let graphql_post = warp::post() - .and(warp::path("graphql")) - .and(warp::header::optional("Authorization")) - .and(schema_filter) - .and_then( |auth: Option<String>, (schema, mut request): (Schema<Query, EmptyMutation, EmptySubscription>, async_graphql::Request)| async move { - // Do something to get auth data from the header - let your_auth_data = AuthInfo { token: auth }; - let response = schema - .execute( - request - .data(your_auth_data) - ).await; - - Ok::<_, Infallible>(async_graphql_warp::GraphQLResponse::from(response)) - }); -``` - -## Headers - -With the Context you can also insert and appends headers. - -```rust -# extern crate async_graphql; -# extern crate http; -# use ::http::header::ACCESS_CONTROL_ALLOW_ORIGIN; -# use async_graphql::*; -# struct Query; -#[Object] -impl Query { - async fn greet(&self, ctx: &Context<'_>) -> String { - // Headers can be inserted using the `http` constants - let was_in_headers = ctx.insert_http_header(ACCESS_CONTROL_ALLOW_ORIGIN, "*"); - - // They can also be inserted using &str - let was_in_headers = ctx.insert_http_header("Custom-Header", "1234"); - - // If multiple headers with the same key are `inserted` then the most recent - // one overwrites the previous. If you want multiple headers for the same key, use - // `append_http_header` for subsequent headers - let was_in_headers = ctx.append_http_header("Custom-Header", "Hello World"); - - String::from("Hello world") - } -} -``` - -## Selection / LookAhead - -Sometimes you want to know what fields are requested in the subquery to optimize the processing of data. You can read fields across the query with `ctx.field()` which will give you a `SelectionField` which will allow you to navigate across the fields and subfields. - -If you want to perform a search across the query or the subqueries, you do not have to do this by hand with the `SelectionField`, you can use the `ctx.look_ahead()` to perform a selection - -```rust -# extern crate async_graphql; -use async_graphql::*; - -#[derive(SimpleObject)] -struct Detail { - c: i32, - d: i32, -} - -#[derive(SimpleObject)] -struct MyObj { - a: i32, - b: i32, - detail: Detail, -} - -struct Query; - -#[Object] -impl Query { - async fn obj(&self, ctx: &Context<'_>) -> MyObj { - if ctx.look_ahead().field("a").exists() { - // This is a query like `obj { a }` - } else if ctx.look_ahead().field("detail").field("c").exists() { - // This is a query like `obj { detail { c } }` - } else { - // This query doesn't have `a` - } - unimplemented!() - } -} -``` diff --git a/docs/en/src/cursor_connections.md b/docs/en/src/cursor_connections.md deleted file mode 100644 index 1f1b59bec..000000000 --- a/docs/en/src/cursor_connections.md +++ /dev/null @@ -1,45 +0,0 @@ -# Cursor connections - -Relay's cursor connection specification is designed to provide a consistent method for query paging. For more details on the specification see the [GraphQL Cursor Connections Specification](https://facebook.github.io/relay/graphql/connections.htm)。 - -Defining a cursor connection in `async-graphql` is very simple, you just call the `connection::query` function and query data in the closure. - -```rust -# extern crate async_graphql; -use async_graphql::*; -use async_graphql::types::connection::*; - -struct Query; - -#[Object] -impl Query { - async fn numbers(&self, - after: Option<String>, - before: Option<String>, - first: Option<i32>, - last: Option<i32>, - ) -> Result<Connection<usize, i32, EmptyFields, EmptyFields>> { - query(after, before, first, last, |after, before, first, last| async move { - let mut start = after.map(|after| after + 1).unwrap_or(0); - let mut end = before.unwrap_or(10000); - if let Some(first) = first { - end = (start + first).min(end); - } - if let Some(last) = last { - start = if last > end - start { - end - } else { - end - last - }; - } - let mut connection = Connection::new(start > 0, end < 10000); - connection.edges.extend( - (start..end).into_iter().map(|n| - Edge::with_additional_fields(n, n as i32, EmptyFields) - )); - Ok::<_, async_graphql::Error>(connection) - }).await - } -} - -``` diff --git a/docs/en/src/custom_directive.md b/docs/en/src/custom_directive.md deleted file mode 100644 index 51a3f63aa..000000000 --- a/docs/en/src/custom_directive.md +++ /dev/null @@ -1,103 +0,0 @@ -# Custom directive - -There are two types of directives in GraphQL: executable and type system. Executable directives are used by the client within an operation to modify the behavior (like the built-in `@include` and `@skip` directives). Type system directives provide additional information about the types, potentially modifying how the server behaves (like `@deprecated` and `@oneOf`). `async-graphql` allows you to declare both types of custom directives, with different limitations on each. - -## Executable directives - -To create a custom executable directive, you need to implement the `CustomDirective` trait, and then use the `Directive` macro to -generate a factory function that receives the parameters of the directive and returns an instance of the directive. - -Currently `async-graphql` only supports custom executable directives located at `FIELD`. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -struct ConcatDirective { - value: String, -} - -#[async_trait::async_trait] -impl CustomDirective for ConcatDirective { - async fn resolve_field(&self, _ctx: &Context<'_>, resolve: ResolveFut<'_>) -> ServerResult<Option<Value>> { - resolve.await.map(|value| { - value.map(|value| match value { - Value::String(str) => Value::String(str + &self.value), - _ => value, - }) - }) - } -} - -#[Directive(location = "Field")] -fn concat(value: String) -> impl CustomDirective { - ConcatDirective { value } -} -``` - -Register the directive when building the schema: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# struct Query; -# #[Object] -# impl Query { async fn version(&self) -> &str { "1.0" } } -# struct ConcatDirective { value: String, } -# #[async_trait::async_trait] -# impl CustomDirective for ConcatDirective { -# async fn resolve_field(&self, _ctx: &Context<'_>, resolve: ResolveFut<'_>) -> ServerResult<Option<Value>> { todo!() } -# } -# #[Directive(location = "Field")] -# fn concat(value: String) -> impl CustomDirective { ConcatDirective { value } } -let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .directive(concat) - .finish(); -``` - -## Type system directives - -To create a custom type system directive, you can use the `#[TypeDirective]` macro on a function: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[TypeDirective( - location = "FieldDefinition", - location = "Object", -)] -fn testDirective(scope: String, input: u32, opt: Option<u64>) {} -``` - -Current only the `FieldDefinition` and `Object` locations are supported, you can select one or both. After declaring the directive, you can apply it to a relevant location (after importing the function) like this: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[TypeDirective( -# location = "FieldDefinition", -# location = "Object", -# )] -# fn testDirective(scope: String, input: u32, opt: Option<u64>) {} -#[derive(SimpleObject)] -#[graphql( - directive = testDirective::apply("simple object type".to_string(), 1, Some(3)) -)] -struct SimpleValue { - #[graphql( - directive = testDirective::apply("field and param with \" symbol".to_string(), 2, Some(3)) - )] - some_data: String, -} -``` - -This example produces a schema like this: - -```graphql -type SimpleValue @testDirective(scope: "simple object type", input: 1, opt: 3) { - someData: String! @testDirective(scope: "field and param with \" symbol", input: 2, opt: 3) -} - -directive @testDirective(scope: String!, input: Int!, opt: Int) on FIELD_DEFINITION | OBJECT -``` - -Note: To use a type-system directive with Apollo Federation's `@composeDirective`, see [the federation docs](./apollo_federation#composeDirective) \ No newline at end of file diff --git a/docs/en/src/custom_scalars.md b/docs/en/src/custom_scalars.md deleted file mode 100644 index efb259915..000000000 --- a/docs/en/src/custom_scalars.md +++ /dev/null @@ -1,56 +0,0 @@ -# Custom scalars - -In `Async-graphql` most common scalar types are built in, but you can also create your own scalar types. - -Using `async-graphql::Scalar`, you can add support for a scalar when you implement it. You only need to implement parsing and output functions. - -The following example defines a 64-bit integer scalar where its input and output are strings. - -```rust -# extern crate async_graphql; -use async_graphql::*; - -struct StringNumber(i64); - -#[Scalar] -impl ScalarType for StringNumber { - fn parse(value: Value) -> InputValueResult<Self> { - if let Value::String(value) = &value { - // Parse the integer value - Ok(value.parse().map(StringNumber)?) - } else { - // If the type does not match - Err(InputValueError::expected_type(value)) - } - } - - fn to_value(&self) -> Value { - Value::String(self.0.to_string()) - } -} -``` - -## Use `scalar!` macro to define scalar - -If your type implemented `serde::Serialize` and `serde::Deserialize`, then you can use this macro to define a scalar more simply. - -```rust -# extern crate async_graphql; -# extern crate serde; -# use async_graphql::*; -# use serde::{Serialize, Deserialize}; -# use std::collections::HashMap; -#[derive(Serialize, Deserialize)] -struct MyValue { - a: i32, - b: HashMap<String, i32>, -} - -scalar!(MyValue); - -// Rename to `MV`. -// scalar!(MyValue, "MV"); - -// Rename to `MV` and add description. -// scalar!(MyValue, "MV", "This is my value"); -``` diff --git a/docs/en/src/dataloader.md b/docs/en/src/dataloader.md deleted file mode 100644 index 892cf6572..000000000 --- a/docs/en/src/dataloader.md +++ /dev/null @@ -1,159 +0,0 @@ -# Optimizing N+1 queries - -Have you noticed some GraphQL queries end can make hundreds of database queries, often with mostly repeated data? Lets take a look why and how to fix it. - -## Query Resolution - -Imagine if you have a simple query like this: - -```graphql -query { - todos { - users { - name - } - } -} -``` - -and `User` resolver is like this: - -```rust,ignore -struct User { - id: u64, -} - -#[Object] -impl User { - async fn name(&self, ctx: &Context<'_>) -> Result<String> { - let pool = ctx.data_unchecked::<Pool<Postgres>>(); - let (name,): (String,) = sqlx::query_as("SELECT name FROM user WHERE id = $1") - .bind(self.id) - .fetch_one(pool) - .await?; - Ok(name) - } -} -``` - -The query executor will call the `Todos` resolver which does a `select * from todo and return N todos`. Then for each -of the todos, concurrently, call the `User` resolver, `SELECT from USER where id = todo.user_id`. - -eg: - -```sql -SELECT id, todo, user_id FROM todo -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -``` - -After executing `SELECT name FROM user WHERE id = $1` many times, and most `Todo` objects belong to the same user, we -need to optimize these codes! - -## Dataloader - -We need to group queries and exclude duplicate queries. `Dataloader` can do this. -[facebook](https://github.com/facebook/dataloader) gives a request-scope batch and caching solution. - -The following is a simplified example of using `DataLoader` to optimize queries, there is also a [full code example available in GitHub](https://github.com/async-graphql/examples/tree/master/tide/dataloader-postgres). - -```rust,ignore -use async_graphql::*; -use async_graphql::dataloader::*; -use std::sync::Arc; - -struct UserNameLoader { - pool: sqlx::PgPool, -} - -impl Loader<u64> for UserNameLoader { - type Value = String; - type Error = Arc<sqlx::Error>; - - async fn load(&self, keys: &[u64]) -> Result<HashMap<u64, Self::Value>, Self::Error> { - Ok(sqlx::query_as("SELECT name FROM user WHERE id = ANY($1)") - .bind(keys) - .fetch(&self.pool) - .map_ok(|name: String| name) - .map_err(Arc::new) - .try_collect().await?) - } -} - -#[derive(SimpleObject)] -#[graphql(complex)] -struct User { - id: u64, -} - -#[ComplexObject] -impl User { - async fn name(&self, ctx: &Context<'_>) -> Result<String> { - let loader = ctx.data_unchecked::<DataLoader<UserNameLoader>>(); - let name: Option<String> = loader.load_one(self.id).await?; - name.ok_or_else(|| "Not found".into()) - } -} -``` - -To expose `UserNameLoader` in the `ctx`, you have to register it with the schema, along with a task spawner, e.g. `async_std::task::spawn`: - -```rust,ignore -let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription) - .data(DataLoader::new( - UserNameLoader, - async_std::task::spawn, // or `tokio::spawn` - )) - .finish(); -``` - -In the end, only two SQLs are needed to query the results we want! - -```sql -SELECT id, todo, user_id FROM todo -SELECT name FROM user WHERE id IN (1, 2, 3, 4) -``` - -## Implement multiple data types - -You can implement multiple data types for the same `Loader`, like this: - -```rust,ignore -# extern crate async_graphql; -# use async_graphql::*; -struct PostgresLoader { - pool: sqlx::PgPool, -} - -impl Loader<UserId> for PostgresLoader { - type Value = User; - type Error = Arc<sqlx::Error>; - - async fn load(&self, keys: &[UserId]) -> Result<HashMap<UserId, Self::Value>, Self::Error> { - // Load users from database - } -} - -impl Loader<TodoId> for PostgresLoader { - type Value = Todo; - type Error = sqlx::Error; - - async fn load(&self, keys: &[TodoId]) -> Result<HashMap<TodoId, Self::Value>, Self::Error> { - // Load todos from database - } -} -``` diff --git a/docs/en/src/default_value.md b/docs/en/src/default_value.md deleted file mode 100644 index a93b96071..000000000 --- a/docs/en/src/default_value.md +++ /dev/null @@ -1,74 +0,0 @@ -# Default value - -You can define default values for input value types. -Below are some examples. - -## Object field - -```rust -# extern crate async_graphql; -use async_graphql::*; - -struct Query; - -fn my_default() -> i32 { - 30 -} - -#[Object] -impl Query { - // The default value of the value parameter is 0, it will call i32::default() - async fn test1(&self, #[graphql(default)] value: i32) -> i32 { todo!() } - - // The default value of the value parameter is 10 - async fn test2(&self, #[graphql(default = 10)] value: i32) -> i32 { todo!() } - - // The default value of the value parameter uses the return result of the my_default function, the value is 30. - async fn test3(&self, #[graphql(default_with = "my_default()")] value: i32) -> i32 { todo!() } -} -``` - -## Interface field - -```rust -# extern crate async_graphql; -# fn my_default() -> i32 { 5 } -# struct MyObj; -# #[Object] -# impl MyObj { -# async fn test1(&self, value: i32) -> i32 { todo!() } -# async fn test2(&self, value: i32) -> i32 { todo!() } -# async fn test3(&self, value: i32) -> i32 { todo!() } -# } -use async_graphql::*; - -#[derive(Interface)] -#[graphql( - field(name = "test1", ty = "i32", arg(name = "value", ty = "i32", default)), - field(name = "test2", ty = "i32", arg(name = "value", ty = "i32", default = 10)), - field(name = "test3", ty = "i32", arg(name = "value", ty = "i32", default_with = "my_default()")), -)] -enum MyInterface { - MyObj(MyObj), -} -``` - -## Input object field - -```rust -# extern crate async_graphql; -# fn my_default() -> i32 { 5 } -use async_graphql::*; - -#[derive(InputObject)] -struct MyInputObject { - #[graphql(default)] - value1: i32, - - #[graphql(default = 10)] - value2: i32, - - #[graphql(default_with = "my_default()")] - value3: i32, -} -``` diff --git a/docs/en/src/define_complex_object.md b/docs/en/src/define_complex_object.md deleted file mode 100644 index 13ed67da4..000000000 --- a/docs/en/src/define_complex_object.md +++ /dev/null @@ -1,45 +0,0 @@ -# Object - -Different from `SimpleObject`, `Object` must have a resolver defined for each field in its `impl`. - -**A resolver function has to be asynchronous. The first argument has to be `&self`, the second is an optional `Context` and it is followed by field arguments.** - -The resolver is used to get the value of the field. For example, you can query a database and return the result. **The return type of the function is the type of the field.** You can also return a `async_graphql::Result` to return an error if it occurs. The error message will then be sent as query result. - -You may need access to global data in your query, for example a database connection pool. -When creating your `Schema`, you can use `SchemaBuilder::data` to configure the global data, and `Context::data` to configure `Context` data. -The following `value_from_db` function shows how to retrieve a database connection from `Context`. - -```rust -# extern crate async_graphql; -# struct Data { pub name: String } -# struct DbConn {} -# impl DbConn { -# fn query_something(&self, id: i64) -> std::result::Result<Data, String> { Ok(Data {name:"".into()})} -# } -# struct DbPool {} -# impl DbPool { -# fn take(&self) -> DbConn { DbConn {} } -# } -use async_graphql::*; - -struct MyObject { - value: i32, -} - -#[Object] -impl MyObject { - async fn value(&self) -> String { - self.value.to_string() - } - - async fn value_from_db( - &self, - ctx: &Context<'_>, - #[graphql(desc = "Id of object")] id: i64 - ) -> Result<String> { - let conn = ctx.data::<DbPool>()?.take(); - Ok(conn.query_something(id)?.name) - } -} -``` diff --git a/docs/en/src/define_enum.md b/docs/en/src/define_enum.md deleted file mode 100644 index 3e4514eb6..000000000 --- a/docs/en/src/define_enum.md +++ /dev/null @@ -1,71 +0,0 @@ -# Enum - -It's easy to define an `Enum`, here we have an example: - -**Async-graphql will automatically change the name of each item to GraphQL's CONSTANT_CASE convention. You can use `name` to rename.** - -```rust -# extern crate async_graphql; -use async_graphql::*; - -/// One of the films in the Star Wars Trilogy -#[derive(Enum, Copy, Clone, Eq, PartialEq)] -pub enum Episode { - /// Released in 1977. - NewHope, - - /// Released in 1980. - Empire, - - /// Released in 1983. - #[graphql(name="AAA")] - Jedi, -} -``` - -## Wrapping a remote enum - -Rust's [orphan rule](https://doc.rust-lang.org/book/traits.html#rules-for-implementing-traits) requires that either the -trait or the type for which you are implementing the trait must be defined in the same crate as the impl, so you cannot -expose remote enumeration types to GraphQL. In order to provide an `Enum` type, a common workaround is to create a new -enum that has parity with the existing, remote enum type. - -```rust -# extern crate async_graphql; -# mod remote_crate { pub enum RemoteEnum { A, B, C } } -use async_graphql::*; - -/// Provides parity with a remote enum type -#[derive(Enum, Copy, Clone, Eq, PartialEq)] -pub enum LocalEnum { - A, - B, - C, -} - -/// Conversion interface from remote type to our local GraphQL enum type -impl From<remote_crate::RemoteEnum> for LocalEnum { - fn from(e: remote_crate::RemoteEnum) -> Self { - match e { - remote_crate::RemoteEnum::A => Self::A, - remote_crate::RemoteEnum::B => Self::B, - remote_crate::RemoteEnum::C => Self::C, - } - } -} -``` - -The process is tedious and requires multiple steps to keep the local and remote enums in sync. `Async_graphql` provides a handy feature to generate the `From<remote_crate::RemoteEnum> for LocalEnum` as well as an opposite direction of `From<LocalEnum> for remote_crate::RemoteEnum` via an additional attribute after deriving `Enum`: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# mod remote_crate { pub enum RemoteEnum { A, B, C } } -#[derive(Enum, Copy, Clone, Eq, PartialEq)] -#[graphql(remote = "remote_crate::RemoteEnum")] -enum LocalEnum { - A, - B, - C, -} -``` diff --git a/docs/en/src/define_input_object.md b/docs/en/src/define_input_object.md deleted file mode 100644 index 2d1e7d558..000000000 --- a/docs/en/src/define_input_object.md +++ /dev/null @@ -1,158 +0,0 @@ -# InputObject - -You can use an `Object` as an argument, and GraphQL calls it an `InputObject`. - -The definition of `InputObject` is similar to [SimpleObject](define_simple_object.md), but -`SimpleObject` can only be used as output and `InputObject` can only be used as input. - -You can add optional `#[graphql]` attributes to add descriptions or rename the field. - -```rust -# extern crate async_graphql; -# #[derive(SimpleObject)] -# struct User { a: i32 } -use async_graphql::*; - -#[derive(InputObject)] -struct Coordinate { - latitude: f64, - longitude: f64 -} - -struct Mutation; - -#[Object] -impl Mutation { - async fn users_at_location(&self, coordinate: Coordinate, radius: f64) -> Vec<User> { - // Writes coordination to database. - // ... -# todo!() - } -} -``` - -## Generic `InputObject`s - -If you want to reuse an `InputObject` for other types, you can define a generic InputObject -and specify how its concrete types should be implemented. - -In the following example, two `InputObject` types are created: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(InputObject)] -# struct SomeType { a: i32 } -# #[derive(InputObject)] -# struct SomeOtherType { a: i32 } -#[derive(InputObject)] -#[graphql(concrete(name = "SomeName", params(SomeType)))] -#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))] -pub struct SomeGenericInput<T: InputType> { - field1: Option<T>, - field2: String -} -``` - -Note: Each generic parameter must implement `InputType`, as shown above. - -The schema generated is: - -```gql -input SomeName { - field1: SomeType - field2: String! -} - -input SomeOtherName { - field1: SomeOtherType - field2: String! -} -``` - -In your resolver method or field of another input object, use as a normal generic type: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(InputObject)] -# struct SomeType { a: i32 } -# #[derive(InputObject)] -# struct SomeOtherType { a: i32 } -# #[derive(InputObject)] -# #[graphql(concrete(name = "SomeName", params(SomeType)))] -# #[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))] -# pub struct SomeGenericInput<T: InputType> { -# field1: Option<T>, -# field2: String -# } -#[derive(InputObject)] -pub struct YetAnotherInput { - a: SomeGenericInput<SomeType>, - b: SomeGenericInput<SomeOtherType>, -} -``` - -You can pass multiple generic types to `params()`, separated by a comma. - -If you also want to implement `OutputType`, then you will need to explicitly declare the input and output type names of the concrete types like so: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(SimpleObject, InputObject)] -#[graphql(concrete( - name = "SomeGenericTypeOut", - input_name = "SomeGenericTypeIn", - params(i32), -))] -pub struct SomeGenericType<T: InputType + OutputType> { - field1: Option<T>, - field2: String -} -``` - -## Redacting sensitive data - -If any part of your input is considered sensitive and you wish to redact it, you can mark it with `secret` directive. For example: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(InputObject)] -pub struct CredentialsInput { - username: String, - #[graphql(secret)] - password: String, -} -``` - -## Flattening fields - -You can add `#[graphql(flatten)]` to a field to inline keys from the field type into it's parent. For example: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(InputObject)] -pub struct ChildInput { - b: String, - c: String, -} - -#[derive(InputObject)] -pub struct ParentInput { - a: String, - #[graphql(flatten)] - child: ChildInput, -} - -// Is the same as - -#[derive(InputObject)] -pub struct Input { - a: String, - b: String, - c: String, -} -``` diff --git a/docs/en/src/define_interface.md b/docs/en/src/define_interface.md deleted file mode 100644 index b6cb28eaf..000000000 --- a/docs/en/src/define_interface.md +++ /dev/null @@ -1,123 +0,0 @@ -# Interface - -`Interface` is used to abstract `Object`s with common fields. -`Async-graphql` implements it as a wrapper. -The wrapper will forward field resolution to the `Object` that implements this `Interface`. -Therefore, the `Object`'s fields' type and arguments must match with the `Interface`'s. - -`Async-graphql` implements auto conversion from `Object` to `Interface`, you only need to call `Into::into`. - -Interface field names are transformed to camelCase for the schema definition. -If you need e.g. a snake_cased GraphQL field name, you can use both the `name` and `method` attributes. - -- When `name` and `method` exist together, `name` is the GraphQL field name and the `method` is the resolver function name. -- When only `name` exists, `name.to_camel_case()` is the GraphQL field name and the `name` is the resolver function name. - -```rust -# extern crate async_graphql; -use async_graphql::*; - -struct Circle { - radius: f32, -} - -#[Object] -impl Circle { - async fn area(&self) -> f32 { - std::f32::consts::PI * self.radius * self.radius - } - - async fn scale(&self, s: f32) -> Shape { - Circle { radius: self.radius * s }.into() - } - - #[graphql(name = "short_description")] - async fn short_description(&self) -> String { - "Circle".to_string() - } -} - -struct Square { - width: f32, -} - -#[Object] -impl Square { - async fn area(&self) -> f32 { - self.width * self.width - } - - async fn scale(&self, s: f32) -> Shape { - Square { width: self.width * s }.into() - } - - #[graphql(name = "short_description")] - async fn short_description(&self) -> String { - "Square".to_string() - } -} - -#[derive(Interface)] -#[graphql( - field(name = "area", ty = "f32"), - field(name = "scale", ty = "Shape", arg(name = "s", ty = "f32")), - field(name = "short_description", method = "short_description", ty = "String") -)] -enum Shape { - Circle(Circle), - Square(Square), -} -``` - -## Register the interface manually - -`Async-graphql` traverses and registers all directly or indirectly referenced types from `Schema` in the initialization phase. -If an interface is not referenced, it will not exist in the registry, as in the following example , even if `MyObject` implements `MyInterface`, -because `MyInterface` is not referenced in `Schema`, the `MyInterface` type will not exist in the registry. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(Interface)] -#[graphql( - field(name = "name", ty = "String"), -)] -enum MyInterface { - MyObject(MyObject), -} - -#[derive(SimpleObject)] -struct MyObject { - name: String, -} - -struct Query; - -#[Object] -impl Query { - async fn obj(&self) -> MyObject { - todo!() - } -} - -type MySchema = Schema<Query, EmptyMutation, EmptySubscription>; -``` - -You need to manually register the `MyInterface` type when constructing the `Schema`: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(Interface)] -# #[graphql(field(name = "name", ty = "String"))] -# enum MyInterface { MyObject(MyObject) } -# #[derive(SimpleObject)] -# struct MyObject { name: String, } -# struct Query; -# #[Object] -# impl Query { async fn version(&self) -> &str { "1.0" } } - -Schema::build(Query, EmptyMutation, EmptySubscription) - .register_output_type::<MyInterface>() - .finish(); -``` diff --git a/docs/en/src/define_one_of_object.md b/docs/en/src/define_one_of_object.md deleted file mode 100644 index ea77fda7a..000000000 --- a/docs/en/src/define_one_of_object.md +++ /dev/null @@ -1,40 +0,0 @@ -# OneofObject - -A `OneofObject` is a special type of `InputObject`, in which only one of its fields must be set and is not-null. -It is especially useful when you want a user to be able to choose between several potential input types. - -This feature is still an [RFC](https://github.com/graphql/graphql-spec/pull/825) and therefore not yet officially part of the GraphQL spec, but `Async-graphql` already supports it! - -```rust -# extern crate async_graphql; -# #[derive(SimpleObject)] -# struct User { a: i32 } -use async_graphql::*; - -#[derive(OneofObject)] -enum UserBy { - Email(String), - RegistrationNumber(i64), - Address(Address) -} - -#[derive(InputObject)] -struct Address { - street: String, - house_number: String, - city: String, - zip: String, -} - -struct Query {} - -#[Object] -impl Query { - async fn search_users(&self, by: Vec<UserBy>) -> Vec<User> { - // ... Searches and returns a list of users ... -# todo!() - } -} -``` - -As you can see, a `OneofObject` is represented by an `enum` in which each variant contains another `InputType`. This means that you can use [`InputObject`](define_input_object.md) as variant too. diff --git a/docs/en/src/define_schema.md b/docs/en/src/define_schema.md deleted file mode 100644 index c7e767a28..000000000 --- a/docs/en/src/define_schema.md +++ /dev/null @@ -1,6 +0,0 @@ -# Schema - -After defining the basic types, you need to define a schema to combine them. The schema consists of three types: a query object, a mutation object, and a subscription object, where the mutation object and subscription object are optional. - -When the schema is created, `Async-graphql` will traverse all object graphs and register all types. This means that if a GraphQL object is defined but never referenced, this object will not be exposed in the schema. - diff --git a/docs/en/src/define_simple_object.md b/docs/en/src/define_simple_object.md deleted file mode 100644 index 3040dfb1a..000000000 --- a/docs/en/src/define_simple_object.md +++ /dev/null @@ -1,94 +0,0 @@ -# SimpleObject - -`SimpleObject` directly maps all the fields of a struct to GraphQL object. -If you don't require automatic mapping of fields, see [Object](define_complex_object.html). - -The example below defines an object `MyObject` which includes the fields `a` and `b`. `c` will be not mapped to GraphQL as it is labelled as `#[graphql(skip)]` - -```rust -# extern crate async_graphql; -use async_graphql::*; - -#[derive(SimpleObject)] -struct MyObject { - /// Value a - a: i32, - - /// Value b - b: i32, - - #[graphql(skip)] - c: i32, -} -``` - -## User-defined resolvers - -Sometimes most of the fields of a GraphQL object simply return the value of the structure member, but a few -fields are calculated. In this case, the [Object](define_complex_object.html) macro cannot be used unless you hand-write all the resolvers. - -The `ComplexObject` macro works in conjunction with the `SimpleObject` macro. The `SimpleObject` derive macro defines -the non-calculated fields, where as the `ComplexObject` macro let's you write user-defined resolvers for the calculated fields. - -Resolvers added to `ComplexObject` adhere to the same rules as resolvers of [Object](define_complex_object.html). - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(SimpleObject)] -#[graphql(complex)] // NOTE: If you want the `ComplexObject` macro to take effect, this `complex` attribute is required. -struct MyObj { - a: i32, - b: i32, -} - -#[ComplexObject] -impl MyObj { - async fn c(&self) -> i32 { - self.a + self.b - } -} -``` - -## Used for both input and output - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(SimpleObject, InputObject)] -#[graphql(input_name = "MyObjInput")] // Note: You must use the input_name attribute to define a new name for the input type, otherwise a runtime error will occur. -struct MyObj { - a: i32, - b: i32, -} -``` - -## Flatten fields - -You can flatten fields by adding `#[graphql(flatten)]`, i.e.: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(SimpleObject)] -pub struct ChildObject { - b: String, - c: String, -} - -#[derive(SimpleObject)] -pub struct ParentObject { - a: String, - #[graphql(flatten)] - child: ChildObject, -} - -// Is the same as - -#[derive(SimpleObject)] -pub struct Object { - a: String, - b: String, - c: String, -} -``` diff --git a/docs/en/src/define_union.md b/docs/en/src/define_union.md deleted file mode 100644 index 6d4bf5cda..000000000 --- a/docs/en/src/define_union.md +++ /dev/null @@ -1,109 +0,0 @@ -# Union - -The definition of a `Union` is similar to an `Interface`, **but with no fields allowed.**. -The implementation is quite similar for `Async-graphql`; from `Async-graphql`'s perspective, `Union` is a subset of `Interface`. - -The following example modified the definition of `Interface` a little bit and removed fields. - -```rust -# extern crate async_graphql; -use async_graphql::*; - -struct Circle { - radius: f32, -} - -#[Object] -impl Circle { - async fn area(&self) -> f32 { - std::f32::consts::PI * self.radius * self.radius - } - - async fn scale(&self, s: f32) -> Shape { - Circle { radius: self.radius * s }.into() - } -} - -struct Square { - width: f32, -} - -#[Object] -impl Square { - async fn area(&self) -> f32 { - self.width * self.width - } - - async fn scale(&self, s: f32) -> Shape { - Square { width: self.width * s }.into() - } -} - -#[derive(Union)] -enum Shape { - Circle(Circle), - Square(Square), -} -``` - -## Flattening nested unions - -A restriction in GraphQL is the inability to create a union type out of -other union types. All members must be `Object`. To support nested -unions, we can "flatten" members that are unions, bringing their members up -into the parent union. This is done by applying `#[graphql(flatten)]` on each -member we want to flatten. - -```rust -# extern crate async_graphql; -#[derive(async_graphql::Union)] -pub enum TopLevelUnion { - A(A), - - // Will fail to compile unless we flatten the union member - #[graphql(flatten)] - B(B), -} - -#[derive(async_graphql::SimpleObject)] -pub struct A { - a: i32, - // ... -} - -#[derive(async_graphql::Union)] -pub enum B { - C(C), - D(D), -} - -#[derive(async_graphql::SimpleObject)] -pub struct C { - c: i32, - // ... -} - -#[derive(async_graphql::SimpleObject)] -pub struct D { - d: i32, - // ... -} -``` - -The above example transforms the top-level union into this equivalent: - -```rust -# extern crate async_graphql; -# #[derive(async_graphql::SimpleObject)] -# struct A { a: i32 } -# #[derive(async_graphql::SimpleObject)] -# struct C { c: i32 } -# #[derive(async_graphql::SimpleObject)] -# struct D { d: i32 } -#[derive(async_graphql::Union)] -pub enum TopLevelUnion { - A(A), - C(C), - D(D), -} -``` diff --git a/docs/en/src/depth_and_complexity.md b/docs/en/src/depth_and_complexity.md deleted file mode 100644 index 7e852706c..000000000 --- a/docs/en/src/depth_and_complexity.md +++ /dev/null @@ -1,128 +0,0 @@ -# Query complexity and depth - -⚠️GraphQL provides a powerful way to query your data, but putting great -power in the hands of your API clients also exposes you to a risk of denial -of service attacks. You can mitigate that risk with `Async-graphql` by limiting the -complexity and depth of the queries you allow. - -## Expensive Queries - -Consider a schema that allows listing blog posts. Each blog post is also related to other posts. - -```graphql -type Query { - posts(count: Int = 10): [Post!]! -} - -type Post { - title: String! - text: String! - related(count: Int = 10): [Post!]! -} -``` - -It's not too hard to craft a query that will cause a very large response: - -```graphql -{ - posts(count: 100) { - related(count: 100) { - related(count: 100) { - related(count: 100) { - title - } - } - } - } -} -``` - -The size of the response increases exponentially with every other level of the `related` field. Fortunately, `Async-graphql` provides -a way to prevent such queries. - -## Limiting Query depth - -The depth is the number of nesting levels of the field, and the following is a query with a depth of `3`. - -```graphql -{ - a { - b { - c - } - } -} -``` - -You can limit the depth when creating `Schema`. If the query exceeds this limit, an error will occur and the -message `Query is nested too deep` will be returned. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# struct Query; -# #[Object] -# impl Query { async fn version(&self) -> &str { "1.0" } } -let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .limit_depth(5) // Limit the maximum depth to 5 - .finish(); -``` - -## Limiting Query complexity - -The complexity is the number of fields in the query. The default complexity of each field is `1`. Below is a -query with a complexity of `6`. - -```graphql -{ - a b c { - d { - e f - } - } -} -``` - -You can limit the complexity when creating the `Schema`. If the query exceeds this limit, an error will occur -and `Query is too complex` will be returned. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# struct Query; -# #[Object] -# impl Query { async fn version(&self) -> &str { "1.0" } } -let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .limit_complexity(5) // Limit the maximum complexity to 5 - .finish(); -``` - -## Custom Complexity Calculation - -There are two ways to customize the complexity for non-list type and list type fields. - -In the following code, the complexity of the `value` field is `5`. The complexity of the `values` field is `count * child_complexity`, -`child_complexity` is a special variable that represents the complexity of the subquery, and `count` is the parameter of the field, -used to calculate the complexity of the `values` field, and the type of the return value must be `usize`. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -struct Query; - -#[Object] -impl Query { - #[graphql(complexity = 5)] - async fn value(&self) -> i32 { - todo!() - } - - #[graphql(complexity = "count * child_complexity")] - async fn values(&self, count: usize) -> i32 { - todo!() - } -} -``` - -**Note: The complexity calculation is done in the validation phase and not the execution phase, -so you don't have to worry about partial execution of over-limit queries.** diff --git a/docs/en/src/derived_fields.md b/docs/en/src/derived_fields.md deleted file mode 100644 index e3364a05f..000000000 --- a/docs/en/src/derived_fields.md +++ /dev/null @@ -1,105 +0,0 @@ -# Derived fields - -Sometimes two fields have the same query logic, but the output type is different. In `async-graphql`, you can create a derived field for it. - -In the following example, you already have a `date_rfc2822` field outputting the time format in `RFC2822` format, and then reuse it to derive a new `date_rfc3339` field. - -```rust -# extern crate chrono; -# use chrono::Utc; -# extern crate async_graphql; -# use async_graphql::*; -struct DateRFC3339(chrono::DateTime<Utc>); -struct DateRFC2822(chrono::DateTime<Utc>); - -#[Scalar] -impl ScalarType for DateRFC3339 { - fn parse(value: Value) -> InputValueResult<Self> { todo!() } - - fn to_value(&self) -> Value { - Value::String(self.0.to_rfc3339()) - } -} - -#[Scalar] -impl ScalarType for DateRFC2822 { - fn parse(value: Value) -> InputValueResult<Self> { todo!() } - - fn to_value(&self) -> Value { - Value::String(self.0.to_rfc2822()) - } -} - -impl From<DateRFC2822> for DateRFC3339 { - fn from(value: DateRFC2822) -> Self { - DateRFC3339(value.0) - } -} - -struct Query; - -#[Object] -impl Query { - #[graphql(derived(name = "date_rfc3339", into = "DateRFC3339"))] - async fn date_rfc2822(&self, arg: String) -> DateRFC2822 { - todo!() - } -} -``` - -It will render a GraphQL like: - -```graphql -type Query { - date_rfc2822(arg: String): DateRFC2822! - date_rfc3339(arg: String): DateRFC3339! -} -``` - -## Wrapper types - -A derived field won't be able to manage everything easily: Rust's [orphan rule](https://doc.rust-lang.org/book/traits.html#rules-for-implementing-traits) requires that either the -trait or the type for which you are implementing the trait must be defined in the same crate as the impl, so the following code cannot be compiled: - -```rust,ignore -impl From<Vec<U>> for Vec<T> { - ... -} -``` - -So you wouldn't be able to generate derived fields for existing wrapper type structures like `Vec` or `Option`. But when you implement a `From<U> for T` you should be able to derived a `From<Vec<U>> for Vec<T>` and a `From<Option<U>> for Option<T>`. -We included a `with` parameter to help you define a function to call instead of using the `Into` trait implementation between wrapper structures. - - -### Example - -```rust -# extern crate serde; -# use serde::{Serialize, Deserialize}; -# extern crate async_graphql; -# use async_graphql::*; -#[derive(Serialize, Deserialize, Clone)] -struct ValueDerived(String); - -#[derive(Serialize, Deserialize, Clone)] -struct ValueDerived2(String); - -scalar!(ValueDerived); -scalar!(ValueDerived2); - -impl From<ValueDerived> for ValueDerived2 { - fn from(value: ValueDerived) -> Self { - ValueDerived2(value.0) - } -} - -fn option_to_option<T, U: From<T>>(value: Option<T>) -> Option<U> { - value.map(|x| x.into()) -} - -#[derive(SimpleObject)] -struct TestObj { - #[graphql(derived(owned, name = "value2", into = "Option<ValueDerived2>", with = "option_to_option"))] - pub value1: Option<ValueDerived>, -} -``` diff --git a/docs/en/src/error_extensions.md b/docs/en/src/error_extensions.md deleted file mode 100644 index 2f823793d..000000000 --- a/docs/en/src/error_extensions.md +++ /dev/null @@ -1,230 +0,0 @@ -# Error extensions -To quote the [graphql-spec](https://spec.graphql.org/June2018/#example-fce18): -> GraphQL services may provide an additional entry to errors with key extensions. -> This entry, if set, must have a map as its value. This entry is reserved for implementer to add -> additional information to errors however they see fit, and there are no additional restrictions on -> its contents. - -## Example -I would recommend on checking out this [async-graphql example](https://github.com/async-graphql/examples/blob/master/actix-web/error-extensions/src/main.rs) as a quickstart. - -## General Concept -In `async-graphql` all user-facing errors are cast to the `Error` type which by default provides -the error message exposed by `std::fmt::Display`. However, `Error` actually provides an additional information that can extend the error. - -A resolver looks like this: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# struct Query; -# #[Object] -# impl Query { -async fn parse_with_extensions(&self) -> Result<i32, Error> { - Err(Error::new("MyMessage").extend_with(|_, e| e.set("details", "CAN_NOT_FETCH"))) -} -# } -``` - -may then return a response like this: - -```json -{ - "errors": [ - { - "message": "MyMessage", - "locations": [ ... ], - "path": [ ... ], - "extensions": { - "details": "CAN_NOT_FETCH", - } - } - ] -} -``` - - -## ErrorExtensions -Constructing new `Error`s by hand quickly becomes tedious. That is why `async-graphql` provides -two convenience traits for casting your errors to the appropriate `Error` with -extensions. - -The easiest way to provide extensions to any error is by calling `extend_with` on the error. -This will on the fly convert any error into a `Error` with the given extension. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# struct Query; -use std::num::ParseIntError; -# #[Object] -# impl Query { -async fn parse_with_extensions(&self) -> Result<i32> { - Ok("234a" - .parse() - .map_err(|err: ParseIntError| err.extend_with(|_err, e| e.set("code", 404)))?) -} -# } -``` - -### Implementing ErrorExtensions for custom errors. -If you find yourself attaching extensions to your errors all over the place you might want to consider -implementing the trait on your custom error type directly. - -```rust -# extern crate async_graphql; -# extern crate thiserror; -# use async_graphql::*; -#[derive(Debug, thiserror::Error)] -pub enum MyError { - #[error("Could not find resource")] - NotFound, - - #[error("ServerError")] - ServerError(String), - - #[error("No Extensions")] - ErrorWithoutExtensions, -} - -impl ErrorExtensions for MyError { - // lets define our base extensions - fn extend(&self) -> Error { - Error::new(format!("{}", self)).extend_with(|err, e| - match self { - MyError::NotFound => e.set("code", "NOT_FOUND"), - MyError::ServerError(reason) => e.set("reason", reason.clone()), - MyError::ErrorWithoutExtensions => {} - }) - } -} -``` - -This way you only need to call `extend` on your error to deliver the error message alongside the provided extensions. -Or further extend your error through `extend_with`. - -```rust -# extern crate async_graphql; -# extern crate thiserror; -# use async_graphql::*; -# #[derive(Debug, thiserror::Error)] -# pub enum MyError { -# #[error("Could not find resource")] -# NotFound, -# -# #[error("ServerError")] -# ServerError(String), -# -# #[error("No Extensions")] -# ErrorWithoutExtensions, -# } -# struct Query; -# #[Object] -# impl Query { -async fn parse_with_extensions_result(&self) -> Result<i32> { - // Err(MyError::NotFound.extend()) - // OR - Err(MyError::NotFound.extend_with(|_, e| e.set("on_the_fly", "some_more_info"))) -} -# } -``` - -```json -{ - "errors": [ - { - "message": "NotFound", - "locations": [ ... ], - "path": [ ... ], - "extensions": { - "code": "NOT_FOUND", - "on_the_fly": "some_more_info" - } - } - ] -} -``` - -## ResultExt -This trait enables you to call `extend_err` directly on results. So the above code becomes less verbose. - -```rust,ignore -# // @todo figure out why this example does not compile! -# extern crate async_graphql; -use async_graphql::*; -# struct Query; -# #[Object] -# impl Query { -async fn parse_with_extensions(&self) -> Result<i32> { - Ok("234a" - .parse() - .extend_err(|_, e| e.set("code", 404))?) -} -# } -``` -### Chained extensions -Since `ErrorExtensions` and `ResultExt` are implemented for any type `&E where E: std::fmt::Display` -we can chain the extension together. - - -```rust -# extern crate async_graphql; -use async_graphql::*; -# struct Query; -# #[Object] -# impl Query { -async fn parse_with_extensions(&self) -> Result<i32> { - match "234a".parse() { - Ok(n) => Ok(n), - Err(e) => Err(e - .extend_with(|_, e| e.set("code", 404)) - .extend_with(|_, e| e.set("details", "some more info..")) - // keys may also overwrite previous keys... - .extend_with(|_, e| e.set("code", 500))), - } -} -# } -``` -Expected response: - -```json -{ - "errors": [ - { - "message": "MyMessage", - "locations": [ ... ], - "path": [ ... ], - "extensions": { - "details": "some more info...", - "code": 500, - } - } - ] -} -``` - -### Pitfalls -Rust does not provide stable trait specialization yet. -That is why `ErrorExtensions` is actually implemented for `&E where E: std::fmt::Display` -instead of `E: std::fmt::Display`. Some specialization is provided through -[Autoref-based stable specialization](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md). -The disadvantage is that the below code does **NOT** compile: - -```rust,ignore,does_not_compile -async fn parse_with_extensions_result(&self) -> Result<i32> { - // the trait `error::ErrorExtensions` is not implemented - // for `std::num::ParseIntError` - "234a".parse().extend_err(|_, e| e.set("code", 404)) -} -``` - -however this does: - -```rust,ignore,does_not_compile -async fn parse_with_extensions_result(&self) -> Result<i32> { - // does work because ErrorExtensions is implemented for &ParseIntError - "234a" - .parse() - .map_err(|ref e: ParseIntError| e.extend_with(|_, e| e.set("code", 404))) -} -``` diff --git a/docs/en/src/error_handling.md b/docs/en/src/error_handling.md deleted file mode 100644 index 2b08cd3ab..000000000 --- a/docs/en/src/error_handling.md +++ /dev/null @@ -1,38 +0,0 @@ -# Error handling - -Resolve can return a `Result`, which has the following definition: - -```rust,ignore -type Result<T> = std::result::Result<T, Error>; -``` - -Any `Error` that implements `std::fmt::Display` can be converted to `Error` and you can extend the error message. - -The following example shows how to parse an input string to an integer. When parsing fails, it will return an error and attach an error message. -See the [Error Extensions](error_extensions.md) section of this book for more details. - -```rust -# extern crate async_graphql; -# use std::num::ParseIntError; -use async_graphql::*; - -struct Query; - -#[Object] -impl Query { - async fn parse_with_extensions(&self, input: String) -> Result<i32> { - Ok("234a" - .parse() - .map_err(|err: ParseIntError| err.extend_with(|_, e| e.set("code", 400)))?) - } -} -``` - -#### Errors in subscriptions - -Errors can be returned from subscription resolvers as well, using a return type of the form: -```rust,ignore -async fn my_subscription_resolver(&self) -> impl Stream<Item = Result<MyItem, MyError>> { ... } -``` - -Note however that the `MyError` struct must have `Clone` implemented, due to the restrictions placed by the `Subscription` macro. One way to accomplish this is by creating a custom error type, with `#[derive(Clone)]`, as [seen here](https://github.com/async-graphql/async-graphql/issues/845#issuecomment-1090933464). diff --git a/docs/en/src/extensions.md b/docs/en/src/extensions.md deleted file mode 100644 index 6e4453288..000000000 --- a/docs/en/src/extensions.md +++ /dev/null @@ -1,3 +0,0 @@ -# Extensions - -`async-graphql` has the capability to be extended with extensions without having to modify the original source code. A lot of features can be added this way, and a lot of extensions already exist. diff --git a/docs/en/src/extensions_available.md b/docs/en/src/extensions_available.md deleted file mode 100644 index ce077f738..000000000 --- a/docs/en/src/extensions_available.md +++ /dev/null @@ -1,54 +0,0 @@ -# Extensions available - -There are a lot of available extensions in the `async-graphql` to empower your GraphQL Server, some of these documentations are documented here. - -## Analyzer -*Available in the repository* - -The `analyzer` extension will output a field containing `complexity` and `depth` in the response extension field of each query. - - -## Apollo Persisted Queries -*Available in the repository* - -To improve network performance for large queries, you can enable this Persisted Queries extension. With this extension enabled, each unique query is associated to a unique identifier, so clients can send this identifier instead of the corresponding query string to reduce requests sizes. - -This extension doesn't force you to use some cache strategy, you can choose the caching strategy you want, you'll just have to implement the `CacheStorage` trait: -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[async_trait::async_trait] -pub trait CacheStorage: Send + Sync + Clone + 'static { - /// Load the query by `key`. - async fn get(&self, key: String) -> Option<String>; - /// Save the query by `key`. - async fn set(&self, key: String, query: String); -} -``` - -References: [Apollo doc - Persisted Queries](https://www.apollographql.com/docs/react/api/link/persisted-queries/) - -## Apollo Tracing -*Available in the repository* - -Apollo Tracing is an extension which includes analytics data for your queries. This extension works to follow the old and now deprecated [Apollo Tracing Spec](https://github.com/apollographql/apollo-tracing). If you want to check the newer Apollo Reporting Protocol, it's implemented by [async-graphql Apollo studio extension](https://github.com/async-graphql/async_graphql_apollo_studio_extension) for Apollo Studio. - -## Apollo Studio -*Available at [async-graphql/async_graphql_apollo_studio_extension](https://github.com/async-graphql/async_graphql_apollo_studio_extension)* - -Apollo Studio is a cloud platform that helps you build, validate, and secure your organization's graph (description from the official documentation). It's a service allowing you to monitor & work with your team around your GraphQL Schema. `async-graphql` provides an extension implementing the official [Apollo Specification](https://www.apollographql.com/docs/studio/setup-analytics/#third-party-support) available at [async-graphql-extension-apollo-tracing](https://github.com/async-graphql/async_graphql_apollo_studio_extension) and [Crates.io](https://crates.io/crates/async-graphql-extension-apollo-tracing). - -## Logger -*Available in the repository* - -Logger is a simple extension allowing you to add some logging feature to `async-graphql`. It's also a good example to learn how to create your own extension. - -## OpenTelemetry -*Available in the repository* - -OpenTelemetry is an extension providing an integration with the [opentelemetry crate](https://crates.io/crates/opentelemetry) to allow your application to capture distributed traces and metrics from `async-graphql`. - -## Tracing -*Available in the repository* - -Tracing is a simple extension allowing you to add some tracing feature to `async-graphql`. A little like the `Logger` extension. diff --git a/docs/en/src/extensions_inner_working.md b/docs/en/src/extensions_inner_working.md deleted file mode 100644 index 33d06616b..000000000 --- a/docs/en/src/extensions_inner_working.md +++ /dev/null @@ -1,221 +0,0 @@ -# How extensions are defined - -An `async-graphql` extension is defined by implementing the trait `Extension` associated. The `Extension` trait allows you to insert custom code to some several steps used to respond to GraphQL's queries through `async-graphql`. With `Extensions`, your application can hook into the GraphQL's requests lifecycle to add behaviors about incoming requests or outgoing response. - -`Extensions` are a lot like middleware from other frameworks, be careful when using those: when you use an extension **it'll be run for every GraphQL request**. - -Across every step, you'll have the `ExtensionContext` supplied with data about your current request execution. Feel free to check how it's constructed in the code, documentation about it will soon come. - -## A word about middleware - -For those who don't know, let's dig deeper into what is a middleware: - -```rust,ignore -async fn middleware(&self, ctx: &ExtensionContext<'_>, next: NextMiddleware<'_>) -> MiddlewareResult { - // Logic to your middleware. - - /* - * Final step to your middleware, we call the next function which will trigger - * the execution of the next middleware. It's like a `callback` in JavaScript. - */ - next.run(ctx).await -} -``` - -As you have seen, a `Middleware` is only a function calling the next function at the end, but we could also do a middleware with the `next.run` function at the start. This is where it's becoming tricky: depending on where you put your logic and where is the `next.run` call, your logic won't have the same execution order. - - -Depending on your logic code, you'll want to process it before or after the `next.run` call. If you need more information about middlewares, there are a lot of things on the web. - -## Processing of a query - -There are several steps to go to process a query to completion, you'll be able to create extension based on these hooks. - -### request - -First, when we receive a request, if it's not a subscription, the first function to be called will be `request`, it's the first step, it's the function called at the incoming request, and it's also the function which will output the response to the user. - -Default implementation for `request`: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# use async_graphql::extensions::*; -# struct MyMiddleware; -# #[async_trait::async_trait] -# impl Extension for MyMiddleware { -async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response { - next.run(ctx).await -} -# } -``` - -Depending on where you put your logic code, it'll be executed at the beginning or at the end of the query being processed. - - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# use async_graphql::extensions::*; -# struct MyMiddleware; -# #[async_trait::async_trait] -# impl Extension for MyMiddleware { -async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response { - // The code here will be run before the prepare_request is executed. - let result = next.run(ctx).await; - // The code after the completion of this future will be after the processing, just before sending the result to the user. - result -} -# } -``` - -### prepare_request - -Just after the `request`, we will have the `prepare_request` lifecycle, which will be hooked. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# use async_graphql::*; -# use async_graphql::extensions::*; -# struct MyMiddleware; -# #[async_trait::async_trait] -# impl Extension for MyMiddleware { -async fn prepare_request( - &self, - ctx: &ExtensionContext<'_>, - request: Request, - next: NextPrepareRequest<'_>, -) -> ServerResult<Request> { - // The code here will be run before the prepare_request is executed, just after the request lifecycle hook. - let result = next.run(ctx, request).await; - // The code here will be run just after the prepare_request - result -} -# } -``` - -### parse_query - -The `parse_query` will create a GraphQL `ExecutableDocument` on your query, it'll check if the query is valid for the GraphQL Spec. Usually the implemented spec in `async-graphql` tends to be the last stable one (October2021). - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# use async_graphql::extensions::*; -# use async_graphql::parser::types::ExecutableDocument; -# struct MyMiddleware; -# #[async_trait::async_trait] -# impl Extension for MyMiddleware { -/// Called at parse query. -async fn parse_query( - &self, - ctx: &ExtensionContext<'_>, - // The raw query - query: &str, - // The variables - variables: &Variables, - next: NextParseQuery<'_>, -) -> ServerResult<ExecutableDocument> { - next.run(ctx, query, variables).await -} -# } -``` - -### validation - -The `validation` step will check (depending on your `validation_mode`) rules the query should abide to and give the client data about why the query is not valid. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# use async_graphql::extensions::*; -# struct MyMiddleware; -# #[async_trait::async_trait] -# impl Extension for MyMiddleware { -/// Called at validation query. -async fn validation( - &self, - ctx: &ExtensionContext<'_>, - next: NextValidation<'_>, -) -> Result<ValidationResult, Vec<ServerError>> { - next.run(ctx).await -} -# } -``` - -### execute - -The `execution` step is a huge one, it'll start the execution of the query by calling each resolver concurrently for a `Query` and serially for a `Mutation`. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# use async_graphql::extensions::*; -# struct MyMiddleware; -# #[async_trait::async_trait] -# impl Extension for MyMiddleware { -/// Called at execute query. -async fn execute( - &self, - ctx: &ExtensionContext<'_>, - operation_name: Option<&str>, - next: NextExecute<'_>, -) -> Response { - // Before starting resolving the whole query - let result = next.run(ctx, operation_name).await; - // After resolving the whole query - result -} -# } -```` - -### resolve - -The `resolve` step is launched for each field. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# use async_graphql::extensions::*; -# struct MyMiddleware; -# #[async_trait::async_trait] -# impl Extension for MyMiddleware { -/// Called at resolve field. -async fn resolve( - &self, - ctx: &ExtensionContext<'_>, - info: ResolveInfo<'_>, - next: NextResolve<'_>, -) -> ServerResult<Option<Value>> { - // Logic before resolving the field - let result = next.run(ctx, info).await; - // Logic after resolving the field - result -} -# } -``` - -### subscribe - -The `subscribe` lifecycle has the same behavior as the `request` but for a `Subscription`. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# use async_graphql::extensions::*; -# use futures_util::stream::BoxStream; -# struct MyMiddleware; -# #[async_trait::async_trait] -# impl Extension for MyMiddleware { -/// Called at subscribe request. -fn subscribe<'s>( - &self, - ctx: &ExtensionContext<'_>, - stream: BoxStream<'s, Response>, - next: NextSubscribe<'_>, -) -> BoxStream<'s, Response> { - next.run(ctx, stream) -} -# } -``` diff --git a/docs/en/src/field_guard.md b/docs/en/src/field_guard.md deleted file mode 100644 index 38f671ba8..000000000 --- a/docs/en/src/field_guard.md +++ /dev/null @@ -1,93 +0,0 @@ -# Field Guard - -You can define a `guard` for the fields of `Object`, `SimpleObject`, `ComplexObject` and `Subscription`, it will be executed before calling the resolver function, and an error will be returned if it fails. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(Eq, PartialEq, Copy, Clone)] -enum Role { - Admin, - Guest, -} - -struct RoleGuard { - role: Role, -} - -impl RoleGuard { - fn new(role: Role) -> Self { - Self { role } - } -} - -impl Guard for RoleGuard { - async fn check(&self, ctx: &Context<'_>) -> Result<()> { - if ctx.data_opt::<Role>() == Some(&self.role) { - Ok(()) - } else { - Err("Forbidden".into()) - } - } -} -``` - -Use it with the `guard` attribute: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(Eq, PartialEq, Copy, Clone)] -# enum Role { Admin, Guest, } -# struct RoleGuard { role: Role, } -# impl RoleGuard { fn new(role: Role) -> Self { Self { role } } } -# impl Guard for RoleGuard { async fn check(&self, ctx: &Context<'_>) -> Result<()> { todo!() } } -#[derive(SimpleObject)] -struct Query { - /// Only allow Admin - #[graphql(guard = "RoleGuard::new(Role::Admin)")] - value1: i32, - /// Allow Admin or Guest - #[graphql(guard = "RoleGuard::new(Role::Admin).or(RoleGuard::new(Role::Guest))")] - value2: i32, -} -``` - -## Use parameter value - -Sometimes guards need to use field parameters, you need to pass the parameter value when creating the guard like this: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -struct EqGuard { - expect: i32, - actual: i32, -} - -impl EqGuard { - fn new(expect: i32, actual: i32) -> Self { - Self { expect, actual } - } -} - -impl Guard for EqGuard { - async fn check(&self, _ctx: &Context<'_>) -> Result<()> { - if self.expect != self.actual { - Err("Forbidden".into()) - } else { - Ok(()) - } - } -} - -struct Query; - -#[Object] -impl Query { - #[graphql(guard = "EqGuard::new(100, value)")] - async fn get(&self, value: i32) -> i32 { - value - } -} -``` diff --git a/docs/en/src/generic.md b/docs/en/src/generic.md deleted file mode 100644 index e2dcf5451..000000000 --- a/docs/en/src/generic.md +++ /dev/null @@ -1,105 +0,0 @@ -# Generics - -It is possible to define reusable objects using generics; however -each concrete instantiation of a generic object must be given a unique GraphQL type name. -There are two ways of specifying these concrete names: concrete instantiation and the `TypeName` trait. - -## Concrete Instantiation - -In the following example, two `SimpleObject` types are created: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(SimpleObject)] -# struct SomeType { a: i32 } -# #[derive(SimpleObject)] -# struct SomeOtherType { a: i32 } -#[derive(SimpleObject)] -#[graphql(concrete(name = "SomeName", params(SomeType)))] -#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))] -pub struct SomeGenericObject<T: OutputType> { - field1: Option<T>, - field2: String -} -``` - -Note: Each generic parameter must implement `OutputType`, as shown above. - -The schema generated is: - -```gql -# SomeGenericObject<SomeType> -type SomeName { - field1: SomeType - field2: String! -} - -# SomeGenericObject<SomeOtherType> -type SomeOtherName { - field1: SomeOtherType - field2: String! -} -``` - -In your resolver method or field of another object, use as a normal generic type: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(SimpleObject)] -# struct SomeType { a: i32 } -# #[derive(SimpleObject)] -# struct SomeOtherType { a: i32 } -# #[derive(SimpleObject)] -# #[graphql(concrete(name = "SomeName", params(SomeType)))] -# #[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))] -# pub struct SomeGenericObject<T: OutputType> { -# field1: Option<T>, -# field2: String, -# } -#[derive(SimpleObject)] -pub struct YetAnotherObject { - a: SomeGenericObject<SomeType>, - b: SomeGenericObject<SomeOtherType>, -} -``` - -You can pass multiple generic types to `params()`, separated by a comma. - -## `TypeName` trait - -Some type names can be derived. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# use std::borrow::Cow; -#[derive(SimpleObject)] -#[graphql(name_type)] // Use `TypeName` trait -struct Bag<T: OutputType> { - content: Vec<T>, - len: usize, -} - -impl<T: OutputType> TypeName for Bag<T> { - fn type_name() -> Cow<'static, str> { - format!("{}Bag", <T as OutputType>::type_name()).into() - } -} -``` - -Using `bool` and `String` the generated schema is: -```gql -# Bag<bool> -type BooleanBag { - content: [Boolean!]! - len: Int! -} - -# Bag<String> -type StringBag { - content: [String!]! - len: Int! -} -``` \ No newline at end of file diff --git a/docs/en/src/input_value_validators.md b/docs/en/src/input_value_validators.md deleted file mode 100644 index 9b0499eb3..000000000 --- a/docs/en/src/input_value_validators.md +++ /dev/null @@ -1,90 +0,0 @@ -# Input value validators - -`Async-graphql` has some common validators built-in, you can use them on the parameters of object fields or on the fields of `InputObject`. - -- **maximum=N** the number cannot be greater than `N`. -- **minimum=N** the number cannot be less than `N`. -- **multiple_of=N** the number must be a multiple of `N`. -- **max_items=N** the length of the list cannot be greater than `N`. -- **min_items=N** the length of the list cannot be less than `N`. -- **max_length=N** the length of the string cannot be greater than `N`. -- **min_length=N** the length of the string cannot be less than `N`. -- **chars_max_length=N** the count of the unicode chars cannot be greater than `N`. -- **chars_min_length=N** the count of the unicode chars cannot be less than `N`. -- **email** is valid email. -- **url** is valid url. -- **ip** is valid ip address. -- **regex=RE** is match for the regex. -- **uuid=V** the string or ID is a valid UUID with version `V`. You may omit `V` to accept any UUID version. - -```rust -# extern crate async_graphql; -use async_graphql::*; - -struct Query; - -#[Object] -impl Query { - /// The length of the name must be greater than or equal to 5 and less than or equal to 10. - async fn input(&self, #[graphql(validator(min_length = 5, max_length = 10))] name: String) -> Result<i32> { -# todo!() - } -} -``` - -## Check every member of the list - -You can enable the `list` attribute, and the validator will check all members in list: - -```rust -# extern crate async_graphql; -use async_graphql::*; - -struct Query; - -#[Object] -impl Query { - async fn input(&self, #[graphql(validator(list, max_length = 10))] names: Vec<String>) -> Result<i32> { -# todo!() - } -} -``` - -## Custom validator - -```rust -# extern crate async_graphql; -# use async_graphql::*; -struct MyValidator { - expect: i32, -} - -impl MyValidator { - pub fn new(n: i32) -> Self { - MyValidator { expect: n } - } -} - -impl CustomValidator<i32> for MyValidator { - fn check(&self, value: &i32) -> Result<(), InputValueError<i32>> { - if *value == self.expect { - Ok(()) - } else { - Err(InputValueError::custom(format!("expect 100, actual {}", value))) - } - } -} - -struct Query; - -#[Object] -impl Query { - /// n must be equal to 100 - async fn value( - &self, - #[graphql(validator(custom = "MyValidator::new(100)"))] n: i32, - ) -> i32 { - n - } -} -``` diff --git a/docs/en/src/integrations.md b/docs/en/src/integrations.md deleted file mode 100644 index 308c25c3f..000000000 --- a/docs/en/src/integrations.md +++ /dev/null @@ -1,11 +0,0 @@ -# Integrations - -`Async-graphql` supports several common Rust web servers. - -- Poem [async-graphql-poem](https://crates.io/crates/async-graphql-poem) -- Actix-web [async-graphql-actix-web](https://crates.io/crates/async-graphql-actix-web) -- Warp [async-graphql-warp](https://crates.io/crates/async-graphql-warp) -- Axum [async-graphql-axum](https://crates.io/crates/async-graphql-axum) -- Rocket [async-graphql-rocket](https://crates.io/crates/async-graphql-rocket) - -**Even if the server you are currently using is not in the above list, it is quite simple to implement similar functionality yourself.** diff --git a/docs/en/src/integrations_to_actix_web.md b/docs/en/src/integrations_to_actix_web.md deleted file mode 100644 index bd56518f8..000000000 --- a/docs/en/src/integrations_to_actix_web.md +++ /dev/null @@ -1,49 +0,0 @@ -# Actix-web - -## Request example - -When you define your `actix_web::App` you need to pass in the Schema as data. - -```rust -# extern crate async_graphql_actix_web; -# extern crate async_graphql; -# extern crate actix_web; -# use async_graphql::*; -# #[derive(Default,SimpleObject)] -# struct Query { a: i32 } -# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish(); -use actix_web::{web, HttpRequest, HttpResponse}; -use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse}; -async fn index( - // Schema now accessible here - schema: web::Data<Schema<Query, EmptyMutation, EmptySubscription>>, - request: GraphQLRequest, -) -> web::Json<GraphQLResponse> { - web::Json(schema.execute(request.into_inner()).await.into()) -} -``` - -## Subscription example - -```rust -# extern crate async_graphql_actix_web; -# extern crate async_graphql; -# extern crate actix_web; -# use async_graphql::*; -# #[derive(Default,SimpleObject)] -# struct Query { a: i32 } -# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish(); -use actix_web::{web, HttpRequest, HttpResponse}; -use async_graphql_actix_web::GraphQLSubscription; -async fn index_ws( - schema: web::Data<Schema<Query, EmptyMutation, EmptySubscription>>, - req: HttpRequest, - payload: web::Payload, -) -> actix_web::Result<HttpResponse> { - GraphQLSubscription::new(Schema::clone(&*schema)).start(&req, payload) -} -``` - -## More examples - -[https://github.com/async-graphql/examples/tree/master/actix-web](https://github.com/async-graphql/examples/tree/master/actix-web) diff --git a/docs/en/src/integrations_to_poem.md b/docs/en/src/integrations_to_poem.md deleted file mode 100644 index 741edbe92..000000000 --- a/docs/en/src/integrations_to_poem.md +++ /dev/null @@ -1,39 +0,0 @@ -# Poem - -## Request example - -```rust -# extern crate async_graphql_poem; -# extern crate async_graphql; -# extern crate poem; -# use async_graphql::*; -# #[derive(Default, SimpleObject)] -# struct Query { a: i32 } -# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish(); -use poem::Route; -use async_graphql_poem::GraphQL; - -let app = Route::new() - .at("/ws", GraphQL::new(schema)); -``` - -## Subscription example - -```rust -# extern crate async_graphql_poem; -# extern crate async_graphql; -# extern crate poem; -# use async_graphql::*; -# #[derive(Default, SimpleObject)] -# struct Query { a: i32 } -# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish(); -use poem::{get, Route}; -use async_graphql_poem::GraphQLSubscription; - -let app = Route::new() - .at("/ws", get(GraphQLSubscription::new(schema))); -``` - -## More examples - -[https://github.com/async-graphql/examples/tree/master/poem](https://github.com/async-graphql/examples/tree/master/poem) diff --git a/docs/en/src/integrations_to_warp.md b/docs/en/src/integrations_to_warp.md deleted file mode 100644 index 62f7af557..000000000 --- a/docs/en/src/integrations_to_warp.md +++ /dev/null @@ -1,66 +0,0 @@ -# Warp - -For `Async-graphql-warp`, two `Filter` integrations are provided: `graphql` and `graphql_subscription`. - -The `graphql` filter is used for execution `Query` and `Mutation` requests. It extracts GraphQL request and outputs `async_graphql::Schema` and `async_graphql::Request`. -You can combine other filters later, or directly call `Schema::execute` to execute the query. - -`graphql_subscription` is used to implement WebSocket subscriptions. It outputs `warp::Reply`. - -## Request example - -```rust -# extern crate async_graphql_warp; -# extern crate async_graphql; -# extern crate warp; -# use async_graphql::*; -# use std::convert::Infallible; -# use warp::Filter; -# struct QueryRoot; -# #[Object] -# impl QueryRoot { async fn version(&self) -> &str { "1.0" } } -# async fn other() { -type MySchema = Schema<QueryRoot, EmptyMutation, EmptySubscription>; - -let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription); -let filter = async_graphql_warp::graphql(schema).and_then(|(schema, request): (MySchema, async_graphql::Request)| async move { - // Execute query - let resp = schema.execute(request).await; - - // Return result - Ok::<_, Infallible>(async_graphql_warp::GraphQLResponse::from(resp)) -}); -warp::serve(filter).run(([0, 0, 0, 0], 8000)).await; -# } -``` - -## Subscription example - -```rust -# extern crate async_graphql_warp; -# extern crate async_graphql; -# extern crate warp; -# use async_graphql::*; -# use futures_util::stream::{Stream, StreamExt}; -# use std::convert::Infallible; -# use warp::Filter; -# struct SubscriptionRoot; -# #[Subscription] -# impl SubscriptionRoot { -# async fn tick(&self) -> impl Stream<Item = i32> { -# futures_util::stream::iter(0..10) -# } -# } -# struct QueryRoot; -# #[Object] -# impl QueryRoot { async fn version(&self) -> &str { "1.0" } } -# async fn other() { -let schema = Schema::new(QueryRoot, EmptyMutation, SubscriptionRoot); -let filter = async_graphql_warp::graphql_subscription(schema); -warp::serve(filter).run(([0, 0, 0, 0], 8000)).await; -# } -``` - -## More examples - -[https://github.com/async-graphql/examples/tree/master/warp](https://github.com/async-graphql/examples/tree/master/warp) diff --git a/docs/en/src/introduction.md b/docs/en/src/introduction.md deleted file mode 100644 index 7d6707c86..000000000 --- a/docs/en/src/introduction.md +++ /dev/null @@ -1,20 +0,0 @@ -# Introduction - -`Async-graphql` is a GraphQL server-side library implemented in Rust. It is fully compatible with the GraphQL specification and most of its extensions, and offers type safety and high performance. - -You can define a Schema in Rust and procedural macros will automatically generate code for a GraphQL query. This library does not extend Rust's syntax, which means that Rustfmt can be used normally. I value this highly and it is one of the reasons why I developed `Async-graphql`. - -## Why do this? - -I like GraphQL and Rust. I've been using `Juniper`, which solves the problem of implementing a GraphQL server with Rust. But Juniper had several problems, the most important of which is that it didn't support async/await at the time. So I decided to make this library for myself. - -## Benchmarks - -Ensure that there is no CPU-heavy process in background! - -```shell script -cd benchmark -cargo bench -``` - -Now a HTML report is available at `benchmark/target/criterion/report`. diff --git a/docs/en/src/merging_objects.md b/docs/en/src/merging_objects.md deleted file mode 100644 index 9cb3f641b..000000000 --- a/docs/en/src/merging_objects.md +++ /dev/null @@ -1,108 +0,0 @@ -# Merging Objects - -Usually we can create multiple implementations for the same type in Rust, but due to the limitation of procedural macros, we can not create multiple Object implementations for the same type. For example, the following code will fail to compile. - -```rust,ignore,does_not_compile -#[Object] -impl Query { - async fn users(&self) -> Vec<User> { - todo!() - } -} - -#[Object] -impl Query { - async fn movies(&self) -> Vec<Movie> { - todo!() - } -} -``` - -Instead, the `#[derive(MergedObject)]` macro allows you to split an object's resolvers across multiple modules or files by merging 2 or more `#[Object]` implementations into one. - -**Tip:** Every `#[Object]` needs a unique name, even in a `MergedObject`, so make sure to give each object you're merging its own name. - -**Note:** This works for queries and mutations. For subscriptions, see "Merging Subscriptions" below. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(SimpleObject)] -# struct User { a: i32 } -# #[derive(SimpleObject)] -# struct Movie { a: i32 } -#[derive(Default)] -struct UserQuery; - -#[Object] -impl UserQuery { - async fn users(&self) -> Vec<User> { - todo!() - } -} - -#[derive(Default)] -struct MovieQuery; - -#[Object] -impl MovieQuery { - async fn movies(&self) -> Vec<Movie> { - todo!() - } -} - -#[derive(MergedObject, Default)] -struct Query(UserQuery, MovieQuery); - -let schema = Schema::new( - Query::default(), - EmptyMutation, - EmptySubscription -); -``` - -> ⚠️ **MergedObject cannot be used in Interface.** - -# Merging Subscriptions - -Along with `MergedObject`, you can derive `MergedSubscription` or use `#[MergedSubscription]` to merge separate `#[Subscription]` blocks. - -Like merging Objects, each subscription block requires a unique name. - -Example: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# use futures_util::stream::{Stream}; -# #[derive(Default,SimpleObject)] -# struct Query { a: i32 } -#[derive(Default)] -struct Subscription1; - -#[Subscription] -impl Subscription1 { - async fn events1(&self) -> impl Stream<Item = i32> { - futures_util::stream::iter(0..10) - } -} - -#[derive(Default)] -struct Subscription2; - -#[Subscription] -impl Subscription2 { - async fn events2(&self) -> impl Stream<Item = i32> { - futures_util::stream::iter(10..20) - } -} - -#[derive(MergedSubscription, Default)] -struct Subscription(Subscription1, Subscription2); - -let schema = Schema::new( - Query::default(), - EmptyMutation, - Subscription::default() -); -``` diff --git a/docs/en/src/query_and_mutation.md b/docs/en/src/query_and_mutation.md deleted file mode 100644 index 79d422222..000000000 --- a/docs/en/src/query_and_mutation.md +++ /dev/null @@ -1,49 +0,0 @@ -# Query and Mutation - -## Query root object - -The query root object is a GraphQL object with a definition similar to other objects. Resolver functions for all fields of the query object are executed concurrently. - -```rust -# extern crate async_graphql; -use async_graphql::*; -# #[derive(SimpleObject)] -# struct User { a: i32 } - -struct Query; - -#[Object] -impl Query { - async fn user(&self, username: String) -> Result<Option<User>> { - // Look up users from the database -# todo!() - } -} - -``` - -## Mutation root object - -The mutation root object is also a GraphQL object, but it executes sequentially. One mutation following from another will only be executed only after the first mutation is completed. - -The following mutation root object provides an example of user registration and login: - -```rust -# extern crate async_graphql; -use async_graphql::*; - -struct Mutation; - -#[Object] -impl Mutation { - async fn signup(&self, username: String, password: String) -> Result<bool> { - // User signup -# todo!() - } - - async fn login(&self, username: String, password: String) -> Result<String> { - // User login (generate token) -# todo!() - } -} -``` diff --git a/docs/en/src/quickstart.md b/docs/en/src/quickstart.md deleted file mode 100644 index 5a29e0fe3..000000000 --- a/docs/en/src/quickstart.md +++ /dev/null @@ -1,69 +0,0 @@ -# Quickstart - -## Add dependency libraries - -```toml -[dependencies] -async-graphql = "4.0" -async-graphql-actix-web = "4.0" # If you need to integrate into actix-web -async-graphql-warp = "4.0" # If you need to integrate into warp -async-graphql-tide = "4.0" # If you need to integrate into tide -``` - -## Write a Schema - -The Schema of a GraphQL contains a required Query, an optional Mutation, and an optional Subscription. These object types are described using the structure of the Rust language. The field of the structure corresponds to the field of the GraphQL object. - -`Async-graphql` implements the mapping of common data types to GraphQL types, such as `i32`, `f64`, `Option<T>`, `Vec<T>`, etc. Also, you can [extend these base types](custom_scalars.md), which are called scalars in the GraphQL. - -Here is a simple example where we provide just one query that returns the sum of `a` and `b`. - -```rust -# extern crate async_graphql; -use async_graphql::*; - -struct Query; - -#[Object] -impl Query { - /// Returns the sum of a and b - async fn add(&self, a: i32, b: i32) -> i32 { - a + b - } -} -``` - -## Execute the query - -In our example, there is only a Query without a Mutation or Subscription, so we create the Schema with `EmptyMutation` and `EmptySubscription`, and then call `Schema::execute` to execute the Query. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# -# struct Query; -# #[Object] -# impl Query { -# async fn version(&self) -> &str { "1.0" } -# } -# async fn other() { -let schema = Schema::new(Query, EmptyMutation, EmptySubscription); -let res = schema.execute("{ add(a: 10, b: 20) }").await; -# } -``` - -## Output the query results as JSON - -```rust,ignore -let json = serde_json::to_string(&res); -``` - -## Web server integration -All examples are in the [sub-repository](https://github.com/async-graphql/examples), located in the examples directory. - -```shell -git submodule update # update the examples repo -cd examples && cargo run --bin [name] -``` - -For more information, see the [sub-repository](https://github.com/async-graphql/examples) README.md. \ No newline at end of file diff --git a/docs/en/src/sdl_export.md b/docs/en/src/sdl_export.md deleted file mode 100644 index fc088ba70..000000000 --- a/docs/en/src/sdl_export.md +++ /dev/null @@ -1,23 +0,0 @@ -# SDL Export - -You can export your schema in Schema Definition Language (SDL) by using the `Schema::sdl()` method. - - -```rust -# extern crate async_graphql; -use async_graphql::*; - -struct Query; - -#[Object] -impl Query { - async fn add(&self, u: i32, v: i32) -> i32 { - u + v - } -} - -let schema = Schema::build(Query, EmptyMutation, EmptySubscription).finish(); - -// Print the schema in SDL format -println!("{}", &schema.sdl()); -``` diff --git a/docs/en/src/subscription.md b/docs/en/src/subscription.md deleted file mode 100644 index b87b83a71..000000000 --- a/docs/en/src/subscription.md +++ /dev/null @@ -1,29 +0,0 @@ -# Subscription - -The definition of the subscription root object is slightly different from other root objects. Its resolver function always returns a [Stream](https://docs.rs/futures-core/~0.3/futures_core/stream/trait.Stream.html) or `Result<Stream>`, and the field parameters are usually used as data filtering conditions. - -The following example subscribes to an integer stream, which generates one integer per second. The parameter `step` specifies the integer step size with a default of 1. - -```rust -# extern crate async_graphql; -# use std::time::Duration; -# use async_graphql::futures_util::stream::Stream; -# use async_graphql::futures_util::StreamExt; -# extern crate tokio_stream; -# extern crate tokio; -use async_graphql::*; - -struct Subscription; - -#[Subscription] -impl Subscription { - async fn integers(&self, #[graphql(default = 1)] step: i32) -> impl Stream<Item = i32> { - let mut value = 0; - tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(Duration::from_secs(1))) - .map(move |_| { - value += step; - value - }) - } -} -``` diff --git a/docs/en/src/typesystem.md b/docs/en/src/typesystem.md deleted file mode 100644 index 6ba9ae875..000000000 --- a/docs/en/src/typesystem.md +++ /dev/null @@ -1,3 +0,0 @@ -# Type System - -`Async-graphql` implements conversions from GraphQL Objects to Rust structs, and it's easy to use. diff --git a/docs/en/src/utilities.md b/docs/en/src/utilities.md deleted file mode 100644 index 72672597f..000000000 --- a/docs/en/src/utilities.md +++ /dev/null @@ -1 +0,0 @@ -# Utilities diff --git a/docs/en/src/visibility.md b/docs/en/src/visibility.md deleted file mode 100644 index 476dbc1ce..000000000 --- a/docs/en/src/visibility.md +++ /dev/null @@ -1,45 +0,0 @@ -# Hide content in introspection - -By default, all types and fields are visible in introspection. But maybe you want to hide some content according to different users to avoid unnecessary misunderstandings. You can add the `visible` attribute to the type or field to do it. - -```rust -# extern crate async_graphql; -use async_graphql::*; - -#[derive(SimpleObject)] -struct MyObj { - // This field will be visible in introspection. - a: i32, - - // This field is always hidden in introspection. - #[graphql(visible = false)] - b: i32, - - // This field calls the `is_admin` function, which - // is visible if the return value is `true`. - #[graphql(visible = "is_admin")] - c: i32, -} - -#[derive(Enum, Copy, Clone, Eq, PartialEq)] -enum MyEnum { - // This item will be visible in introspection. - A, - - // This item is always hidden in introspection. - #[graphql(visible = false)] - B, - - // This item calls the `is_admin` function, which - // is visible if the return value is `true`. - #[graphql(visible = "is_admin")] - C, -} - -struct IsAdmin(bool); - -fn is_admin(ctx: &Context<'_>) -> bool { - ctx.data_unchecked::<IsAdmin>().0 -} - -``` diff --git a/docs/zh-CN/book.toml b/docs/zh-CN/book.toml deleted file mode 100644 index 61920e0b6..000000000 --- a/docs/zh-CN/book.toml +++ /dev/null @@ -1,9 +0,0 @@ -[book] -authors = ["sunli"] -description = "Async-graphql使用手册" -src = "src" -language = "zh-CN" -title = "Async-graphql教程" - -[rust] -edition = "2024" diff --git a/docs/zh-CN/src/SUMMARY.md b/docs/zh-CN/src/SUMMARY.md deleted file mode 100644 index a37d3ab68..000000000 --- a/docs/zh-CN/src/SUMMARY.md +++ /dev/null @@ -1,40 +0,0 @@ -# Async-graphql 教程 - -- [介绍](introduction.md) -- [快速开始](quickstart.md) -- [类型系统](typesystem.md) - - [简单对象 (SimpleObject)](define_simple_object.md) - - [对象 (Object)](define_complex_object.md) - - [查询上下文 (Context)](context.md) - - [错误处理](error_handling.md) - - [合并对象 (MergedObject)](merging_objects.md) - - [派生字段](derived_fields.md) - - [枚举 (Enum)](define_enum.md) - - [接口 (Interface)](define_interface.md) - - [联合 (Union)](define_union.md) - - [输入对象 (InputObject)](define_input_object.md) - - [默认值](default_value.md) -- [定义模式 (Schema)](define_schema.md) - - [查询和变更](query_and_mutation.md) - - [订阅](subscription.md) -- [实用功能](utilities.md) - - [字段守卫](field_guard.md) - - [输入值校验器](input_value_validators.md) - - [查询缓存控制](cache_control.md) - - [游标连接](cursor_connections.md) - - [错误扩展](error_extensions.md) - - [Apollo Tracing 支持](apollo_tracing.md) - - [查询的深度和复杂度](depth_and_complexity.md) - - [在内省中隐藏内容](visibility.md) -- [扩展](extensions.md) - - [扩展如何工作](extensions_inner_working.md) - - [可用的扩展列表](extensions_available.md) -- [集成到 WebServer](integrations.md) - - [Poem](integrations_to_poem.md) - - [Warp](integrations_to_warp.md) - - [Actix-web](integrations_to_actix_web.md) -- [高级主题](advanced_topics.md) - - [自定义标量](custom_scalars.md) - - [优化查询(解决 N+1 问题)](dataloader.md) - - [自定义指令](custom_directive.md) - - [Apollo Federation 集成](apollo_federation.md) diff --git a/docs/zh-CN/src/advanced_topics.md b/docs/zh-CN/src/advanced_topics.md deleted file mode 100644 index f51cc3c02..000000000 --- a/docs/zh-CN/src/advanced_topics.md +++ /dev/null @@ -1 +0,0 @@ -# 高级主题 diff --git a/docs/zh-CN/src/apollo_federation.md b/docs/zh-CN/src/apollo_federation.md deleted file mode 100644 index edf7e7292..000000000 --- a/docs/zh-CN/src/apollo_federation.md +++ /dev/null @@ -1,85 +0,0 @@ -# Apollo Federation 集成 - -`Apollo Federation`是一个`GraphQL`网关,它可以组合多个 GraphQL 服务,允许每服务仅实现它负责的那一部分数据,参考[官方文档](https://www.apollographql.com/docs/apollo-server/federation/introduction)。 - -`Async-graphql`可以完全支持`Apollo Federation`的所有功能,但需要对`Schema`定义做一些小小的改造。 - -- `async_graphql::Object`和`async_graphql::Interface`的`extends`属性声明这个类别是一个已有类型的扩充。 - -- 字段的`external`属性声明这个字段定义来自其它服务。 - -- 字段的`provides`属性用于要求网关提供的字段集。 - -- 字段的`requires`属性表示解析该字段值需要依赖该类型的字段集。 - -## 实体查找函数 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(SimpleObject)] -# struct User { id: ID } -struct Query; - -#[Object] -impl Query { - #[graphql(entity)] - async fn find_user_by_id(&self, id: ID) -> User { - User { id } - } - - #[graphql(entity)] - async fn find_user_by_id_with_username(&self, #[graphql(key)] id: ID, username: String) -> User { - User { id } - } - - #[graphql(entity)] - async fn find_user_by_id_and_username(&self, id: ID, username: String) -> User { - User { id } - } -} -``` - -**注意这三个查找函数的不同,他们都是查找 User 对象。** - -- find_user_by_id - - 使用`id`查找`User`对象,`User`对象的 key 是`id`。 - -- find_user_by_id_with_username - - 使用`id`查找`User`对象,`User`对象的 key 是`id`,并且请求`User`对象的`username`字段值。 - -- find_user_by_id_and_username - - 使用`id`和`username`查找`User`对象,`User`对象的 key 是`id`和`username`。 - -完整的例子请参考 https://github.com/async-graphql/examples/tree/master/federation - -## 定义复合主键 - -一个主键可以包含多个字段,什么包含嵌套字段,你可以用`InputObject`来实现一个嵌套字段的 Key 类型。 - -下面的例子中`User`对象的主键是`key { a b }`。 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(SimpleObject)] -# struct User { id: i32 } -#[derive(InputObject)] -struct NestedKey { - a: i32, - b: i32, -} - -struct Query; - -#[Object] -impl Query { - #[graphql(entity)] - async fn find_user_by_key(&self, key: NestedKey) -> User { - User { id: key.a } - } -} -``` diff --git a/docs/zh-CN/src/apollo_tracing.md b/docs/zh-CN/src/apollo_tracing.md deleted file mode 100644 index a86cc2a8f..000000000 --- a/docs/zh-CN/src/apollo_tracing.md +++ /dev/null @@ -1,20 +0,0 @@ -# Apollo Tracing 支持 - -`Apollo Tracing`提供了查询每个步骤的性能分析结果,它是一个`Schema`扩展,性能分析结果保存在`QueryResponse`中。 - -启用`Apollo Tracing`扩展需要在创建`Schema`的时候添加该扩展。 - -```rust -# extern crate async_graphql; -use async_graphql::*; -use async_graphql::extensions::ApolloTracing; - -# struct Query; -# #[Object] -# impl Query { async fn version(&self) -> &str { "1.0" } } - -let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .extension(ApolloTracing) // 启用 ApolloTracing 扩展 - .finish(); - -``` diff --git a/docs/zh-CN/src/cache_control.md b/docs/zh-CN/src/cache_control.md deleted file mode 100644 index f75b5013a..000000000 --- a/docs/zh-CN/src/cache_control.md +++ /dev/null @@ -1,54 +0,0 @@ -# 查询缓存控制 - -生产环境下通常依赖缓存来提高性能。 - -一个 GraphQL 查询会调用多个 Resolver 函数,每个 Resolver 函数都能够具有不同的缓存定义。有的可能缓存几秒钟,有的可能缓存几个小时,有的可能所有用户都相同,有的可能每个会话都不同。 - -`Async-graphql`提供一种机制允许定义结果的缓存时间和作用域。 - -你可以在**对象**上定义缓存参数,也可以在**字段**上定义,下面的例子展示了缓存控制参数的两种用法。 - -你可以用`max_age`参数来控制缓存时长(单位是秒),也可以用`public`和`private`来控制缓存的作用域,当你不指定时,作用域默认是`public`。 - -`Async-graphql`查询时会合并所有缓存控制指令的结果,`max_age`取最小值。如果任何对象或者字段的作用域为`private`,则其结果的作用域为`private`,否则为`public`。 - -我们可以从查询结果`QueryResponse`中获取缓存控制合并结果,并且调用`CacheControl::value`来获取对应的 HTTP 头。 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# struct Query; -#[Object(cache_control(max_age = 60))] -impl Query { - #[graphql(cache_control(max_age = 30))] - async fn value1(&self) -> i32 { - 1 - } - - #[graphql(cache_control(private))] - async fn value2(&self) -> i32 { - 2 - } - - async fn value3(&self) -> i32 { - 3 - } -} -``` - -下面是不同的查询对应不同缓存控制结果: - -```graphql -# max_age=30 -{ value1 } -``` - -```graphql -# max_age=30, private -{ value1 value2 } -``` - -```graphql -# max_age=60 -{ value3 } -``` diff --git a/docs/zh-CN/src/context.md b/docs/zh-CN/src/context.md deleted file mode 100644 index bb3226cc1..000000000 --- a/docs/zh-CN/src/context.md +++ /dev/null @@ -1,158 +0,0 @@ -# 查询上下文 (Context) - -`Context`的主要目标是获取附加到`Schema`的全局数据或者与正在处理的实际查询相关的数据。 - -## 存储数据 - -在`Context`中你可以存放全局数据,例如环境变量、数据库连接池,以及你在每个查询中可能需要的任何内容。 - -数据必须实现`Send`和`Sync`。 - -你可以通过调用`ctx.data::<TypeOfYourData>()`来获取查询中的数据。 - -**主意:如果 Resolver 函数的返回值是从`Context`中借用的,则需要明确说明参数的生命周期。** - -下面的例子展示了如何从`Context`中借用数据。 - -```rust -# extern crate async_graphql; -use async_graphql::*; - -struct Query; - -#[Object] -impl Query { - async fn borrow_from_context_data<'ctx>( - &self, - ctx: &Context<'ctx> - ) -> Result<&'ctx String> { - ctx.data::<String>() - } -} -``` - -### Schema 数据 - -你可以在创建`Schema`时将数据放入上下文中,这对于不会更改的数据非常有用,例如连接池。 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(Default,SimpleObject)] -# struct Query { version: i32} -# struct EnvStruct; -# let env_struct = EnvStruct; -# struct S3Object; -# let s3_storage = S3Object; -# struct DBConnection; -# let db_core = DBConnection; -let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription) - .data(env_struct) - .data(s3_storage) - .data(db_core) - .finish(); -``` - -### 请求数据 - -你可以在执行请求时将数据放入上下文中,它对于身份验证数据很有用。 - -一个使用`warp`的小例子: - -```rust -# extern crate async_graphql; -# extern crate async_graphql_warp; -# extern crate warp; -# use async_graphql::*; -# use warp::{Filter, Reply}; -# use std::convert::Infallible; -# #[derive(Default, SimpleObject)] -# struct Query { name: String } -# struct AuthInfo { pub token: Option<String> } -# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish(); -# let schema_filter = async_graphql_warp::graphql(schema); -let graphql_post = warp::post() - .and(warp::path("graphql")) - .and(warp::header::optional("Authorization")) - .and(schema_filter) - .and_then( |auth: Option<String>, (schema, mut request): (Schema<Query, EmptyMutation, EmptySubscription>, async_graphql::Request)| async move { - // Do something to get auth data from the header - let your_auth_data = AuthInfo { token: auth }; - let response = schema - .execute( - request - .data(your_auth_data) - ).await; - - Ok::<_, Infallible>(async_graphql_warp::GraphQLResponse::from(response)) - }); -``` - -## HTTP 头 - -使用`Context`你还可以插入或添加 HTTP 头。 - -```rust -# extern crate async_graphql; -# extern crate http; -# use ::http::header::ACCESS_CONTROL_ALLOW_ORIGIN; -# use async_graphql::*; -# struct Query; -#[Object] -impl Query { - async fn greet(&self, ctx: &Context<'_>) -> String { - // Headers can be inserted using the `http` constants - let was_in_headers = ctx.insert_http_header(ACCESS_CONTROL_ALLOW_ORIGIN, "*"); - - // They can also be inserted using &str - let was_in_headers = ctx.insert_http_header("Custom-Header", "1234"); - - // If multiple headers with the same key are `inserted` then the most recent - // one overwrites the previous. If you want multiple headers for the same key, use - // `append_http_header` for subsequent headers - let was_in_headers = ctx.append_http_header("Custom-Header", "Hello World"); - - String::from("Hello world") - } -} -``` - -## Selection / LookAhead - -有时你想知道子查询中请求了哪些字段用于优化数据处理,则可以使用`ctx.field()`读取查询中的字段,它将提供一个`SelectionField`,允许你在当前字段和子字段之间导航。 - -如果要跨查询或子查询执行搜索,则不必使用 `SelectionField` 手动执行此操作,可以使用 `ctx.look_ahead()` 来执行选择。 - -```rust -# extern crate async_graphql; -use async_graphql::*; - -#[derive(SimpleObject)] -struct Detail { - c: i32, - d: i32, -} - -#[derive(SimpleObject)] -struct MyObj { - a: i32, - b: i32, - detail: Detail, -} - -struct Query; - -#[Object] -impl Query { - async fn obj(&self, ctx: &Context<'_>) -> MyObj { - if ctx.look_ahead().field("a").exists() { - // This is a query like `obj { a }` - } else if ctx.look_ahead().field("detail").field("c").exists() { - // This is a query like `obj { detail { c } }` - } else { - // This query doesn't have `a` - } - unimplemented!() - } -} -``` diff --git a/docs/zh-CN/src/cursor_connections.md b/docs/zh-CN/src/cursor_connections.md deleted file mode 100644 index 2dc02f577..000000000 --- a/docs/zh-CN/src/cursor_connections.md +++ /dev/null @@ -1,47 +0,0 @@ -# 游标连接 (Cursor Connections) - -Relay 定义了一套游标连接规范,以提供一致性的分页查询方式,具体的规范文档请参考[GraphQL Cursor Connections Specification](https://facebook.github.io/relay/graphql/connections.htm)。 - -在`Async-graphql`中定义一个游标连接非常简单,你只需要调用 connection::query 函数,并在闭包中查询数据。 - -下面是一个简单的获取连续整数的数据源: - -```rust -# extern crate async_graphql; -use async_graphql::*; -use async_graphql::types::connection::*; - -struct Query; - -#[Object] -impl Query { - async fn numbers(&self, - after: Option<String>, - before: Option<String>, - first: Option<i32>, - last: Option<i32>, - ) -> Result<Connection<usize, i32, EmptyFields, EmptyFields>> { - query(after, before, first, last, |after, before, first, last| async move { - let mut start = after.map(|after| after + 1).unwrap_or(0); - let mut end = before.unwrap_or(10000); - if let Some(first) = first { - end = (start + first).min(end); - } - if let Some(last) = last { - start = if last > end - start { - end - } else { - end - last - }; - } - let mut connection = Connection::new(start > 0, end < 10000); - connection.edges.extend( - (start..end).into_iter().map(|n| - Edge::with_additional_fields(n, n as i32, EmptyFields) - )); - Ok::<_, async_graphql::Error>(connection) - }).await - } -} - -``` diff --git a/docs/zh-CN/src/custom_directive.md b/docs/zh-CN/src/custom_directive.md deleted file mode 100644 index aeca7ce4f..000000000 --- a/docs/zh-CN/src/custom_directive.md +++ /dev/null @@ -1,52 +0,0 @@ -# 自定义指令 - -`Async-graphql`可以很方便的自定义指令,这可以扩展 GraphQL 的行为。 - -创建一个自定义指令,需要实现 `CustomDirective` trait,然后用`Directive`宏生成一个工厂函数,该函数接收指令的参数并返回指令的实例。 - -目前`Async-graphql`仅支持添加`FIELD`位置的指令。 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -struct ConcatDirective { - value: String, -} - -#[async_trait::async_trait] -impl CustomDirective for ConcatDirective { - async fn resolve_field(&self, _ctx: &Context<'_>, resolve: ResolveFut<'_>) -> ServerResult<Option<Value>> { - resolve.await.map(|value| { - value.map(|value| match value { - Value::String(str) => Value::String(str + &self.value), - _ => value, - }) - }) - } -} - -#[Directive(location = "Field")] -fn concat(value: String) -> impl CustomDirective { - ConcatDirective { value } -} -``` - -创建模式时注册指令: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# struct Query; -# #[Object] -# impl Query { async fn version(&self) -> &str { "1.0" } } -# struct ConcatDirective { value: String, } -# #[async_trait::async_trait] -# impl CustomDirective for ConcatDirective { -# async fn resolve_field(&self, _ctx: &Context<'_>, resolve: ResolveFut<'_>) -> ServerResult<Option<Value>> { todo!() } -# } -# #[Directive(location = "Field")] -# fn concat(value: String) -> impl CustomDirective { ConcatDirective { value } } -let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .directive(concat) - .finish(); -``` diff --git a/docs/zh-CN/src/custom_scalars.md b/docs/zh-CN/src/custom_scalars.md deleted file mode 100644 index 33b1f3122..000000000 --- a/docs/zh-CN/src/custom_scalars.md +++ /dev/null @@ -1,58 +0,0 @@ -# 自定义标量 - -`Async-graphql`已经内置了绝大部分常用的标量类型,同时你也能自定义标量。 - -实现`Async-graphql::Scalar`即可自定义一个标量,你只需要实现一个解析函数和输出函数。 - -下面的例子定义一个 64 位整数标量,但它的输入输出都是字符串。 (`Async-graphql`已经内置了对 64 位整数的支持,正是采用字符串作为输入输出) - -```rust -# extern crate async_graphql; -use async_graphql::*; - - -struct StringNumber(i64); - -#[Scalar] -impl ScalarType for StringNumber { - fn parse(value: Value) -> InputValueResult<Self> { - if let Value::String(value) = &value { - // 解析整数 - Ok(value.parse().map(StringNumber)?) - } else { - // 类型不匹配 - Err(InputValueError::expected_type(value)) - } - } - - fn to_value(&self) -> Value { - Value::String(self.0.to_string()) - } -} - -``` - -## 使用`scalar!`宏定义标量 - -如果你的类型实现了`serde :: Serialize`和`serde :: Deserialize`,那么可以使用此宏更简单地定义标量。 - -```rust -# extern crate async_graphql; -# extern crate serde; -# use async_graphql::*; -# use serde::{Serialize, Deserialize}; -# use std::collections::HashMap; -#[derive(Serialize, Deserialize)] -struct MyValue { - a: i32, - b: HashMap<String, i32>, -} - -scalar!(MyValue); - -// 重命名为 `MV`. -// scalar!(MyValue, "MV"); - -// 重命名为 `MV` 并且添加描述。 -// scalar!(MyValue, "MV", "This is my value"); -``` diff --git a/docs/zh-CN/src/dataloader.md b/docs/zh-CN/src/dataloader.md deleted file mode 100644 index bce373552..000000000 --- a/docs/zh-CN/src/dataloader.md +++ /dev/null @@ -1,148 +0,0 @@ -# 优化查询(解决 N+1 问题) - -您是否注意到某些 GraphQL 查询需要执行数百个数据库查询,这些查询通常包含重复的数据,让我们来看看为什么以及如何修复它。 - -## 查询解析 - -想象一下,如果您有一个简单的查询,例如: - -```graphql -query { todos { users { name } } } -``` - -实现`User`的 resolver 代码如下: - -```rust,ignore -struct User { - id: u64, -} - -#[Object] -impl User { - async fn name(&self, ctx: &Context<'_>) -> Result<String> { - let pool = ctx.data_unchecked::<Pool<Postgres>>(); - let (name,): (String,) = sqlx::query_as("SELECT name FROM user WHERE id = $1") - .bind(self.id) - .fetch_one(pool) - .await?; - Ok(name) - } -} -``` - -执行查询将调用`Todos`的 resolver,该 resolver 执行`SELECT * FROM todo`并返回 N 个`Todo`对象。然后对每个`Todo`对象同时调用`User`的 -resolver 执行`SELECT name FROM user where id = $1`。 - -例如: - -```sql -SELECT id, todo, user_id FROM todo -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -SELECT name FROM user WHERE id = $1 -``` - -执行了多次`SELECT name FROM user WHERE id = $1`,并且,大多数`Todo`对象都属于同一个用户,我们需要优化这些代码! - -## Dataloader - -我们需要对查询分组,并且排除重复的查询。`Dataloader`就能完成这个工作,[facebook](https://github.com/facebook/dataloader) 给出了一个请求范围的批处理和缓存解决方案。 - -下面是使用`DataLoader`来优化查询请求的例子: - -```rust,ignore -use async_graphql::*; -use async_graphql::dataloader::*; -use itertools::Itertools; -use std::sync::Arc; - -struct UserNameLoader { - pool: sqlx::Pool<Postgres>, -} - -impl Loader<u64> for UserNameLoader { - type Value = String; - type Error = Arc<sqlx::Error>; - - async fn load(&self, keys: &[u64]) -> Result<HashMap<u64, Self::Value>, Self::Error> { - let query = format!("SELECT name FROM user WHERE id IN ({})", keys.iter().join(",")); - Ok(sqlx::query_as(query) - .fetch(&self.pool) - .map_ok(|name: String| name) - .map_err(Arc::new) - .try_collect().await?) - } -} - -struct User { - id: u64, -} - -#[Object] -impl User { - async fn name(&self, ctx: &Context<'_>) -> Result<String> { - let loader = ctx.data_unchecked::<DataLoader<UserNameLoader>>(); - let name: Option<String> = loader.load_one(self.id).await?; - name.ok_or_else(|| "Not found".into()) - } -} -``` - -要在 `ctx` 中获取 `UserNameLoader`,您必须将其和任务生成器(例如 `async_std::task::spawn`)注册到 `Schema` 中: - -```rust,ignore -let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription) - .data(DataLoader::new( - UserNameLoader, - async_std::task::spawn, // 或者 `tokio::spawn` - )) - .finish(); -``` - -最终只需要两个查询语句,就查询出了我们想要的结果! - -```sql -SELECT id, todo, user_id FROM todo -SELECT name FROM user WHERE id IN (1, 2, 3, 4) -``` - -## 同一个 Loader 支持多种数据类型 - -你可以为同一个`Loader`实现多种数据类型,就像下面这样: - -```rust,ignore -struct PostgresLoader { - pool: sqlx::Pool<Postgres>, -} - -impl Loader<UserId> for PostgresLoader { - type Value = User; - type Error = Arc<sqlx::Error>; - - async fn load(&self, keys: &[UserId]) -> Result<HashMap<UserId, Self::Value>, Self::Error> { - // 从数据库中加载 User - } -} - -impl Loader<TodoId> for PostgresLoader { - type Value = Todo; - type Error = sqlx::Error; - - async fn load(&self, keys: &[TodoId]) -> Result<HashMap<TodoId, Self::Value>, Self::Error> { - // 从数据库中加载 Todo - } -} -``` diff --git a/docs/zh-CN/src/default_value.md b/docs/zh-CN/src/default_value.md deleted file mode 100644 index a080b9a2d..000000000 --- a/docs/zh-CN/src/default_value.md +++ /dev/null @@ -1,73 +0,0 @@ -# 默认值 - -你可以为输入值类型定义默认值,下面展示了在不同类型上默认值的定义方法。 - -## 对象字段参数 - -```rust -# extern crate async_graphql; -use async_graphql::*; - -struct Query; - -fn my_default() -> i32 { - 30 -} - -#[Object] -impl Query { - // value 参数的默认值为 0,它会调用 i32::default() - async fn test1(&self, #[graphql(default)] value: i32) -> i32 { todo!() } - - // value 参数的默认值为 10 - async fn test2(&self, #[graphql(default = 10)] value: i32) -> i32 { todo!() } - - // value 参数的默认值使用 my_default 函数的返回结果,值为 30 - async fn test3(&self, #[graphql(default_with = "my_default()")] value: i32) -> i32 { todo!() } -} -``` - -## 接口字段参数 - -```rust -# extern crate async_graphql; -# fn my_default() -> i32 { 5 } -# struct MyObj; -# #[Object] -# impl MyObj { -# async fn test1(&self, value: i32) -> i32 { todo!() } -# async fn test2(&self, value: i32) -> i32 { todo!() } -# async fn test3(&self, value: i32) -> i32 { todo!() } -# } -use async_graphql::*; - -#[derive(Interface)] -#[graphql( - field(name = "test1", ty = "i32", arg(name = "value", ty = "i32", default)), - field(name = "test2", ty = "i32", arg(name = "value", ty = "i32", default = 10)), - field(name = "test3", ty = "i32", arg(name = "value", ty = "i32", default_with = "my_default()")), -)] -enum MyInterface { - MyObj(MyObj), -} -``` - -## 输入对象 (InputObject) - -```rust -# extern crate async_graphql; -# fn my_default() -> i32 { 5 } -use async_graphql::*; - -#[derive(InputObject)] -struct MyInputObject { - #[graphql(default)] - value1: i32, - - #[graphql(default = 10)] - value2: i32, - - #[graphql(default_with = "my_default()")] - value3: i32, -} -``` diff --git a/docs/zh-CN/src/define_complex_object.md b/docs/zh-CN/src/define_complex_object.md deleted file mode 100644 index 8014a80e1..000000000 --- a/docs/zh-CN/src/define_complex_object.md +++ /dev/null @@ -1,44 +0,0 @@ -# 对象 (Object) - -和简单对象不同,对象必须为所有的字段定义 Resolver 函数,Resolver 函数定义在 impl 块中。 - -**一个 Resolver 函数必须是异步的,它的第一个参数必须是`&self`,第二个参数是可选的`Context`,接下来是字段的参数。** - -Resolver 函数用于计算字段的值,你可以执行一个数据库查询,并返回查询结果。**函数的返回值是字段的类型**,你也可以返回一个`async_graphql::Result`类型,这样能够返回一个错误,这个错误信息将输出到查询结果中。 - -在查询数据库时,你可能需要一个数据库连接池对象,这个对象是个全局的,你可以在创建 Schema 的时候,用`SchemaBuilder::data`函数设置`Schema`数据,用`Context::data`函数设置`Context`数据。下面的`value_from_db`字段展示了如何从`Context`中获取一个数据库连接。 - -```rust -# extern crate async_graphql; -# struct Data { pub name: String } -# struct DbConn {} -# impl DbConn { -# fn query_something(&self, id: i64) -> std::result::Result<Data, String> { Ok(Data {name:"".into()})} -# } -# struct DbPool {} -# impl DbPool { -# fn take(&self) -> DbConn { DbConn {} } -# } -use async_graphql::*; - -struct MyObject { - value: i32, -} - -#[Object] -impl MyObject { - async fn value(&self) -> String { - self.value.to_string() - } - - async fn value_from_db( - &self, - ctx: &Context<'_>, - #[graphql(desc = "Id of object")] id: i64 - ) -> Result<String> { - let conn = ctx.data::<DbPool>()?.take(); - Ok(conn.query_something(id)?.name) - } -} -``` - diff --git a/docs/zh-CN/src/define_enum.md b/docs/zh-CN/src/define_enum.md deleted file mode 100644 index af45afdee..000000000 --- a/docs/zh-CN/src/define_enum.md +++ /dev/null @@ -1,68 +0,0 @@ -# 枚举 (Enum) - -定义枚举相当简单,直接给出一个例子。 - -**Async-graphql 会自动把枚举项的名称转换为 GraphQL 标准的大写加下划线形式,你也可以用`name`属性自已定义名称。** - -```rust -# extern crate async_graphql; -use async_graphql::*; - -/// One of the films in the Star Wars Trilogy -#[derive(Enum, Copy, Clone, Eq, PartialEq)] -pub enum Episode { - /// Released in 1977. - NewHope, - - /// Released in 1980. - Empire, - - /// Released in 1983. - #[graphql(name="AAA")] - Jedi, -} -``` - -## 封装外部枚举类型 - -Rust 的 [孤儿规则](https://doc.rust-lang.org/book/traits.html#rules-for-implementing-traits) 要求特质或您要实现特质的类型必须在相同的板条箱中定义,因此你不能向 GraphQL 公开外部枚举类型。为了创建`Enum`类型,一种常见的解决方法是创建一个新的与现有远程枚举类型同等的枚举。 - -```rust -# extern crate async_graphql; -# mod remote_crate { pub enum RemoteEnum { A, B, C } } -use async_graphql::*; - -/// Provides parity with a remote enum type -#[derive(Enum, Copy, Clone, Eq, PartialEq)] -pub enum LocalEnum { - A, - B, - C, -} - -/// Conversion interface from remote type to our local GraphQL enum type -impl From<remote_crate::RemoteEnum> for LocalEnum { - fn from(e: remote_crate::RemoteEnum) -> Self { - match e { - remote_crate::RemoteEnum::A => Self::A, - remote_crate::RemoteEnum::B => Self::B, - remote_crate::RemoteEnum::C => Self::C, - } - } -} -``` - -该过程很繁琐,需要多个步骤才能使本地枚举和远程枚举保持同步。`Async_graphql`提供了一个方便的功能,可在派生`Enum`之后通过附加属性生成 LocalEnum 的`From <remote_crate::RemoteEnum>`以及相反的`From<LocalEnum> for remote_crate::RemoteEnum`: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# mod remote_crate { pub enum RemoteEnum { A, B, C } } -#[derive(Enum, Copy, Clone, Eq, PartialEq)] -#[graphql(remote = "remote_crate::RemoteEnum")] -enum LocalEnum { - A, - B, - C, -} -``` diff --git a/docs/zh-CN/src/define_input_object.md b/docs/zh-CN/src/define_input_object.md deleted file mode 100644 index add3a0ca4..000000000 --- a/docs/zh-CN/src/define_input_object.md +++ /dev/null @@ -1,92 +0,0 @@ -# 输入对象 (InputObject) - -你可以定义一个对象作为参数类型,GraphQL 称之为`Input Object`,输入对象的定义方式和[简单对象](define_simple_object.md)很像,不同的是,简单对象只能用于输出,而输入对象只能用于输入。 - -你也通过可选的`#[graphql]`属性来给字段添加描述,重命名。 - -```rust -# extern crate async_graphql; -# #[derive(SimpleObject)] -# struct User { a: i32 } -use async_graphql::*; - -#[derive(InputObject)] -struct Coordinate { - latitude: f64, - longitude: f64, -} - -struct Mutation; - -#[Object] -impl Mutation { - async fn users_at_location(&self, coordinate: Coordinate, radius: f64) -> Vec<User> { - // 将坐标写入数据库 - // ... -# todo!() - } -} -``` - -## 泛型 - -如果你希望其它类型能够重用`InputObject`,则可以定义泛型的`InputObject`,并指定具体的类型。 - -在下面的示例中,创建了两种`InputObject`类型: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(InputObject)] -# struct SomeType { a: i32 } -# #[derive(InputObject)] -# struct SomeOtherType { a: i32 } -#[derive(InputObject)] -#[graphql(concrete(name = "SomeName", params(SomeType)))] -#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))] -pub struct SomeGenericInput<T: InputType> { - field1: Option<T>, - field2: String -} -``` - -注意:每个泛型参数必须实现`InputType`,如上所示。 - -生成的 SDL 如下: - -```gql -input SomeName { - field1: SomeType - field2: String! -} - -input SomeOtherName { - field1: SomeOtherType - field2: String! -} -``` - -在其它`InputObject`中使用具体的泛型类型: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(InputObject)] -# struct SomeType { a: i32 } -# #[derive(InputObject)] -# struct SomeOtherType { a: i32 } -# #[derive(InputObject)] -# #[graphql(concrete(name = "SomeName", params(SomeType)))] -# #[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))] -# pub struct SomeGenericInput<T: InputType> { -# field1: Option<T>, -# field2: String -# } -#[derive(InputObject)] -pub struct YetAnotherInput { - a: SomeGenericInput<SomeType>, - b: SomeGenericInput<SomeOtherType>, -} -``` - -你可以将多个通用类型传递给`params()`,并用逗号分隔。 diff --git a/docs/zh-CN/src/define_interface.md b/docs/zh-CN/src/define_interface.md deleted file mode 100644 index 065faa621..000000000 --- a/docs/zh-CN/src/define_interface.md +++ /dev/null @@ -1,120 +0,0 @@ -# 接口 (Interface) - -接口用于抽象具有特定字段集合的对象,`Async-graphql`内部实现实际上是一个包装器,包装器转发接口上定义的 Resolver 函数到实现该接口的对象,所以接口类型所包含的字段类型,参数都必须和实现该接口的对象完全匹配。 - -`Async-graphql`自动实现了对象到接口的转换,把一个对象类型转换为接口类型只需要调用`Into::into`。 - -接口字段的`name`属性表示转发的 Resolver 函数,并且它将被转换为驼峰命名作为字段名称。 -如果你需要自定义 GraphQL 接口字段名称,可以同时使用`name`和`method`属性。 - -- 当`name`和`method`属性同时存在时,`name`是 GraphQL 接口字段名,而`method`是 Resolver 函数名。 -- 当只有`name`存在时,转换为驼峰命名后的`name`是 GraphQL 接口字段名,而`name`是 Resolver 函数名。 - - -```rust -# extern crate async_graphql; -use async_graphql::*; - -struct Circle { - radius: f32, -} - -#[Object] -impl Circle { - async fn area(&self) -> f32 { - std::f32::consts::PI * self.radius * self.radius - } - - async fn scale(&self, s: f32) -> Shape { - Circle { radius: self.radius * s }.into() - } - - #[graphql(name = "short_description")] - async fn short_description(&self) -> String { - "Circle".to_string() - } -} - -struct Square { - width: f32, -} - -#[Object] -impl Square { - async fn area(&self) -> f32 { - self.width * self.width - } - - async fn scale(&self, s: f32) -> Shape { - Square { width: self.width * s }.into() - } - - #[graphql(name = "short_description")] - async fn short_description(&self) -> String { - "Square".to_string() - } -} - -#[derive(Interface)] -#[graphql( - field(name = "area", ty = "f32"), - field(name = "scale", ty = "Shape", arg(name = "s", ty = "f32")), - field(name = "short_description", method = "short_description", ty = "String") -)] -enum Shape { - Circle(Circle), - Square(Square), -} -``` - -## 手工注册接口类型 - -`Async-graphql`在初始化阶段从`Schema`开始遍历并注册所有被直接或者间接引用的类型,如果某个接口没有被引用到,那么它将不会存在于注册表中,就像下面的例子, -即使`MyObject`实现了`MyInterface`,但由于`Schema`中并没有引用`MyInterface`,类型注册表中将不会存在`MyInterface`类型的信息。 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(Interface)] -#[graphql( - field(name = "name", ty = "String"), -)] -enum MyInterface { - MyObject(MyObject), -} - -#[derive(SimpleObject)] -struct MyObject { - name: String, -} - -struct Query; - -#[Object] -impl Query { - async fn obj(&self) -> MyObject { - todo!() - } -} - -type MySchema = Schema<Query, EmptyMutation, EmptySubscription>; -``` - -你需要在构造 Schema 时手工注册`MyInterface`类型: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(Interface)] -# #[graphql(field(name = "name", ty = "String"))] -# enum MyInterface { MyObject(MyObject) } -# #[derive(SimpleObject)] -# struct MyObject { name: String, } -# struct Query; -# #[Object] -# impl Query { async fn version(&self) -> &str { "1.0" } } - -Schema::build(Query, EmptyMutation, EmptySubscription) - .register_output_type::<MyInterface>() - .finish(); -``` diff --git a/docs/zh-CN/src/define_schema.md b/docs/zh-CN/src/define_schema.md deleted file mode 100644 index 30a93f687..000000000 --- a/docs/zh-CN/src/define_schema.md +++ /dev/null @@ -1,6 +0,0 @@ -# 定义模式 (Schema) - -在定义了基本的类型之后,需要定义一个模式把他们组合起来,模式由三种类型组成,查询对象,变更对象和订阅对象,其中变更对象和订阅对象是可选的。 - -当模式创建时,`Async-graphql`会遍历所有对象图,并注册所有类型。这意味着,如果定义了 GraphQL 对象但从未引用,那么此对象就不会暴露在模式中。 - diff --git a/docs/zh-CN/src/define_simple_object.md b/docs/zh-CN/src/define_simple_object.md deleted file mode 100644 index bec20de1e..000000000 --- a/docs/zh-CN/src/define_simple_object.md +++ /dev/null @@ -1,123 +0,0 @@ -# 简单对象 (SimpleObject) - -简单对象是把 Rust 结构的所有字段都直接映射到 GraphQL 对象,不支持定义单独的 Resolver 函数。 - -下面的例子定义了一个名称为 MyObject 的对象,包含字段`a`和`b`,`c`由于标记为`#[graphql(skip)]`,所以不会映射到 GraphQL。 - -```rust -# extern crate async_graphql; -use async_graphql::*; - -#[derive(SimpleObject)] -struct MyObject { - /// Value a - a: i32, - - /// Value b - b: i32, - - #[graphql(skip)] - c: i32, -} -``` - -## 泛型 - -如果你希望其它类型能够重用`SimpleObject`,则可以定义泛型的`SimpleObject`,并指定具体的类型。 - -在下面的示例中,创建了两种`SimpleObject`类型: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(SimpleObject)] -# struct SomeType { a: i32 } -# #[derive(SimpleObject)] -# struct SomeOtherType { a: i32 } -#[derive(SimpleObject)] -#[graphql(concrete(name = "SomeName", params(SomeType)))] -#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))] -pub struct SomeGenericObject<T: OutputType> { - field1: Option<T>, - field2: String -} -``` - -注意:每个泛型参数必须实现`OutputType`,如上所示。 - -生成的 SDL 如下: - -```gql -type SomeName { - field1: SomeType - field2: String! -} - -type SomeOtherName { - field1: SomeOtherType - field2: String! -} -``` - -在其它`Object`中使用具体的泛型类型: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(SimpleObject)] -# struct SomeType { a: i32 } -# #[derive(SimpleObject)] -# struct SomeOtherType { a: i32 } -# #[derive(SimpleObject)] -# #[graphql(concrete(name = "SomeName", params(SomeType)))] -# #[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))] -# pub struct SomeGenericObject<T: OutputType> { -# field1: Option<T>, -# field2: String, -# } -#[derive(SimpleObject)] -pub struct YetAnotherObject { - a: SomeGenericObject<SomeType>, - b: SomeGenericObject<SomeOtherType>, -} -``` - -你可以将多个通用类型传递给`params()`,并用逗号分隔。 - -## 复杂字段 - -有时 GraphQL 对象的大多数字段仅返回结构成员的值,但是少数字段需要计算。通常我们使用`Object`宏来定义这样一个 GraphQL 对象。 - -用`ComplexObject`宏可以更漂亮的完成这件事,我们可以使用`SimpleObject`宏来定义 -一些简单的字段,并使用`ComplexObject`宏来定义其他一些需要计算的字段。 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(SimpleObject)] -#[graphql(complex)] // 注意:如果你希望 ComplexObject 宏生效,complex 属性是必须的 -struct MyObj { - a: i32, - b: i32, -} - -#[ComplexObject] -impl MyObj { - async fn c(&self) -> i32 { - self.a + self.b - } -} -``` - -## 同时用于输入和输出 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(SimpleObject, InputObject)] -#[graphql(input_name = "MyObjInput")] // 注意:你必须用 input_name 属性为输入类型定义一个新的名称,否则将产生一个运行时错误。 -struct MyObj { - a: i32, - b: i32, -} -``` diff --git a/docs/zh-CN/src/define_union.md b/docs/zh-CN/src/define_union.md deleted file mode 100644 index 987e07842..000000000 --- a/docs/zh-CN/src/define_union.md +++ /dev/null @@ -1,104 +0,0 @@ -# 联合 (Union) - -联合的定义和接口非常像,**但不允许定义字段**。这两个类型的实现原理也差不多,对于`Async-graphql`来说,联合类型是接口类型的子集。 - -下面把接口定义的例子做一个小小的修改,去掉字段的定义。 - -```rust -# extern crate async_graphql; -use async_graphql::*; - -struct Circle { - radius: f32, -} - -#[Object] -impl Circle { - async fn area(&self) -> f32 { - std::f32::consts::PI * self.radius * self.radius - } - - async fn scale(&self, s: f32) -> Shape { - Circle { radius: self.radius * s }.into() - } -} - -struct Square { - width: f32, -} - -#[Object] -impl Square { - async fn area(&self) -> f32 { - self.width * self.width - } - - async fn scale(&self, s: f32) -> Shape { - Square { width: self.width * s }.into() - } -} - -#[derive(Union)] -enum Shape { - Circle(Circle), - Square(Square), -} -``` - -## 展平嵌套联合 - -GraphQL 的有个限制是`Union`类型内不能包含其它联合类型。所有成员必须为`Object`。 -位置支持嵌套`Union`,我们可以用`#graphql(flatten)`,是它们合并到上级`Union`类型。 -```rust -# extern crate async_graphql; -#[derive(async_graphql::Union)] -pub enum TopLevelUnion { - A(A), - - // 除非我们使用 `flatten` 属性,否则将无法编译 - #[graphql(flatten)] - B(B), -} - -#[derive(async_graphql::SimpleObject)] -pub struct A { - a: i32, - // ... -} - -#[derive(async_graphql::Union)] -pub enum B { - C(C), - D(D), -} - -#[derive(async_graphql::SimpleObject)] -pub struct C { - c: i32, - // ... -} - -#[derive(async_graphql::SimpleObject)] -pub struct D { - d: i32, - // ... -} -``` - -上面的示例将顶级`Union`转换为以下等效形式: - -```rust -# extern crate async_graphql; -# #[derive(async_graphql::SimpleObject)] -# struct A { a: i32 } -# #[derive(async_graphql::SimpleObject)] -# struct C { c: i32 } -# #[derive(async_graphql::SimpleObject)] -# struct D { d: i32 } -#[derive(async_graphql::Union)] -pub enum TopLevelUnion { - A(A), - C(C), - D(D), -} -``` diff --git a/docs/zh-CN/src/depth_and_complexity.md b/docs/zh-CN/src/depth_and_complexity.md deleted file mode 100644 index 16a799cc8..000000000 --- a/docs/zh-CN/src/depth_and_complexity.md +++ /dev/null @@ -1,118 +0,0 @@ -# 查询的深度和复杂度 - -⚠️GraphQL 提供了非常灵活的查询方法,但在客户端上滥用复杂的查询可能造成风险,限制查询语句的深度和复杂度可以减轻这种风险。 - -## 昂贵的查询 - -考虑一种允许列出博客文章的架构。每个博客帖子也与其他帖子相关。 - -```graphql -type Query { - posts(count: Int = 10): [Post!]! -} - -type Post { - title: String! - text: String! - related(count: Int = 10): [Post!]! -} -``` - -创建一个会引起很大响应的查询不是很困难: - -```graphql -{ - posts(count: 100) { - related(count: 100) { - related(count: 100) { - related(count: 100) { - title - } - } - } - } -} -``` - -响应的大小随`related`字段的每个其他级别呈指数增长。幸运的是,`Async-graphql`提供了一种防止此类查询的方法。 - -## 限制查询的深度 - -查询的深度是字段嵌套的层数,下面是一个深度为`3`的查询。 - -```graphql -{ - a { - b { - c - } - } -} -``` - -在创建`Schema`的时候可以限制深度,如果查询语句超过这个限制,则会出错并且返回`Query is nested too deep.`消息。 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# struct Query; -# #[Object] -# impl Query { async fn version(&self) -> &str { "1.0" } } -let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .limit_depth(5) // 限制最大深度为 5 - .finish(); -``` - -## 限制查询的复杂度 - -复杂度是查询语句中字段的数量,每个字段的复杂度默认为`1`,下面是一个复杂度为`6`的查询。 - -```graphql -{ - a b c { - d { - e f - } - } -} -``` - -在创建`Schema`的时候可以限制复杂度,如果查询语句超过这个限制,则会出错并且返回`Query is too complex.`。 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# struct Query; -# #[Object] -# impl Query { async fn version(&self) -> &str { "1.0" } } -let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .limit_complexity(5) // 限制复杂度为 5 - .finish(); -``` - -## 自定义字段的复杂度 - -针对非列表类型和列表类型的字段,有两种自定义复杂度的方法。 -下面的代码中,`value`字段的复杂度为`5`。而`values`字段的复杂度为`count * child_complexity`,`child_complexity`是一个特殊的变量,表示子查询的复杂度, -`count`是字段的参数,这个表达式用于计算`values`字段的复杂度,并且返回值的类型必须是`usize`。 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -struct Query; - -#[Object] -impl Query { - #[graphql(complexity = 5)] - async fn value(&self) -> i32 { - todo!() - } - - #[graphql(complexity = "count * child_complexity")] - async fn values(&self, count: usize) -> i32 { - todo!() - } -} -``` - -**注意:计算复杂度是在验证阶段完成而不是在执行阶段,所以你不用担心超限的查询语句会导致查询只执行一部分。** diff --git a/docs/zh-CN/src/derived_fields.md b/docs/zh-CN/src/derived_fields.md deleted file mode 100644 index d17819e21..000000000 --- a/docs/zh-CN/src/derived_fields.md +++ /dev/null @@ -1,104 +0,0 @@ -# 派生字段 - -有时两个字段有一样的查询逻辑,仅仅是输出的类型不同,在 `async-graphql` 中,你可以为它创建派生字段。 - -在以下例子中,你已经有一个`duration_rfc2822`字段输出`RFC2822`格式的时间格式,然后复用它派生一个新的`date_rfc3339`字段。 - -```rust -# extern crate chrono; -# use chrono::Utc; -# extern crate async_graphql; -# use async_graphql::*; -struct DateRFC3339(chrono::DateTime<Utc>); -struct DateRFC2822(chrono::DateTime<Utc>); - -#[Scalar] -impl ScalarType for DateRFC3339 { - fn parse(value: Value) -> InputValueResult<Self> { todo!() } - - fn to_value(&self) -> Value { - Value::String(self.0.to_rfc3339()) - } -} - -#[Scalar] -impl ScalarType for DateRFC2822 { - fn parse(value: Value) -> InputValueResult<Self> { todo!() } - - fn to_value(&self) -> Value { - Value::String(self.0.to_rfc2822()) - } -} - -impl From<DateRFC2822> for DateRFC3339 { - fn from(value: DateRFC2822) -> Self { - DateRFC3339(value.0) - } -} - -struct Query; - -#[Object] -impl Query { - #[graphql(derived(name = "date_rfc3339", into = "DateRFC3339"))] - async fn duration_rfc2822(&self, arg: String) -> DateRFC2822 { - todo!() - } -} -``` - -它将呈现为如下 GraphQL: - -```graphql -type Query { - duration_rfc2822(arg: String): DateRFC2822! - duration_rfc3339(arg: String): DateRFC3339! -} -``` - -## 包装类型 - -因为 [孤儿规则](https://doc.rust-lang.org/book/traits.html#rules-for-implementing-traits),以下代码无法通过编译: - -```rust,ignore -impl From<Vec<U>> for Vec<T> { - ... -} -``` - -因此,你将无法为现有的包装类型结构(如`Vec`或`Option`)生成派生字段。 -但是当你为 `T` 实现了 `From<U>` 后,你可以为 `Vec<T>` 实现 `From<Vec<U>>`,为 `Option<T>` 实现 `From<Option<U>>`. -使用 `with` 参数来定义一个转换函数,而不是用 `Into::into`。 - -### Example - -```rust -# extern crate serde; -# use serde::{Serialize, Deserialize}; -# extern crate async_graphql; -# use async_graphql::*; -#[derive(Serialize, Deserialize, Clone)] -struct ValueDerived(String); - -#[derive(Serialize, Deserialize, Clone)] -struct ValueDerived2(String); - -scalar!(ValueDerived); -scalar!(ValueDerived2); - -impl From<ValueDerived> for ValueDerived2 { - fn from(value: ValueDerived) -> Self { - ValueDerived2(value.0) - } -} - -fn option_to_option<T, U: From<T>>(value: Option<T>) -> Option<U> { - value.map(|x| x.into()) -} - -#[derive(SimpleObject)] -struct TestObj { - #[graphql(derived(owned, name = "value2", into = "Option<ValueDerived2>", with = "option_to_option"))] - pub value1: Option<ValueDerived>, -} -``` diff --git a/docs/zh-CN/src/error_extensions.md b/docs/zh-CN/src/error_extensions.md deleted file mode 100644 index a5fb5c9ed..000000000 --- a/docs/zh-CN/src/error_extensions.md +++ /dev/null @@ -1,234 +0,0 @@ -# 错误扩展 - -引用 [graphql-spec](https://spec.graphql.org/June2018/#example-fce18) - -> GraphQL 服务可以通过扩展提供错误的附加条目。 -> 该条目(如果设置)必须是一个映射作为其值,用于附加错误的其它信息。 - -## 示例 - -我建议您查看此 [错误扩展示例](https://github.com/async-graphql/examples/blob/master/actix-web/error-extensions/src/main.rs) 作为快速入门。 - -## 一般概念 - -在`Async-graphql`中,所有面向用户的错误都强制转换为`Error`类型,默认情况下会提供 -由`std:::fmt::Display`暴露的错误消息。但是,`Error`实际上提供了一个额外的可以扩展错误的信息。 - -Resolver 函数类似这样: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# struct Query; -# #[Object] -# impl Query { -async fn parse_with_extensions(&self) -> Result<i32, Error> { - Err(Error::new("MyMessage").extend_with(|_, e| e.set("details", "CAN_NOT_FETCH"))) -} -# } -``` - -然后可以返回如下响应: - -```json -{ - "errors": [ - { - "message": "MyMessage", - "locations": [ ... ], - "path": [ ... ], - "extensions": { - "details": "CAN_NOT_FETCH", - } - } - ] -} -``` - - -## ErrorExtensions - -手动构造新的`Error`很麻烦。这就是为什么`Async-graphql`提供 -两个方便特性,可将您的错误转换为适当的`Error`扩展。 - -扩展任何错误的最简单方法是对错误调用`extend_with`。 -这将把任何错误转换为具有给定扩展信息的`Error`。 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# struct Query; -use std::num::ParseIntError; -# #[Object] -# impl Query { -async fn parse_with_extensions(&self) -> Result<i32> { - Ok("234a" - .parse() - .map_err(|err: ParseIntError| err.extend_with(|_err, e| e.set("code", 404)))?) -} -# } -``` - -### 为自定义错误实现 ErrorExtensions - -你也可以给自己的错误类型实现`ErrorExtensions`: - - -```rust -# extern crate async_graphql; -# extern crate thiserror; -# use async_graphql::*; -#[derive(Debug, thiserror::Error)] -pub enum MyError { - #[error("Could not find resource")] - NotFound, - - #[error("ServerError")] - ServerError(String), - - #[error("No Extensions")] - ErrorWithoutExtensions, -} - -impl ErrorExtensions for MyError { - // lets define our base extensions - fn extend(&self) -> Error { - Error::new(format!("{}", self)).extend_with(|err, e| - match self { - MyError::NotFound => e.set("code", "NOT_FOUND"), - MyError::ServerError(reason) => e.set("reason", reason.clone()), - MyError::ErrorWithoutExtensions => {} - }) - } -} -``` - -您只需要对错误调用`extend`即可将错误与其提供的扩展信息一起传递,或者通过`extend_with`进一步扩展错误信息。 - -```rust -# extern crate async_graphql; -# extern crate thiserror; -# use async_graphql::*; -# #[derive(Debug, thiserror::Error)] -# pub enum MyError { -# #[error("Could not find resource")] -# NotFound, -# -# #[error("ServerError")] -# ServerError(String), -# -# #[error("No Extensions")] -# ErrorWithoutExtensions, -# } -# struct Query; -# #[Object] -# impl Query { -async fn parse_with_extensions_result(&self) -> Result<i32> { - // Err(MyError::NotFound.extend()) - // OR - Err(MyError::NotFound.extend_with(|_, e| e.set("on_the_fly", "some_more_info"))) -} -# } -``` - -```json -{ - "errors": [ - { - "message": "NotFound", - "locations": [ ... ], - "path": [ ... ], - "extensions": { - "code": "NOT_FOUND", - "on_the_fly": "some_more_info" - } - } - ] -} -``` - -## ResultExt -这个特质使您可以直接在结果上调用`extend_err`。因此上面的代码不再那么冗长。 - -```rust,ignore -# // @todo figure out why this example does not compile! -# extern crate async_graphql; -use async_graphql::*; -# struct Query; -# #[Object] -# impl Query { -async fn parse_with_extensions(&self) -> Result<i32> { - Ok("234a" - .parse() - .extend_err(|_, e| e.set("code", 404))?) -} -# } -``` - -### 链式调用 - -由于对所有`&E where E: std::fmt::Display`实现了`ErrorExtensions`和`ResultsExt`,我们可以将扩展链接在一起。 - -```rust -# extern crate async_graphql; -use async_graphql::*; -# struct Query; -# #[Object] -# impl Query { -async fn parse_with_extensions(&self) -> Result<i32> { - match "234a".parse() { - Ok(n) => Ok(n), - Err(e) => Err(e - .extend_with(|_, e| e.set("code", 404)) - .extend_with(|_, e| e.set("details", "some more info..")) - // keys may also overwrite previous keys... - .extend_with(|_, e| e.set("code", 500))), - } -} -# } -``` - -响应: - -```json -{ - "errors": [ - { - "message": "MyMessage", - "locations": [ ... ], - "path": [ ... ], - "extensions": { - "details": "some more info...", - "code": 500, - } - } - ] -} -``` - -### 缺陷 - -Rust 的稳定版本还未提供特化功能,这就是为什么`ErrorExtensions`为`&E where E: std::fmt::Display`实现,代替`E:std::fmt::Display`通过提供一些特化功能。 - -[Autoref-based stable specialization](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md). - -缺点是下面的代码**不能**编译: - -```rust,ignore,does_not_compile -async fn parse_with_extensions_result(&self) -> Result<i32> { - // the trait `error::ErrorExtensions` is not implemented - // for `std::num::ParseIntError` - "234a".parse().extend_err(|_, e| e.set("code", 404)) -} -``` - -但这可以通过编译: - -```rust,ignore,does_not_compile -async fn parse_with_extensions_result(&self) -> Result<i32> { - // does work because ErrorExtensions is implemented for &ParseIntError - "234a" - .parse() - .map_err(|ref e: ParseIntError| e.extend_with(|_, e| e.set("code", 404))) -} -``` diff --git a/docs/zh-CN/src/error_handling.md b/docs/zh-CN/src/error_handling.md deleted file mode 100644 index b0a91b1ac..000000000 --- a/docs/zh-CN/src/error_handling.md +++ /dev/null @@ -1,28 +0,0 @@ -# 错误处理 - -Resolver 函数可以返回一个 Result 类型,以下是 Result 的定义: - -```rust,ignore -type Result<T> = std::result::Result<T, Error>; -``` - -任何错误都能够被转换为`Error`,并且你还能扩展标准的错误信息。 - -下面是一个例子,解析一个输入的字符串到整数,当解析失败时返回错误,并且附加额外的错误信息。 - -```rust -# extern crate async_graphql; -# use std::num::ParseIntError; -use async_graphql::*; - -struct Query; - -#[Object] -impl Query { - async fn parse_with_extensions(&self, input: String) -> Result<i32> { - Ok("234a" - .parse() - .map_err(|err: ParseIntError| err.extend_with(|_, e| e.set("code", 400)))?) - } -} -``` diff --git a/docs/zh-CN/src/extensions.md b/docs/zh-CN/src/extensions.md deleted file mode 100644 index 68714de5c..000000000 --- a/docs/zh-CN/src/extensions.md +++ /dev/null @@ -1,3 +0,0 @@ -# 扩展 - -`async-graphql` 允许你不修改核心代码就能扩展它功能。 diff --git a/docs/zh-CN/src/extensions_available.md b/docs/zh-CN/src/extensions_available.md deleted file mode 100644 index 4c9c5d3a5..000000000 --- a/docs/zh-CN/src/extensions_available.md +++ /dev/null @@ -1,55 +0,0 @@ -# 可用的扩展列表 - -`async-graphql` 中有很多可用的扩展用于增强你的 GraphQL 服务器。 - -## Analyzer -*Available in the repository* - -`Analyzer` 扩展将在每个响应的扩展中输出 `complexity` 和 `depth` 字段。 - -## Apollo Persisted Queries -*Available in the repository* - -要提高大型查询的性能,你可以启用此扩展,每个查询语句都将与一个唯一 ID 相关联,因此客户端可以直接发送此 ID 查询以减少请求的大小。 - -这个扩展不会强迫你使用一些缓存策略,你可以选择你想要的缓存策略,你只需要实现 `CacheStorage` trait: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[async_trait::async_trait] -pub trait CacheStorage: Send + Sync + Clone + 'static { - /// Load the query by `key`. - async fn get(&self, key: String) -> Option<String>; - /// Save the query by `key`. - async fn set(&self, key: String, query: String); -} -``` - -References: [Apollo doc - Persisted Queries](https://www.apollographql.com/docs/react/api/link/persisted-queries/) - -## Apollo Tracing -*Available in the repository* - -`Apollo Tracing` 扩展用于在响应中包含此查询分析数据。此扩展程序遵循旧的且现已弃用的 [Apollo Tracing Spec](https://github.com/apollographql/apollo-tracing) 。 -如果你想支持更新的 Apollo Reporting Protocol,推荐使用 [async-graphql Apollo studio extension](https://github.com/async-graphql/async_graphql_apollo_studio_extension) 。 - -## Apollo Studio -*Available at [async-graphql/async_graphql_apollo_studio_extension](https://github.com/async-graphql/async_graphql_apollo_studio_extension)* - - `async-graphql` 提供了实现官方 [Apollo Specification](https://www.apollographql.com/docs/studio/setup-analytics/#third-party-support) 的扩展,位于 [async-graphql-extension- apollo-tracing](https://github.com/async-graphql/async_graphql_apollo_studio_extension) 和 [crates.io](https://crates.io/crates/async-graphql-extension-apollo-tracing) 。 - -## Logger -*Available in the repository* - -`Logger` 是一个简单的扩展,允许你向 `async-graphql` 添加一些日志记录功能。这也是学习如何创建自己的扩展的一个很好的例子。 - -## OpenTelemetry -*Available in the repository* - -`OpenTelemetry` 扩展提供 [opentelemetry crate](https://crates.io/crates/opentelemetry) 的集成,以允许你的应用程序从 `async-graphql` 捕获分布式跟踪和指标。 - -## Tracing -*Available in the repository* - -`Tracing` 扩展提供 [tracing crate](https://crates.io/crates/tracing) 的集成,允许您向 `async-graphql` 添加一些跟踪功能,有点像`Logger` 扩展。 diff --git a/docs/zh-CN/src/extensions_inner_working.md b/docs/zh-CN/src/extensions_inner_working.md deleted file mode 100644 index d206e303a..000000000 --- a/docs/zh-CN/src/extensions_inner_working.md +++ /dev/null @@ -1,217 +0,0 @@ -# 如何定义扩展 - -`async-graphql` 扩展是通过实现 `Extension` trait 来定义的。 `Extension` trait 允许你将自定义代码插入到执行 GraphQL 查询的步骤中。 - -`Extensions` 很像来自其他框架的中间件,使用它们时要小心:当你使用扩展时**它对每个 GraphQL 请求生效**。 - -## 一句话解释什么是中间件 - -让我们了解什么是中间件: - -```rust,ignore -async fn middleware(&self, ctx: &ExtensionContext<'_>, next: NextMiddleware<'_>) -> MiddlewareResult { - // 你的中间件代码 - - /* - * 调用 next.run 函数执行下个中间件的逻辑 - */ - next.run(ctx).await -} -``` - -如你所见,`middleware` 只是在末尾调用 next 函数的函数。但我们也可以在开头使用 `next.run` 来实现中间件。这就是它变得棘手的地方:根据你放置逻辑的位置以及`next.run`调用的位置,你的逻辑将不会具有相同的执行顺序。 - -根据你代码,你需要在 `next.run` 调用之前或之后处理它。如果你需要更多关于中间件的信息,网上有很多。 - -## 查询的处理 - -查询的每个阶段都有回调,你将能够基于这些回调创建扩展。 - -### 请求 - -首先,当我们收到一个请求时,如果它不是订阅,第一个被调用的函数将是 `request`,它在传入请求时调用,并输出结果给客户端。 - -Default implementation for `request`: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# use async_graphql::extensions::*; -# struct MyMiddleware; -# #[async_trait::async_trait] -# impl Extension for MyMiddleware { -async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response { - next.run(ctx).await -} -# } -``` - -根据你放置逻辑代码的位置,它将在正在查询执行的开头或结尾执行。 - - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# use async_graphql::extensions::*; -# struct MyMiddleware; -# #[async_trait::async_trait] -# impl Extension for MyMiddleware { -async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response { - // 此处的代码将在执行 prepare_request 之前运行。 - let result = next.run(ctx).await; - // 此处的代码将在把结果发送给客户端之前执行 - result -} -# } -``` - -### 准备查询 - -在 `request` 之后,将调用`prepare_request`,你可以在此处对请求做一些转换。 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# use async_graphql::*; -# use async_graphql::extensions::*; -# struct MyMiddleware; -# #[async_trait::async_trait] -# impl Extension for MyMiddleware { -async fn prepare_request( - &self, - ctx: &ExtensionContext<'_>, - request: Request, - next: NextPrepareRequest<'_>, -) -> ServerResult<Request> { - // 此处的代码在 prepare_request 之前执行 - let result = next.run(ctx, request).await; - // 此处的代码在 prepare_request 之后执行 - result -} -# } -``` - -### 解析查询 - -`parse_query` 将解析查询语句并生成 GraphQL `ExecutableDocument`,并且检查查询是否遵循 GraphQL 规范。通常,`async-graphql` 遵循最后一个稳定的规范(October2021)。 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# use async_graphql::extensions::*; -# use async_graphql::parser::types::ExecutableDocument; -# struct MyMiddleware; -# #[async_trait::async_trait] -# impl Extension for MyMiddleware { -/// Called at parse query. -async fn parse_query( - &self, - ctx: &ExtensionContext<'_>, - // The raw query - query: &str, - // The variables - variables: &Variables, - next: NextParseQuery<'_>, -) -> ServerResult<ExecutableDocument> { - next.run(ctx, query, variables).await -} -# } -``` - -### 校验 - -`validation` 步骤将执行查询校验(取决于你指定的 `validation_mode`),并向客户端提供有关查询无效的原因。 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# use async_graphql::extensions::*; -# struct MyMiddleware; -# #[async_trait::async_trait] -# impl Extension for MyMiddleware { -/// Called at validation query. -async fn validation( - &self, - ctx: &ExtensionContext<'_>, - next: NextValidation<'_>, -) -> Result<ValidationResult, Vec<ServerError>> { - next.run(ctx).await -} -# } -``` - -### 执行 - -`execution` 步骤是一个很大的步骤,它将并发执行`Query`,或者顺序执行`Mutation`。 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# use async_graphql::extensions::*; -# struct MyMiddleware; -# #[async_trait::async_trait] -# impl Extension for MyMiddleware { -/// Called at execute query. -async fn execute( - &self, - ctx: &ExtensionContext<'_>, - operation_name: Option<&str>, - next: NextExecute<'_>, -) -> Response { - // 此处的代码在执行完整查询之前执行 - let result = next.run(ctx, operation_name).await; - // 此处的代码在执行完整查询之后执行 - result -} -# } -```` - -### resolve - -为每个字段执行`resolve`. - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# use async_graphql::extensions::*; -# struct MyMiddleware; -# #[async_trait::async_trait] -# impl Extension for MyMiddleware { -/// Called at resolve field. -async fn resolve( - &self, - ctx: &ExtensionContext<'_>, - info: ResolveInfo<'_>, - next: NextResolve<'_>, -) -> ServerResult<Option<Value>> { - // resolve 字段之前 - let result = next.run(ctx, info).await; - // resolve 字段之后 - result -} -# } -``` - -### 订阅 - -`subscribe`的行为和`request`很像,只是专门用于订阅查询。 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# use async_graphql::extensions::*; -# use futures_util::stream::BoxStream; -# struct MyMiddleware; -# #[async_trait::async_trait] -# impl Extension for MyMiddleware { -/// Called at subscribe request. -fn subscribe<'s>( - &self, - ctx: &ExtensionContext<'_>, - stream: BoxStream<'s, Response>, - next: NextSubscribe<'_>, -) -> BoxStream<'s, Response> { - next.run(ctx, stream) -} -# } -``` diff --git a/docs/zh-CN/src/field_guard.md b/docs/zh-CN/src/field_guard.md deleted file mode 100644 index 53c1740ca..000000000 --- a/docs/zh-CN/src/field_guard.md +++ /dev/null @@ -1,93 +0,0 @@ -# 字段守卫 (Field Guard) - -你可以为`Object`, `SimpleObject`, `ComplexObject`和`Subscription`的字段定义`守卫`,它将在调用字段的 Resolver 函数前执行,如果失败则返回一个错误。 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -#[derive(Eq, PartialEq, Copy, Clone)] -enum Role { - Admin, - Guest, -} - -struct RoleGuard { - role: Role, -} - -impl RoleGuard { - fn new(role: Role) -> Self { - Self { role } - } -} - -impl Guard for RoleGuard { - async fn check(&self, ctx: &Context<'_>) -> Result<()> { - if ctx.data_opt::<Role>() == Some(&self.role) { - Ok(()) - } else { - Err("Forbidden".into()) - } - } -} -``` - -用`guard`属性使用它: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(Eq, PartialEq, Copy, Clone)] -# enum Role { Admin, Guest, } -# struct RoleGuard { role: Role, } -# impl RoleGuard { fn new(role: Role) -> Self { Self { role } } } -# impl Guard for RoleGuard { async fn check(&self, ctx: &Context<'_>) -> Result<()> { todo!() } } -#[derive(SimpleObject)] -struct Query { - /// 只允许 Admin 访问 - #[graphql(guard = "RoleGuard::new(Role::Admin)")] - value1: i32, - /// 允许 Admin 或者 Guest 访问 - #[graphql(guard = "RoleGuard::new(Role::Admin).or(RoleGuard::new(Role::Guest))")] - value2: i32, -} -``` - -## 从参数中获取值 - -有时候守卫需要从字段参数中获取值,你需要像下面这样在创建守卫时传递该参数值: - -```rust -# extern crate async_graphql; -# use async_graphql::*; -struct EqGuard { - expect: i32, - actual: i32, -} - -impl EqGuard { - fn new(expect: i32, actual: i32) -> Self { - Self { expect, actual } - } -} - -impl Guard for EqGuard { - async fn check(&self, _ctx: &Context<'_>) -> Result<()> { - if self.expect != self.actual { - Err("Forbidden".into()) - } else { - Ok(()) - } - } -} - -struct Query; - -#[Object] -impl Query { - #[graphql(guard = "EqGuard::new(100, value)")] - async fn get(&self, value: i32) -> i32 { - value - } -} -``` \ No newline at end of file diff --git a/docs/zh-CN/src/input_value_validators.md b/docs/zh-CN/src/input_value_validators.md deleted file mode 100644 index c205158bd..000000000 --- a/docs/zh-CN/src/input_value_validators.md +++ /dev/null @@ -1,89 +0,0 @@ -# 输入值校验器 - -`Async-graphql`内置了一些常用的校验器,你可以在对象字段的参数或者`InputObject`的字段上使用它们。 - -- **maximum=N** 指定数字不能大于`N` -- **minimum=N** 指定数字不能小于`N` -- **multiple_of=N** 指定数字必须是`N`的倍数 -- **max_items=N** 指定列表的长度不能大于`N` -- **min_items=N** 指定列表的长度不能小于`N` -- **max_length=N** 字符串的长度不能大于`N` -- **min_length=N** 字符串的长度不能小于`N` -- **chars_max_length=N** 字符串中 unicode 字符的的数量不能小于`N` -- **chars_min_length=N** 字符串中 unicode 字符的的数量不能大于`N` -- **email** 有效的 email -- **url** 有效的 url -- **ip** 有效的 ip 地址 -- **regex=RE** 匹配正则表达式 - -```rust -# extern crate async_graphql; -use async_graphql::*; - -struct Query; - -#[Object] -impl Query { - /// name 参数的长度必须大于等于 5,小于等于 10 - async fn input(&self, #[graphql(validator(min_length = 5, max_length = 10))] name: String) -> Result<i32> { -# todo!() - } -} -``` - -## 校验列表成员 - -你可以打开`list`属性表示校验器作用于一个列表内的所有成员: - -```rust -# extern crate async_graphql; -use async_graphql::*; - -struct Query; - -#[Object] -impl Query { - async fn input(&self, #[graphql(validator(list, max_length = 10))] names: Vec<String>) -> Result<i32> { -# todo!() - } -} -``` - -## 自定义校验器 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -struct MyValidator { - expect: i32, -} - -impl MyValidator { - pub fn new(n: i32) -> Self { - MyValidator { expect: n } - } -} - -impl CustomValidator<i32> for MyValidator { - fn check(&self, value: &i32) -> Result<(), InputValueError<i32>> { - if *value == self.expect { - Ok(()) - } else { - Err(InputValueError::custom(format!("expect 100, actual {}", value))) - } - } -} - -struct Query; - -#[Object] -impl Query { - /// n 的值必须等于 100 - async fn value( - &self, - #[graphql(validator(custom = "MyValidator::new(100)"))] n: i32, - ) -> i32 { - n - } -} -``` diff --git a/docs/zh-CN/src/integrations.md b/docs/zh-CN/src/integrations.md deleted file mode 100644 index 8483fd371..000000000 --- a/docs/zh-CN/src/integrations.md +++ /dev/null @@ -1,11 +0,0 @@ -# 集成到 WebServer - -`Async-graphql`提供了对一些常用 Web Server 的集成支持。 - -- Poem [async-graphql-poem](https://crates.io/crates/async-graphql-poem) -- Actix-web [async-graphql-actix-web](https://crates.io/crates/async-graphql-actix-web) -- Warp [async-graphql-warp](https://crates.io/crates/async-graphql-warp) -- Axum [async-graphql-axum](https://crates.io/crates/async-graphql-axum) -- Rocket [async-graphql-rocket](https://crates.io/crates/async-graphql-rocket) - -**即使你目前使用的 Web Server 不在上面的列表中,自己实现类似的功能也相当的简单。** diff --git a/docs/zh-CN/src/integrations_to_actix_web.md b/docs/zh-CN/src/integrations_to_actix_web.md deleted file mode 100644 index c5ad1b057..000000000 --- a/docs/zh-CN/src/integrations_to_actix_web.md +++ /dev/null @@ -1,52 +0,0 @@ -# Actix-web - -`Async-graphql-actix-web`提供了`GraphQLRequest`提取器用于提取`GraphQL`请求,和`GraphQLResponse`用于输出`GraphQL`响应。 - -`GraphQLSubscription`用于创建一个支持 Web Socket 订阅的 Actor。 - -## 请求例子 - -你需要把 Schema 传入`actix_web::App`作为全局数据。 - -```rust -# extern crate async_graphql_actix_web; -# extern crate async_graphql; -# extern crate actix_web; -# use async_graphql::*; -# #[derive(Default,SimpleObject)] -# struct Query { a: i32 } -# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish(); -use actix_web::{web, HttpRequest, HttpResponse}; -use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse}; -async fn index( - schema: web::Data<Schema<Query, EmptyMutation, EmptySubscription>>, - request: GraphQLRequest, -) -> web::Json<GraphQLResponse> { - web::Json(schema.execute(request.into_inner()).await.into()) -} -``` - -## 订阅例子 - -```rust -# extern crate async_graphql_actix_web; -# extern crate async_graphql; -# extern crate actix_web; -# use async_graphql::*; -# #[derive(Default,SimpleObject)] -# struct Query { a: i32 } -# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish(); -use actix_web::{web, HttpRequest, HttpResponse}; -use async_graphql_actix_web::GraphQLSubscription; -async fn index_ws( - schema: web::Data<Schema<Query, EmptyMutation, EmptySubscription>>, - req: HttpRequest, - payload: web::Payload, -) -> actix_web::Result<HttpResponse> { - GraphQLSubscription::new(Schema::clone(&*schema)).start(&req, payload) -} -``` - -## 更多例子 - -[https://github.com/async-graphql/examples/tree/master/actix-web](https://github.com/async-graphql/examples/tree/master/actix-web) diff --git a/docs/zh-CN/src/integrations_to_poem.md b/docs/zh-CN/src/integrations_to_poem.md deleted file mode 100644 index 64f5875d3..000000000 --- a/docs/zh-CN/src/integrations_to_poem.md +++ /dev/null @@ -1,39 +0,0 @@ -# Poem - -## 请求例子 - -```rust -# extern crate async_graphql_poem; -# extern crate async_graphql; -# extern crate poem; -# use async_graphql::*; -# #[derive(Default, SimpleObject)] -# struct Query { a: i32 } -# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish(); -use poem::Route; -use async_graphql_poem::GraphQL; - -let app = Route::new() - .at("/ws", GraphQL::new(schema)); -``` - -## 订阅例子 - -```rust -# extern crate async_graphql_poem; -# extern crate async_graphql; -# extern crate poem; -# use async_graphql::*; -# #[derive(Default, SimpleObject)] -# struct Query { a: i32 } -# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish(); -use poem::{get, Route}; -use async_graphql_poem::GraphQLSubscription; - -let app = Route::new() - .at("/ws", get(GraphQLSubscription::new(schema))); -``` - -## 更多例子 - -[https://github.com/async-graphql/examples/tree/master/poem](https://github.com/async-graphql/examples/tree/master/poem) diff --git a/docs/zh-CN/src/integrations_to_warp.md b/docs/zh-CN/src/integrations_to_warp.md deleted file mode 100644 index 566c62f8e..000000000 --- a/docs/zh-CN/src/integrations_to_warp.md +++ /dev/null @@ -1,65 +0,0 @@ -# Warp - -`Async-graphql-warp`提供了两个`Filter`,`graphql`和`graphql_subscription`。 - -`graphql`用于执行`Query`和`Mutation`请求,它提取 GraphQL 请求,然后输出一个包含`async_graphql::Schema`和`async_graphql::Request`元组,你可以在之后组合其它 Filter,或者直接调用`Schema::execute`执行查询。 - -`graphql_subscription`用于实现基于 Web Socket 的订阅,它输出`warp::Reply`。 - -## 请求例子 - -```rust -# extern crate async_graphql_warp; -# extern crate async_graphql; -# extern crate warp; -# use async_graphql::*; -# use std::convert::Infallible; -# use warp::Filter; -# struct QueryRoot; -# #[Object] -# impl QueryRoot { async fn version(&self) -> &str { "1.0" } } -# async fn other() { -type MySchema = Schema<QueryRoot, EmptyMutation, EmptySubscription>; - -let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription); -let filter = async_graphql_warp::graphql(schema).and_then(|(schema, request): (MySchema, async_graphql::Request)| async move { - // 执行查询 - let resp = schema.execute(request).await; - - // 返回结果 - Ok::<_, Infallible>(async_graphql_warp::GraphQLResponse::from(resp)) -}); -warp::serve(filter).run(([0, 0, 0, 0], 8000)).await; -# } -``` - -## 订阅例子 - -```rust -# extern crate async_graphql_warp; -# extern crate async_graphql; -# extern crate warp; -# use async_graphql::*; -# use futures_util::stream::{Stream, StreamExt}; -# use std::convert::Infallible; -# use warp::Filter; -# struct SubscriptionRoot; -# #[Subscription] -# impl SubscriptionRoot { -# async fn tick(&self) -> impl Stream<Item = i32> { -# futures_util::stream::iter(0..10) -# } -# } -# struct QueryRoot; -# #[Object] -# impl QueryRoot { async fn version(&self) -> &str { "1.0" } } -# async fn other() { -let schema = Schema::new(QueryRoot, EmptyMutation, SubscriptionRoot); -let filter = async_graphql_warp::graphql_subscription(schema); -warp::serve(filter).run(([0, 0, 0, 0], 8000)).await; -# } -``` - -## 更多例子 - -[https://github.com/async-graphql/examples/tree/master/warp](https://github.com/async-graphql/examples/tree/master/warp) diff --git a/docs/zh-CN/src/introduction.md b/docs/zh-CN/src/introduction.md deleted file mode 100644 index 7093c47ff..000000000 --- a/docs/zh-CN/src/introduction.md +++ /dev/null @@ -1,9 +0,0 @@ -# 介绍 - -`Async-graphql`是用 Rust 语言实现的 GraphQL 服务端库。它完全兼容 GraphQL 规范以及绝大部分的扩展功能,类型安全并且高性能。 - -你可以用 Rust 语言的方式来定义 Schema,过程宏会自动生成 GraphQL 查询的框架代码,没有扩展 Rust 的语法,意味着 Rustfmt 可以正常使用,我很看重这一点,这也是为什么我会开发`Async-graphql`的原因之一。 - -## 为什么我要开发 Async-graphql? - -我喜欢 GraphQL 和 Rust,之前我一直用`Juniper`,它解决了我用 Rust 实现 GraphQL 服务器的问题,但也有一些遗憾,其中最重要的是它当时不支持 async/await,所以我决定做一个给自己用。 diff --git a/docs/zh-CN/src/merging_objects.md b/docs/zh-CN/src/merging_objects.md deleted file mode 100644 index 7c181e7e4..000000000 --- a/docs/zh-CN/src/merging_objects.md +++ /dev/null @@ -1,106 +0,0 @@ -# 合并对象 (MergedObject) - -## 为同一类型实现多次 Object - -通常我们在 Rust 中可以为同一类型创建多个实现,但由于过程宏的限制,无法为同一个类型创建多个 Object 实现。例如,下面的代码将无法通过编译。 - -```rust,ignore,does_not_compile -#[Object] -impl MyObject { - async fn field1(&self) -> i32 { - todo!() - } -} - -#[Object] -impl MyObject { - async fn field2(&self) -> i32 { - todo!() - } -} -``` - -用 `#[derive(MergedObject)]` 宏允许你合并多个独立的 Object 为一个。 - -**提示:** 每个`#[Object]`需要一个唯一的名称,即使在一个`MergedObject`内,所以确保每个对象有单独的名称。 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# #[derive(SimpleObject)] -# struct User { a: i32 } -# #[derive(SimpleObject)] -# struct Movie { a: i32 } -#[derive(Default)] -struct UserQuery; - -#[Object] -impl UserQuery { - async fn users(&self) -> Vec<User> { - todo!() - } -} - -#[derive(Default)] -struct MovieQuery; - -#[Object] -impl MovieQuery { - async fn movies(&self) -> Vec<Movie> { - todo!() - } -} - -#[derive(MergedObject, Default)] -struct Query(UserQuery, MovieQuery); - -let schema = Schema::new( - Query::default(), - EmptyMutation, - EmptySubscription -); -``` - -> ⚠️ **合并的对象无法在 Interface 中使用。** - -# 合并订阅 - -和`MergedObject`一样,你可以派生`MergedSubscription`来合并单独的`#[Subscription]`块。 - -像合并对象一样,每个订阅块都需要一个唯一的名称。 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# use futures_util::stream::{Stream}; -# #[derive(Default,SimpleObject)] -# struct Query { a: i32 } -#[derive(Default)] -struct Subscription1; - -#[Subscription] -impl Subscription1 { - async fn events1(&self) -> impl Stream<Item = i32> { - futures_util::stream::iter(0..10) - } -} - -#[derive(Default)] -struct Subscription2; - -#[Subscription] -impl Subscription2 { - async fn events2(&self) -> impl Stream<Item = i32> { - futures_util::stream::iter(10..20) - } -} - -#[derive(MergedSubscription, Default)] -struct Subscription(Subscription1, Subscription2); - -let schema = Schema::new( - Query::default(), - EmptyMutation, - Subscription::default() -); -``` diff --git a/docs/zh-CN/src/query_and_mutation.md b/docs/zh-CN/src/query_and_mutation.md deleted file mode 100644 index bb60f72d6..000000000 --- a/docs/zh-CN/src/query_and_mutation.md +++ /dev/null @@ -1,49 +0,0 @@ -# 查询和变更 - -## 查询根对象 - -查询根对象是一个 GraphQL 对象,定义类似其它对象。查询对象的所有字段 Resolver 函数是并发执行的。 - -```rust -# extern crate async_graphql; -use async_graphql::*; -# #[derive(SimpleObject)] -# struct User { a: i32 } - -struct Query; - -#[Object] -impl Query { - async fn user(&self, username: String) -> Result<Option<User>> { - // 在数据库中查找用户 -# todo!() - } -} - -``` - -## 变更根对象 - -变更根对象也是一个 GraphQL,但变更根对象的执行是顺序的,只有第一个变更执行完成之后才会执行下一个。 - -下面的变更根对象提供用户注册和登录操作: - -```rust -# extern crate async_graphql; -use async_graphql::*; - -struct Mutation; - -#[Object] -impl Mutation { - async fn signup(&self, username: String, password: String) -> Result<bool> { - // 用户注册 -# todo!() - } - - async fn login(&self, username: String, password: String) -> Result<String> { - // 用户登录并生成 token -# todo!() - } -} -``` diff --git a/docs/zh-CN/src/quickstart.md b/docs/zh-CN/src/quickstart.md deleted file mode 100644 index c7c52a111..000000000 --- a/docs/zh-CN/src/quickstart.md +++ /dev/null @@ -1,64 +0,0 @@ -# 快速开始 - -## 添加依赖 - -```toml -[dependencies] -async-graphql = "4.0" -async-graphql-actix-web = "4.0" # 如果你需要集成到 Actix-web -async-graphql-warp = "4.0" # 如果你需要集成到 Warp -async-graphql-tide = "4.0" # 如果你需要集成到 Tide -``` - -## 写一个 Schema - -一个 GraphQL 的 Schema 包含一个必须的查询 (Query) 根对象,可选的变更 (Mutation) 根对象和可选的订阅 (Subscription) 根对象,这些对象类型都是用 Rust 语言的结构来描述它们,结构的字段对应 GraphQL 对象的字段。 - -Async-graphql 实现了常用数据类型到 GraphQL 类型的映射,例如`i32`, `f64`, `Option<T>`, `Vec<T>`等。同时,你也能够[扩展这些基础类型](custom_scalars.md),基础数据类型在 GraphQL 里面称为标量。 - -下面是一个简单的例子,我们只提供一个查询,返回`a`和`b`的和。 - -```rust -# extern crate async_graphql; -use async_graphql::*; - -struct Query; - -#[Object] -impl Query { - /// Returns the sum of a and b - async fn add(&self, a: i32, b: i32) -> i32 { - a + b - } -} - -``` - -## 执行查询 - -在我们这个例子里面,只有 Query,没有 Mutation 和 Subscription,所以我们用`EmptyMutation`和`EmptySubscription`来创建 Schema,然后调用`Schema::execute`来执行查询。 - -```rust -# extern crate async_graphql; -# use async_graphql::*; -# -# struct Query; -# #[Object] -# impl Query { -# async fn version(&self) -> &str { "1.0" } -# } -# async fn other() { -let schema = Schema::new(Query, EmptyMutation, EmptySubscription); -let res = schema.execute("{ add(a: 10, b: 20) }").await; -# } -``` - -## 把查询结果输出为 JSON - -```rust,ignore -let json = serde_json::to_string(&res); -``` - -## 和 Web Server 的集成 - -请参考 https://github.com/async-graphql/examples。 diff --git a/docs/zh-CN/src/subscription.md b/docs/zh-CN/src/subscription.md deleted file mode 100644 index a1a70f164..000000000 --- a/docs/zh-CN/src/subscription.md +++ /dev/null @@ -1,29 +0,0 @@ -# 订阅 - -订阅根对象和其它根对象定义稍有不同,它的 Resolver 函数总是返回一个 [Stream](https://docs.rs/futures-core/~0.3/futures_core/stream/trait.Stream.html) 或者`Result<Stream>`,而字段参数通常作为数据的筛选条件。 - -下面的例子订阅一个整数流,它每秒产生一个整数,参数`step`指定了整数的步长,默认为 1。 - -```rust -# extern crate async_graphql; -# use std::time::Duration; -# use async_graphql::futures_util::stream::Stream; -# use async_graphql::futures_util::StreamExt; -# extern crate tokio_stream; -# extern crate tokio; -use async_graphql::*; - -struct Subscription; - -#[Subscription] -impl Subscription { - async fn integers(&self, #[graphql(default = 1)] step: i32) -> impl Stream<Item = i32> { - let mut value = 0; - tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(Duration::from_secs(1))) - .map(move |_| { - value += step; - value - }) - } -} -``` diff --git a/docs/zh-CN/src/typesystem.md b/docs/zh-CN/src/typesystem.md deleted file mode 100644 index 26ea8c96f..000000000 --- a/docs/zh-CN/src/typesystem.md +++ /dev/null @@ -1,3 +0,0 @@ -# 类型系统 - -`Async-graphql`包含 GraphQL 类型到 Rust 类型的完整实现,并且非常易于使用。 \ No newline at end of file diff --git a/docs/zh-CN/src/utilities.md b/docs/zh-CN/src/utilities.md deleted file mode 100644 index 2a7d258d8..000000000 --- a/docs/zh-CN/src/utilities.md +++ /dev/null @@ -1 +0,0 @@ -# 实用功能 diff --git a/docs/zh-CN/src/visibility.md b/docs/zh-CN/src/visibility.md deleted file mode 100644 index 7539966ab..000000000 --- a/docs/zh-CN/src/visibility.md +++ /dev/null @@ -1,43 +0,0 @@ -# 在内省中隐藏内容 - -默认情况下,所有类型,字段在内省中都是可见的。但可能你希望根据不同的用户来隐藏一些信息,避免引起不必要的误会。你可以在类型或者字段上添加`visible`属性来做到。 - -```rust -# extern crate async_graphql; -use async_graphql::*; - -#[derive(SimpleObject)] -struct MyObj { - // 这个字段将在内省中可见 - a: i32, - - // 这个字段在内省中总是隐藏 - #[graphql(visible = false)] - b: i32, - - // 这个字段调用 `is_admin` 函数,如果函数的返回值为 `true` 则可见 - #[graphql(visible = "is_admin")] - c: i32, -} - -#[derive(Enum, Copy, Clone, Eq, PartialEq)] -enum MyEnum { - // 这个项目将在内省中可见 - A, - - // 这个项目在内省中总是隐藏 - #[graphql(visible = false)] - B, - - // 这个项目调用 `is_admin` 函数,如果函数的返回值为 `true` 则可见 - #[graphql(visible = "is_admin")] - C, -} - -struct IsAdmin(bool); - -fn is_admin(ctx: &Context<'_>) -> bool { - ctx.data_unchecked::<IsAdmin>().0 -} - -``` \ No newline at end of file diff --git a/en/.nojekyll b/en/.nojekyll new file mode 100644 index 000000000..f17311098 --- /dev/null +++ b/en/.nojekyll @@ -0,0 +1 @@ +This file makes sure that Github Pages doesn't process mdBook's output. diff --git a/en/404.html b/en/404.html new file mode 100644 index 000000000..bce711637 --- /dev/null +++ b/en/404.html @@ -0,0 +1,211 @@ +<!DOCTYPE HTML> +<html lang="en" class="light sidebar-visible" dir="ltr"> + <head> + <!-- Book generated using mdBook --> + <meta charset="UTF-8"> + <title>Page not found - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Document not found (404)

+

This URL is invalid, sorry. Please use the navigation bar or search to continue.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/FontAwesome/css/font-awesome.css b/en/FontAwesome/css/font-awesome.css new file mode 100644 index 000000000..540440ce8 --- /dev/null +++ b/en/FontAwesome/css/font-awesome.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/en/FontAwesome/fonts/FontAwesome.ttf b/en/FontAwesome/fonts/FontAwesome.ttf new file mode 100644 index 000000000..35acda2fa Binary files /dev/null and b/en/FontAwesome/fonts/FontAwesome.ttf differ diff --git a/en/FontAwesome/fonts/fontawesome-webfont.eot b/en/FontAwesome/fonts/fontawesome-webfont.eot new file mode 100644 index 000000000..e9f60ca95 Binary files /dev/null and b/en/FontAwesome/fonts/fontawesome-webfont.eot differ diff --git a/en/FontAwesome/fonts/fontawesome-webfont.svg b/en/FontAwesome/fonts/fontawesome-webfont.svg new file mode 100644 index 000000000..855c845e5 --- /dev/null +++ b/en/FontAwesome/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/en/FontAwesome/fonts/fontawesome-webfont.ttf b/en/FontAwesome/fonts/fontawesome-webfont.ttf new file mode 100644 index 000000000..35acda2fa Binary files /dev/null and b/en/FontAwesome/fonts/fontawesome-webfont.ttf differ diff --git a/en/FontAwesome/fonts/fontawesome-webfont.woff b/en/FontAwesome/fonts/fontawesome-webfont.woff new file mode 100644 index 000000000..400014a4b Binary files /dev/null and b/en/FontAwesome/fonts/fontawesome-webfont.woff differ diff --git a/en/FontAwesome/fonts/fontawesome-webfont.woff2 b/en/FontAwesome/fonts/fontawesome-webfont.woff2 new file mode 100644 index 000000000..4d13fc604 Binary files /dev/null and b/en/FontAwesome/fonts/fontawesome-webfont.woff2 differ diff --git a/en/advanced_topics.html b/en/advanced_topics.html new file mode 100644 index 000000000..d0610b3c6 --- /dev/null +++ b/en/advanced_topics.html @@ -0,0 +1,221 @@ + + + + + + Advanced topics - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Advanced topics

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/apollo_federation.html b/en/apollo_federation.html new file mode 100644 index 000000000..2ca96b578 --- /dev/null +++ b/en/apollo_federation.html @@ -0,0 +1,660 @@ + + + + + + Apollo Federation - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Apollo Federation

+

Apollo Federation is a GraphQL architecture for combining multiple GraphQL services, or subgraphs, into a single supergraph. You can read more in the official documentation.

+
+

To see a complete example of federation, check out the federation example.

+
+

Enabling federation support

+

async-graphql supports all the functionality of Apollo Federation v2. Support will be enabled automatically if any #[graphql(entity)] resolvers are found in the schema. To enable it manually, use the enable_federation method on the SchemaBuilder.

+
extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query {
+   async fn hello(&self) -> String { "Hello".to_string() }
+}
+fn main() {
+  let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
+    .enable_federation()
+    .finish();
+  // ... Start your server of choice
+}
+

This will define the @link directive on your schema to enable Federation v2.

+

Entities and @key

+

Entities are a core feature of federation, they allow multiple subgraphs to contribute fields to the same type. An entity is a GraphQL type with at least one @key directive. To create a @key for a type, create a reference resolver using the #[graphql(entity)] attribute. This resolver should be defined on the Query struct, but will not appear as a field in the schema.

+
+

Even though a reference resolver looks up an individual entity, it is crucial that you use a dataloader in the implementation. The federation router will look up entities in batches, which can quickly lead the N+1 performance issues.

+
+

Example

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct User { id: ID }
+struct Query;
+
+#[Object]
+impl Query {
+    #[graphql(entity)]
+    async fn find_user_by_id(&self, id: ID) -> User {
+        User { id }
+    }
+
+    #[graphql(entity)]
+    async fn find_user_by_id_with_username(&self, #[graphql(key)] id: ID, username: String) -> User {
+        User { id }
+    }
+
+    #[graphql(entity)]
+    async fn find_user_by_id_and_username(&self, id: ID, username: String) -> User {
+        User { id }
+    }
+}
+}
+

Notice the difference between these three lookup functions, which are all looking for the User object.

+
    +
  • +

    find_user_by_id: Use id to find a User object, the key for User is id.

    +
  • +
  • +

    find_user_by_id_with_username: Use id to find an User object, the key for User is id, and the username field value of the User object is requested (e.g., via @external and @requires).

    +
  • +
  • +

    find_user_by_id_and_username: Use id and username to find an User object, the keys for User are id and username.

    +
  • +
+

The resulting schema will look like this:

+
type Query {
+  # These fields will not be exposed to users, they are only used by the router to resolve entities
+  _entities(representations: [_Any!]!): [_Entity]!
+  _service: _Service!
+}
+
+type User @key(fields: "id") @key(fields: "id username") {
+  id: ID!
+}
+
+

Defining a compound primary key

+

A single primary key can consist of multiple fields, and even nested fields, you can use InputObject to implements a nested primary key.

+

In the following example, the primary key of the User object is key { a b }.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct User { key: Key }
+#[derive(SimpleObject)]
+struct Key { a: i32, b: i32 }
+#[derive(InputObject)]
+struct NestedKey {
+  a: i32,
+  b: i32,
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+  #[graphql(entity)]
+  async fn find_user_by_key(&self, key: NestedKey) -> User {
+    let NestedKey { a, b } = key;
+    User { key: Key{a, b} }
+  }
+}
+}
+

The resulting schema will look like this:

+
type Query {
+  # These fields will not be exposed to users, they are only used by the router to resolve entities
+  _entities(representations: [_Any!]!): [_Entity]!
+  _service: _Service!
+}
+
+type User @key(fields: "key { a b }") {
+  key: Key!
+}
+
+type Key {
+  a: Int!
+  b: Int!
+}
+
+

Creating unresolvable entities

+

There are certain times when you need to reference an entity, but not add any fields to it. This is particularly useful when you want to link data from separate subgraphs together, but neither subgraph has all the data.

+

If you wanted to implement the products and reviews subgraphs example from the Apollo Docs, you would create the following types for the reviews subgraph:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct Review {
+    product: Product,
+    score: u64,
+}
+
+#[derive(SimpleObject)]
+#[graphql(unresolvable)]
+struct Product {
+    id: u64,
+}
+}
+

This will add the @key(fields: "id", resolvable: false) directive to the Product type in the reviews subgraph.

+

For more complex entity keys, such as ones with nested fields in compound keys, you can override the fields in the directive as so:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+#[graphql(unresolvable = "id organization { id }")]
+struct User {
+    id: u64,
+    organization: Organization,
+}
+
+#[derive(SimpleObject)]
+struct Organization {
+    id: u64,
+}
+}
+

However, it is important to note that no validation will be done to check that these fields exist.

+

@shareable

+

Apply the @shareable directive to a type or field to indicate that multiple subgraphs can resolve it.

+

@shareable fields

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+#[graphql(complex)]
+struct Position {
+  #[graphql(shareable)]
+  x: u64,
+}
+
+#[ComplexObject]
+impl Position {
+  #[graphql(shareable)]
+  async fn y(&self) -> u64 {
+    0
+  }
+}
+}
+

The resulting schema will look like this:

+
type Position {
+  x: Int! @shareable
+  y: Int! @shareable
+}
+
+

@shareable type

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+#[graphql(shareable)]
+struct Position {
+  x: u64,
+  y: u64,
+}
+}
+

The resulting schema will look like this:

+
type Position @shareable {
+  x: Int!
+  y: Int!
+}
+
+

@inaccessible

+

The @inaccessible directive is used to omit something from the supergraph schema (e.g., if it's not yet added to all subgraphs which share a @shareable type).

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+#[graphql(shareable)]
+struct Position {
+  x: u32,
+  y: u32,
+  #[graphql(inaccessible)]
+  z: u32,
+} 
+}
+

Results in:

+
type Position @shareable {
+  x: Int!
+  y: Int!
+  z: Int! @inaccessible
+}
+
+

@override

+

The @override directive is used to take ownership of a field from another subgraph. This is useful for migrating a field from one subgraph to another.

+

For example, if you add a new "Inventory" subgraph which should take over responsibility for the inStock field currently provided by the "Products" subgraph, you might have something like this:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct Product {
+  id: ID,
+  #[graphql(override_from = "Products")]
+  in_stock: bool,
+}
+}
+

Which results in:

+
type Product @key(fields: "id") {
+  id: ID!
+  inStock: Boolean! @override(from: "Products")
+}
+
+

@external

+

The @external directive is used to indicate that a field is usually provided by another subgraph, but is sometimes required by this subgraph (when combined with @requires) or provided by this subgraph (when combined with @provides).

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct Product {
+  id: ID,
+  #[graphql(external)]
+  name: String,
+  in_stock: bool,
+}
+}
+

Results in:

+
type Product {
+  id: ID!
+  name: String! @external
+  inStock: Boolean!
+}
+
+

@provides

+

The @provides directive is used to indicate that a field is provided by this subgraph, but only sometimes.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct Product {
+    id: ID,
+    #[graphql(external)]
+    human_name: String,
+    in_stock: bool,
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    /// This operation will provide the `humanName` field on `Product
+    #[graphql(provides = "humanName")]
+    async fn out_of_stock_products(&self) -> Vec<Product> {
+      vec![Product {
+        id: "1".into(),
+        human_name: "My Product".to_string(),
+        in_stock: false,
+      }]
+    }
+    async fn discontinued_products(&self) -> Vec<Product> {
+        vec![Product {
+            id: "2".into(),
+            human_name: String::new(),  // This is ignored by the router
+            in_stock: false,
+        }]
+    }
+    #[graphql(entity)]
+    async fn find_product_by_id(&self, id: ID) -> Product {
+        Product {
+            id,
+            human_name: String::new(),  // This is ignored by the router
+            in_stock: true,
+        }
+    }
+}
+}
+

Note that the #[graphql(provides)] attribute takes the field name as it appears in the schema, not the Rust field name.

+

The resulting schema will look like this:

+
type Product @key(fields: "id") {
+    id: ID!
+    humanName: String! @external
+    inStock: Boolean!
+}
+
+type Query {
+    outOfStockProducts: [Product!]! @provides(fields: "humanName")
+    discontinuedProducts: [Product!]!
+}
+
+

@requires

+

The @requires directive is used to indicate that an @external field is required for this subgraph to resolve some other field(s). If our shippingEstimate field requires the size and weightInPounts fields, then we might want a subgraph entity which looks like this:

+
type Product @key(fields: "id") {
+  id: ID!
+  size: Int! @external
+  weightInPounds: Int! @external
+  shippingEstimate: String! @requires(fields: "size weightInPounds")
+}
+
+

In order to implement this in Rust, we can use the #[graphql(requires)] attribute:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+#[graphql(complex)]
+struct Product {
+  id: ID,
+  #[graphql(external)]
+  size: u32,
+  #[graphql(external)]
+  weight_in_pounds: u32,
+}
+
+#[ComplexObject]
+impl Product {
+  #[graphql(requires = "size weightInPounds")]
+  async fn shipping_estimate(&self) -> String {
+    let price = self.size * self.weight_in_pounds;
+    format!("${}", price)
+  }
+}
+}
+

Note that we use the GraphQL field name weightInPounds, not the Rust field name weight_in_pounds in requires. To populate those external fields, we add them as arguments in the entity resolver:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct Product {
+    id: ID,
+    #[graphql(external)]
+    size: u32,
+    #[graphql(external)]
+    weight_in_pounds: u32,
+}
+struct Query;
+#[Object]
+impl Query {
+  #[graphql(entity)]
+  async fn find_product_by_id(
+    &self, 
+    #[graphql(key)] id: ID, 
+    size: Option<u32>, 
+    weight_in_pounds: Option<u32>
+  ) -> Product {
+    Product {
+      id,
+      size: size.unwrap_or_default(),
+      weight_in_pounds: weight_in_pounds.unwrap_or_default(),
+    }
+  }
+}
+}
+

The inputs are Option<> even though the fields are required. This is because the external fields are only passed to the subgraph when the field(s) that require them are being selected. If the shippingEstimate field is not selected, then the size and weightInPounds fields will not be passed to the subgraph. Always use optional types for external fields.

+

We have to put something in place for size and weight_in_pounds as they are still required fields on the type, so we use unwrap_or_default() to provide a default value. This looks a little funny, as we're populating the fields with nonsense values, but we have confidence that they will not be needed if they were not provided. Make sure to use @requires if you are consuming @external fields, or your code will be wrong.

+

Nested @requires

+

A case where the @requires directive can be confusing is when there are nested entities. For example, if we had an Order type which contained a Product, then we would need an entity resolver like this:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+pub struct Order { id: ID }
+struct Query;
+#[Object]
+impl Query {
+  #[graphql(entity)]
+  async fn find_order_by_id(&self, id: ID) -> Option<Order> {
+      Some(Order { id })
+  }
+}
+}
+

There are no inputs on this entity resolver, so how do we populate the size and weight_in_pounds fields on Product if a user has a query like order { product { shippingEstimate } }? The supergraph implementation will solve this for us by calling the find_product_by_id separately for any fields which have a @requires directive, so the subgraph code does not need to worry about how entities relate.

+

@tag

+

The @tag directive is used to add metadata to a schema location for features like contracts. To add a tag like this:

+
type User @tag(name: "team-accounts") {
+  id: String!
+  name: String!
+}
+
+

You can write code like this:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+#[graphql(tag = "team-accounts")]
+struct User {
+  id: ID,
+  name: String,
+}
+}
+

@composeDirective

+

The @composeDirective directive is used to add a custom type system directive to the supergraph schema. Without @composeDirective, and custom type system directives are omitted from the composed supergraph schema. To include a custom type system directive as a composed directive, just add the composable attribute to the #[TypeDirective] macro:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[TypeDirective(
+    location = "Object",
+    composable = "https://custom.spec.dev/extension/v1.0",
+)]
+fn custom() {}
+}
+

In addition to the normal type system directive behavior, this will add the following bits to the output schema:

+
extend schema @link(
+	url: "https://custom.spec.dev/extension/v1.0"
+	import: ["@custom"]
+)
+	@composeDirective(name: "@custom")
+
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/apollo_tracing.html b/en/apollo_tracing.html new file mode 100644 index 000000000..37b832dd4 --- /dev/null +++ b/en/apollo_tracing.html @@ -0,0 +1,237 @@ + + + + + + Apollo Tracing - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Apollo Tracing

+

Apollo Tracing provides performance analysis results for each step of query. This is an extension to Schema, and the performance analysis results are stored in QueryResponse.

+

To enable the Apollo Tracing extension, add the extension when the Schema is created.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::ApolloTracing;
+
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+
+let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
+    .extension(ApolloTracing) // Enable ApolloTracing extension
+    .finish();
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/ayu-highlight.css b/en/ayu-highlight.css new file mode 100644 index 000000000..32c943222 --- /dev/null +++ b/en/ayu-highlight.css @@ -0,0 +1,78 @@ +/* +Based off of the Ayu theme +Original by Dempfi (https://github.com/dempfi/ayu) +*/ + +.hljs { + display: block; + overflow-x: auto; + background: #191f26; + color: #e6e1cf; +} + +.hljs-comment, +.hljs-quote { + color: #5c6773; + font-style: italic; +} + +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-attr, +.hljs-regexp, +.hljs-link, +.hljs-selector-id, +.hljs-selector-class { + color: #ff7733; +} + +.hljs-number, +.hljs-meta, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #ffee99; +} + +.hljs-string, +.hljs-bullet { + color: #b8cc52; +} + +.hljs-title, +.hljs-built_in, +.hljs-section { + color: #ffb454; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-symbol { + color: #ff7733; +} + +.hljs-name { + color: #36a3d9; +} + +.hljs-tag { + color: #00568d; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-addition { + color: #91b362; +} + +.hljs-deletion { + color: #d96c75; +} diff --git a/en/book.js b/en/book.js new file mode 100644 index 000000000..5df2096f7 --- /dev/null +++ b/en/book.js @@ -0,0 +1,818 @@ +'use strict'; + +/* global default_theme, default_dark_theme, default_light_theme, hljs, ClipboardJS */ + +// Fix back button cache problem +window.onunload = function() { }; + +// Global variable, shared between modules +function playground_text(playground, hidden = true) { + const code_block = playground.querySelector('code'); + + if (window.ace && code_block.classList.contains('editable')) { + const editor = window.ace.edit(code_block); + return editor.getValue(); + } else if (hidden) { + return code_block.textContent; + } else { + return code_block.innerText; + } +} + +(function codeSnippets() { + function fetch_with_timeout(url, options, timeout = 6000) { + return Promise.race([ + fetch(url, options), + new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout)), + ]); + } + + const playgrounds = Array.from(document.querySelectorAll('.playground')); + if (playgrounds.length > 0) { + fetch_with_timeout('https://play.rust-lang.org/meta/crates', { + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + mode: 'cors', + }) + .then(response => response.json()) + .then(response => { + // get list of crates available in the rust playground + const playground_crates = response.crates.map(item => item['id']); + playgrounds.forEach(block => handle_crate_list_update(block, playground_crates)); + }); + } + + function handle_crate_list_update(playground_block, playground_crates) { + // update the play buttons after receiving the response + update_play_button(playground_block, playground_crates); + + // and install on change listener to dynamically update ACE editors + if (window.ace) { + const code_block = playground_block.querySelector('code'); + if (code_block.classList.contains('editable')) { + const editor = window.ace.edit(code_block); + editor.addEventListener('change', () => { + update_play_button(playground_block, playground_crates); + }); + // add Ctrl-Enter command to execute rust code + editor.commands.addCommand({ + name: 'run', + bindKey: { + win: 'Ctrl-Enter', + mac: 'Ctrl-Enter', + }, + exec: _editor => run_rust_code(playground_block), + }); + } + } + } + + // updates the visibility of play button based on `no_run` class and + // used crates vs ones available on https://play.rust-lang.org + function update_play_button(pre_block, playground_crates) { + const play_button = pre_block.querySelector('.play-button'); + + // skip if code is `no_run` + if (pre_block.querySelector('code').classList.contains('no_run')) { + play_button.classList.add('hidden'); + return; + } + + // get list of `extern crate`'s from snippet + const txt = playground_text(pre_block); + const re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g; + const snippet_crates = []; + let item; + // eslint-disable-next-line no-cond-assign + while (item = re.exec(txt)) { + snippet_crates.push(item[1]); + } + + // check if all used crates are available on play.rust-lang.org + const all_available = snippet_crates.every(function(elem) { + return playground_crates.indexOf(elem) > -1; + }); + + if (all_available) { + play_button.classList.remove('hidden'); + } else { + play_button.classList.add('hidden'); + } + } + + function run_rust_code(code_block) { + let result_block = code_block.querySelector('.result'); + if (!result_block) { + result_block = document.createElement('code'); + result_block.className = 'result hljs language-bash'; + + code_block.append(result_block); + } + + const text = playground_text(code_block); + const classes = code_block.querySelector('code').classList; + let edition = '2015'; + classes.forEach(className => { + if (className.startsWith('edition')) { + edition = className.slice(7); + } + }); + const params = { + version: 'stable', + optimize: '0', + code: text, + edition: edition, + }; + + if (text.indexOf('#![feature') !== -1) { + params.version = 'nightly'; + } + + result_block.innerText = 'Running...'; + + fetch_with_timeout('https://play.rust-lang.org/evaluate.json', { + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + mode: 'cors', + body: JSON.stringify(params), + }) + .then(response => response.json()) + .then(response => { + if (response.result.trim() === '') { + result_block.innerText = 'No output'; + result_block.classList.add('result-no-output'); + } else { + result_block.innerText = response.result; + result_block.classList.remove('result-no-output'); + } + }) + .catch(error => result_block.innerText = 'Playground Communication: ' + error.message); + } + + // Syntax highlighting Configuration + hljs.configure({ + tabReplace: ' ', // 4 spaces + languages: [], // Languages used for auto-detection + }); + + const code_nodes = Array + .from(document.querySelectorAll('code')) + // Don't highlight `inline code` blocks in headers. + .filter(function(node) { + return !node.parentElement.classList.contains('header'); + }); + + if (window.ace) { + // language-rust class needs to be removed for editable + // blocks or highlightjs will capture events + code_nodes + .filter(function(node) { + return node.classList.contains('editable'); + }) + .forEach(function(block) { + block.classList.remove('language-rust'); + }); + + code_nodes + .filter(function(node) { + return !node.classList.contains('editable'); + }) + .forEach(function(block) { + hljs.highlightBlock(block); + }); + } else { + code_nodes.forEach(function(block) { + hljs.highlightBlock(block); + }); + } + + // Adding the hljs class gives code blocks the color css + // even if highlighting doesn't apply + code_nodes.forEach(function(block) { + block.classList.add('hljs'); + }); + + Array.from(document.querySelectorAll('code.hljs')).forEach(function(block) { + + const lines = Array.from(block.querySelectorAll('.boring')); + // If no lines were hidden, return + if (!lines.length) { + return; + } + block.classList.add('hide-boring'); + + const buttons = document.createElement('div'); + buttons.className = 'buttons'; + buttons.innerHTML = ''; + + // add expand button + const pre_block = block.parentNode; + pre_block.insertBefore(buttons, pre_block.firstChild); + + pre_block.querySelector('.buttons').addEventListener('click', function(e) { + if (e.target.classList.contains('fa-eye')) { + e.target.classList.remove('fa-eye'); + e.target.classList.add('fa-eye-slash'); + e.target.title = 'Hide lines'; + e.target.setAttribute('aria-label', e.target.title); + + block.classList.remove('hide-boring'); + } else if (e.target.classList.contains('fa-eye-slash')) { + e.target.classList.remove('fa-eye-slash'); + e.target.classList.add('fa-eye'); + e.target.title = 'Show hidden lines'; + e.target.setAttribute('aria-label', e.target.title); + + block.classList.add('hide-boring'); + } + }); + }); + + if (window.playground_copyable) { + Array.from(document.querySelectorAll('pre code')).forEach(function(block) { + const pre_block = block.parentNode; + if (!pre_block.classList.contains('playground')) { + let buttons = pre_block.querySelector('.buttons'); + if (!buttons) { + buttons = document.createElement('div'); + buttons.className = 'buttons'; + pre_block.insertBefore(buttons, pre_block.firstChild); + } + + const clipButton = document.createElement('button'); + clipButton.className = 'clip-button'; + clipButton.title = 'Copy to clipboard'; + clipButton.setAttribute('aria-label', clipButton.title); + clipButton.innerHTML = ''; + + buttons.insertBefore(clipButton, buttons.firstChild); + } + }); + } + + // Process playground code blocks + Array.from(document.querySelectorAll('.playground')).forEach(function(pre_block) { + // Add play button + let buttons = pre_block.querySelector('.buttons'); + if (!buttons) { + buttons = document.createElement('div'); + buttons.className = 'buttons'; + pre_block.insertBefore(buttons, pre_block.firstChild); + } + + const runCodeButton = document.createElement('button'); + runCodeButton.className = 'fa fa-play play-button'; + runCodeButton.hidden = true; + runCodeButton.title = 'Run this code'; + runCodeButton.setAttribute('aria-label', runCodeButton.title); + + buttons.insertBefore(runCodeButton, buttons.firstChild); + runCodeButton.addEventListener('click', () => { + run_rust_code(pre_block); + }); + + if (window.playground_copyable) { + const copyCodeClipboardButton = document.createElement('button'); + copyCodeClipboardButton.className = 'clip-button'; + copyCodeClipboardButton.innerHTML = ''; + copyCodeClipboardButton.title = 'Copy to clipboard'; + copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title); + + buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild); + } + + const code_block = pre_block.querySelector('code'); + if (window.ace && code_block.classList.contains('editable')) { + const undoChangesButton = document.createElement('button'); + undoChangesButton.className = 'fa fa-history reset-button'; + undoChangesButton.title = 'Undo changes'; + undoChangesButton.setAttribute('aria-label', undoChangesButton.title); + + buttons.insertBefore(undoChangesButton, buttons.firstChild); + + undoChangesButton.addEventListener('click', function() { + const editor = window.ace.edit(code_block); + editor.setValue(editor.originalCode); + editor.clearSelection(); + }); + } + }); +})(); + +(function themes() { + const html = document.querySelector('html'); + const themeToggleButton = document.getElementById('theme-toggle'); + const themePopup = document.getElementById('theme-list'); + const themeColorMetaTag = document.querySelector('meta[name="theme-color"]'); + const themeIds = []; + themePopup.querySelectorAll('button.theme').forEach(function(el) { + themeIds.push(el.id); + }); + const stylesheets = { + ayuHighlight: document.querySelector('#ayu-highlight-css'), + tomorrowNight: document.querySelector('#tomorrow-night-css'), + highlight: document.querySelector('#highlight-css'), + }; + + function showThemes() { + themePopup.style.display = 'block'; + themeToggleButton.setAttribute('aria-expanded', true); + themePopup.querySelector('button#' + get_theme()).focus(); + } + + function updateThemeSelected() { + themePopup.querySelectorAll('.theme-selected').forEach(function(el) { + el.classList.remove('theme-selected'); + }); + const selected = get_saved_theme() ?? 'default_theme'; + let element = themePopup.querySelector('button#' + selected); + if (element === null) { + // Fall back in case there is no "Default" item. + element = themePopup.querySelector('button#' + get_theme()); + } + element.classList.add('theme-selected'); + } + + function hideThemes() { + themePopup.style.display = 'none'; + themeToggleButton.setAttribute('aria-expanded', false); + themeToggleButton.focus(); + } + + function get_saved_theme() { + let theme = null; + try { + theme = localStorage.getItem('mdbook-theme'); + } catch (e) { + // ignore error. + } + return theme; + } + + function delete_saved_theme() { + localStorage.removeItem('mdbook-theme'); + } + + function get_theme() { + const theme = get_saved_theme(); + if (theme === null || theme === undefined || !themeIds.includes(theme)) { + if (typeof default_dark_theme === 'undefined') { + // A customized index.hbs might not define this, so fall back to + // old behavior of determining the default on page load. + return default_theme; + } + return window.matchMedia('(prefers-color-scheme: dark)').matches + ? default_dark_theme + : default_light_theme; + } else { + return theme; + } + } + + let previousTheme = default_theme; + function set_theme(theme, store = true) { + let ace_theme; + + if (theme === 'coal' || theme === 'navy') { + stylesheets.ayuHighlight.disabled = true; + stylesheets.tomorrowNight.disabled = false; + stylesheets.highlight.disabled = true; + + ace_theme = 'ace/theme/tomorrow_night'; + } else if (theme === 'ayu') { + stylesheets.ayuHighlight.disabled = false; + stylesheets.tomorrowNight.disabled = true; + stylesheets.highlight.disabled = true; + ace_theme = 'ace/theme/tomorrow_night'; + } else { + stylesheets.ayuHighlight.disabled = true; + stylesheets.tomorrowNight.disabled = true; + stylesheets.highlight.disabled = false; + ace_theme = 'ace/theme/dawn'; + } + + setTimeout(function() { + themeColorMetaTag.content = getComputedStyle(document.documentElement).backgroundColor; + }, 1); + + if (window.ace && window.editors) { + window.editors.forEach(function(editor) { + editor.setTheme(ace_theme); + }); + } + + if (store) { + try { + localStorage.setItem('mdbook-theme', theme); + } catch (e) { + // ignore error. + } + } + + html.classList.remove(previousTheme); + html.classList.add(theme); + previousTheme = theme; + updateThemeSelected(); + } + + const query = window.matchMedia('(prefers-color-scheme: dark)'); + query.onchange = function() { + set_theme(get_theme(), false); + }; + + // Set theme. + set_theme(get_theme(), false); + + themeToggleButton.addEventListener('click', function() { + if (themePopup.style.display === 'block') { + hideThemes(); + } else { + showThemes(); + } + }); + + themePopup.addEventListener('click', function(e) { + let theme; + if (e.target.className === 'theme') { + theme = e.target.id; + } else if (e.target.parentElement.className === 'theme') { + theme = e.target.parentElement.id; + } else { + return; + } + if (theme === 'default_theme' || theme === null) { + delete_saved_theme(); + set_theme(get_theme(), false); + } else { + set_theme(theme); + } + }); + + themePopup.addEventListener('focusout', function(e) { + // e.relatedTarget is null in Safari and Firefox on macOS (see workaround below) + if (!!e.relatedTarget && + !themeToggleButton.contains(e.relatedTarget) && + !themePopup.contains(e.relatedTarget) + ) { + hideThemes(); + } + }); + + // Should not be needed, but it works around an issue on macOS & iOS: + // https://github.com/rust-lang/mdBook/issues/628 + document.addEventListener('click', function(e) { + if (themePopup.style.display === 'block' && + !themeToggleButton.contains(e.target) && + !themePopup.contains(e.target) + ) { + hideThemes(); + } + }); + + document.addEventListener('keydown', function(e) { + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { + return; + } + if (!themePopup.contains(e.target)) { + return; + } + + let li; + switch (e.key) { + case 'Escape': + e.preventDefault(); + hideThemes(); + break; + case 'ArrowUp': + e.preventDefault(); + li = document.activeElement.parentElement; + if (li && li.previousElementSibling) { + li.previousElementSibling.querySelector('button').focus(); + } + break; + case 'ArrowDown': + e.preventDefault(); + li = document.activeElement.parentElement; + if (li && li.nextElementSibling) { + li.nextElementSibling.querySelector('button').focus(); + } + break; + case 'Home': + e.preventDefault(); + themePopup.querySelector('li:first-child button').focus(); + break; + case 'End': + e.preventDefault(); + themePopup.querySelector('li:last-child button').focus(); + break; + } + }); +})(); + +(function sidebar() { + const body = document.querySelector('body'); + const sidebar = document.getElementById('sidebar'); + const sidebarLinks = document.querySelectorAll('#sidebar a'); + const sidebarToggleButton = document.getElementById('sidebar-toggle'); + const sidebarToggleAnchor = document.getElementById('sidebar-toggle-anchor'); + const sidebarResizeHandle = document.getElementById('sidebar-resize-handle'); + let firstContact = null; + + function showSidebar() { + body.classList.remove('sidebar-hidden'); + body.classList.add('sidebar-visible'); + Array.from(sidebarLinks).forEach(function(link) { + link.setAttribute('tabIndex', 0); + }); + sidebarToggleButton.setAttribute('aria-expanded', true); + sidebar.setAttribute('aria-hidden', false); + try { + localStorage.setItem('mdbook-sidebar', 'visible'); + } catch (e) { + // Ignore error. + } + } + + function hideSidebar() { + body.classList.remove('sidebar-visible'); + body.classList.add('sidebar-hidden'); + Array.from(sidebarLinks).forEach(function(link) { + link.setAttribute('tabIndex', -1); + }); + sidebarToggleButton.setAttribute('aria-expanded', false); + sidebar.setAttribute('aria-hidden', true); + try { + localStorage.setItem('mdbook-sidebar', 'hidden'); + } catch (e) { + // Ignore error. + } + } + + // Toggle sidebar + sidebarToggleAnchor.addEventListener('change', function sidebarToggle() { + if (sidebarToggleAnchor.checked) { + const current_width = parseInt( + document.documentElement.style.getPropertyValue('--sidebar-target-width'), 10); + if (current_width < 150) { + document.documentElement.style.setProperty('--sidebar-target-width', '150px'); + } + showSidebar(); + } else { + hideSidebar(); + } + }); + + sidebarResizeHandle.addEventListener('mousedown', initResize, false); + + function initResize() { + window.addEventListener('mousemove', resize, false); + window.addEventListener('mouseup', stopResize, false); + body.classList.add('sidebar-resizing'); + } + function resize(e) { + let pos = e.clientX - sidebar.offsetLeft; + if (pos < 20) { + hideSidebar(); + } else { + if (body.classList.contains('sidebar-hidden')) { + showSidebar(); + } + pos = Math.min(pos, window.innerWidth - 100); + document.documentElement.style.setProperty('--sidebar-target-width', pos + 'px'); + } + } + //on mouseup remove windows functions mousemove & mouseup + function stopResize() { + body.classList.remove('sidebar-resizing'); + window.removeEventListener('mousemove', resize, false); + window.removeEventListener('mouseup', stopResize, false); + } + + document.addEventListener('touchstart', function(e) { + firstContact = { + x: e.touches[0].clientX, + time: Date.now(), + }; + }, { passive: true }); + + document.addEventListener('touchmove', function(e) { + if (!firstContact) { + return; + } + + const curX = e.touches[0].clientX; + const xDiff = curX - firstContact.x, + tDiff = Date.now() - firstContact.time; + + if (tDiff < 250 && Math.abs(xDiff) >= 150) { + if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300)) { + showSidebar(); + } else if (xDiff < 0 && curX < 300) { + hideSidebar(); + } + + firstContact = null; + } + }, { passive: true }); +})(); + +(function chapterNavigation() { + document.addEventListener('keydown', function(e) { + if (e.altKey || e.ctrlKey || e.metaKey) { + return; + } + if (window.search && window.search.hasFocus()) { + return; + } + const html = document.querySelector('html'); + + function next() { + const nextButton = document.querySelector('.nav-chapters.next'); + if (nextButton) { + window.location.href = nextButton.href; + } + } + function prev() { + const previousButton = document.querySelector('.nav-chapters.previous'); + if (previousButton) { + window.location.href = previousButton.href; + } + } + function showHelp() { + const container = document.getElementById('mdbook-help-container'); + const overlay = document.getElementById('mdbook-help-popup'); + container.style.display = 'flex'; + + // Clicking outside the popup will dismiss it. + const mouseHandler = event => { + if (overlay.contains(event.target)) { + return; + } + if (event.button !== 0) { + return; + } + event.preventDefault(); + event.stopPropagation(); + document.removeEventListener('mousedown', mouseHandler); + hideHelp(); + }; + + // Pressing esc will dismiss the popup. + const escapeKeyHandler = event => { + if (event.key === 'Escape') { + event.preventDefault(); + event.stopPropagation(); + document.removeEventListener('keydown', escapeKeyHandler, true); + hideHelp(); + } + }; + document.addEventListener('keydown', escapeKeyHandler, true); + document.getElementById('mdbook-help-container') + .addEventListener('mousedown', mouseHandler); + } + function hideHelp() { + document.getElementById('mdbook-help-container').style.display = 'none'; + } + + // Usually needs the Shift key to be pressed + switch (e.key) { + case '?': + e.preventDefault(); + showHelp(); + break; + } + + // Rest of the keys are only active when the Shift key is not pressed + if (e.shiftKey) { + return; + } + + switch (e.key) { + case 'ArrowRight': + e.preventDefault(); + if (html.dir === 'rtl') { + prev(); + } else { + next(); + } + break; + case 'ArrowLeft': + e.preventDefault(); + if (html.dir === 'rtl') { + next(); + } else { + prev(); + } + break; + } + }); +})(); + +(function clipboard() { + const clipButtons = document.querySelectorAll('.clip-button'); + + function hideTooltip(elem) { + elem.firstChild.innerText = ''; + elem.className = 'clip-button'; + } + + function showTooltip(elem, msg) { + elem.firstChild.innerText = msg; + elem.className = 'clip-button tooltipped'; + } + + const clipboardSnippets = new ClipboardJS('.clip-button', { + text: function(trigger) { + hideTooltip(trigger); + const playground = trigger.closest('pre'); + return playground_text(playground, false); + }, + }); + + Array.from(clipButtons).forEach(function(clipButton) { + clipButton.addEventListener('mouseout', function(e) { + hideTooltip(e.currentTarget); + }); + }); + + clipboardSnippets.on('success', function(e) { + e.clearSelection(); + showTooltip(e.trigger, 'Copied!'); + }); + + clipboardSnippets.on('error', function(e) { + showTooltip(e.trigger, 'Clipboard error!'); + }); +})(); + +(function scrollToTop() { + const menuTitle = document.querySelector('.menu-title'); + + menuTitle.addEventListener('click', function() { + document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' }); + }); +})(); + +(function controllMenu() { + const menu = document.getElementById('menu-bar'); + + (function controllPosition() { + let scrollTop = document.scrollingElement.scrollTop; + let prevScrollTop = scrollTop; + const minMenuY = -menu.clientHeight - 50; + // When the script loads, the page can be at any scroll (e.g. if you reforesh it). + menu.style.top = scrollTop + 'px'; + // Same as parseInt(menu.style.top.slice(0, -2), but faster + let topCache = menu.style.top.slice(0, -2); + menu.classList.remove('sticky'); + let stickyCache = false; // Same as menu.classList.contains('sticky'), but faster + document.addEventListener('scroll', function() { + scrollTop = Math.max(document.scrollingElement.scrollTop, 0); + // `null` means that it doesn't need to be updated + let nextSticky = null; + let nextTop = null; + const scrollDown = scrollTop > prevScrollTop; + const menuPosAbsoluteY = topCache - scrollTop; + if (scrollDown) { + nextSticky = false; + if (menuPosAbsoluteY > 0) { + nextTop = prevScrollTop; + } + } else { + if (menuPosAbsoluteY > 0) { + nextSticky = true; + } else if (menuPosAbsoluteY < minMenuY) { + nextTop = prevScrollTop + minMenuY; + } + } + if (nextSticky === true && stickyCache === false) { + menu.classList.add('sticky'); + stickyCache = true; + } else if (nextSticky === false && stickyCache === true) { + menu.classList.remove('sticky'); + stickyCache = false; + } + if (nextTop !== null) { + menu.style.top = nextTop + 'px'; + topCache = nextTop; + } + prevScrollTop = scrollTop; + }, { passive: true }); + })(); + (function controllBorder() { + function updateBorder() { + if (menu.offsetTop === 0) { + menu.classList.remove('bordered'); + } else { + menu.classList.add('bordered'); + } + } + updateBorder(); + document.addEventListener('scroll', updateBorder, { passive: true }); + })(); +})(); diff --git a/en/cache_control.html b/en/cache_control.html new file mode 100644 index 000000000..c33edf4f4 --- /dev/null +++ b/en/cache_control.html @@ -0,0 +1,260 @@ + + + + + + Cache control - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Cache control

+

Production environments often rely on caching to improve performance.

+

A GraphQL query will call multiple resolver functions and each resolver can have a different cache definition. Some may cache for a few seconds, some may cache for a few hours, some may be the same for all users, and some may be different for each session.

+

Async-graphql provides a mechanism that allows you to define the cache time and scope for each resolver.

+

You can define cache parameters on the object or on its fields. The following example shows two uses of cache control parameters.

+

You can use max_age parameters to control the age of the cache (in seconds), and you can also use public and private to control the scope of the cache. When you do not specify it, the scope will default to public.

+

when querying multiple resolvers, the results of all cache control parameters will be combined and the max_age minimum value will be taken. If the scope of any object or field is private, the result will be private.

+

We can use QueryResponse to get a merged cache control result from a query result, and call CacheControl::value to get the corresponding HTTP header.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object(cache_control(max_age = 60))]
+impl Query {
+    #[graphql(cache_control(max_age = 30))]
+    async fn value1(&self) -> i32 {
+        1
+    }
+
+    #[graphql(cache_control(private))]
+    async fn value2(&self) -> i32 {
+        2
+    }
+
+    async fn value3(&self) -> i32 {
+        3
+    }
+}
+}
+

The following are different queries corresponding to different cache control results:

+
# max_age=30
+{ value1 }
+
+
# max_age=30, private
+{ value1 value2 }
+
+
# max_age=60
+{ value3 }
+
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/clipboard.min.js b/en/clipboard.min.js new file mode 100644 index 000000000..02c549e35 --- /dev/null +++ b/en/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.4 + * https://zenorocha.github.io/clipboard.js + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return function(n){var o={};function r(t){if(o[t])return o[t].exports;var e=o[t]={i:t,l:!1,exports:{}};return n[t].call(e.exports,e,e.exports,r),e.l=!0,e.exports}return r.m=n,r.c=o,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=0)}([function(t,e,n){"use strict";var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function o(t,e){for(var n=0;n + + + + + Context - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Context

+

The main goal of Context is to acquire global data attached to Schema and also data related to the actual query being processed.

+

Store Data

+

Inside the Context you can put global data, like environment variables, db connection pool, whatever you may need in every query.

+

The data must implement Send and Sync.

+

You can request the data inside a query by just calling ctx.data::<TypeOfYourData>().

+

Note that if the return value of resolver function is borrowed from Context, you will need to explicitly state the lifetime of the argument.

+

The following example shows how to borrow data in Context.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn borrow_from_context_data<'ctx>(
+        &self,
+        ctx: &Context<'ctx>
+    ) -> Result<&'ctx String> {
+        ctx.data::<String>()
+    }
+}
+}
+

Schema data

+

You can put data inside the context at the creation of the schema, it's useful for data that do not change, like a connection pool.

+

An instance of how it would be written inside an application:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Default,SimpleObject)]
+struct Query { version: i32}
+struct EnvStruct;
+let env_struct = EnvStruct;
+struct S3Object;
+let s3_storage = S3Object;
+struct DBConnection;
+let db_core = DBConnection;
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription)
+    .data(env_struct)
+    .data(s3_storage)
+    .data(db_core)
+    .finish();
+}
+

Request data

+

You can put data inside the context at the execution of the request, it's useful for authentication data for instance.

+

A little example with a warp route:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate async_graphql_warp;
+extern crate warp;
+use async_graphql::*;
+use warp::{Filter, Reply};
+use std::convert::Infallible;
+#[derive(Default, SimpleObject)]
+struct Query { name: String }
+struct AuthInfo { pub token: Option<String> }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+let schema_filter = async_graphql_warp::graphql(schema);
+let graphql_post = warp::post()
+  .and(warp::path("graphql"))
+  .and(warp::header::optional("Authorization"))
+  .and(schema_filter)
+  .and_then( |auth: Option<String>, (schema, mut request): (Schema<Query, EmptyMutation, EmptySubscription>, async_graphql::Request)| async move {
+    // Do something to get auth data from the header
+    let your_auth_data = AuthInfo { token: auth };
+    let response = schema
+      .execute(
+        request
+         .data(your_auth_data)
+      ).await;
+
+    Ok::<_, Infallible>(async_graphql_warp::GraphQLResponse::from(response))
+  });
+}
+

Headers

+

With the Context you can also insert and appends headers.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate http;
+use ::http::header::ACCESS_CONTROL_ALLOW_ORIGIN;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query {
+    async fn greet(&self, ctx: &Context<'_>) -> String {
+        // Headers can be inserted using the `http` constants
+        let was_in_headers = ctx.insert_http_header(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
+
+        // They can also be inserted using &str
+        let was_in_headers = ctx.insert_http_header("Custom-Header", "1234");
+
+        // If multiple headers with the same key are `inserted` then the most recent
+        // one overwrites the previous. If you want multiple headers for the same key, use
+        // `append_http_header` for subsequent headers
+        let was_in_headers = ctx.append_http_header("Custom-Header", "Hello World");
+
+        String::from("Hello world")
+    }
+}
+}
+

Selection / LookAhead

+

Sometimes you want to know what fields are requested in the subquery to optimize the processing of data. You can read fields across the query with ctx.field() which will give you a SelectionField which will allow you to navigate across the fields and subfields.

+

If you want to perform a search across the query or the subqueries, you do not have to do this by hand with the SelectionField, you can use the ctx.look_ahead() to perform a selection

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+#[derive(SimpleObject)]
+struct Detail {
+    c: i32,
+    d: i32,
+}
+
+#[derive(SimpleObject)]
+struct MyObj {
+    a: i32,
+    b: i32,
+    detail: Detail,
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn obj(&self, ctx: &Context<'_>) -> MyObj {
+        if ctx.look_ahead().field("a").exists() {
+            // This is a query like `obj { a }`
+        } else if ctx.look_ahead().field("detail").field("c").exists() {
+            // This is a query like `obj { detail { c } }`
+        } else {
+            // This query doesn't have `a`
+        }
+        unimplemented!()
+    }
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/css/chrome.css b/en/css/chrome.css new file mode 100644 index 000000000..360a65372 --- /dev/null +++ b/en/css/chrome.css @@ -0,0 +1,701 @@ +/* CSS for UI elements (a.k.a. chrome) */ + +html { + scrollbar-color: var(--scrollbar) var(--bg); +} +#searchresults a, +.content a:link, +a:visited, +a > .hljs { + color: var(--links); +} + +/* + body-container is necessary because mobile browsers don't seem to like + overflow-x on the body tag when there is a tag. +*/ +#body-container { + /* + This is used when the sidebar pushes the body content off the side of + the screen on small screens. Without it, dragging on mobile Safari + will want to reposition the viewport in a weird way. + */ + overflow-x: clip; +} + +/* Menu Bar */ + +#menu-bar, +#menu-bar-hover-placeholder { + z-index: 101; + margin: auto calc(0px - var(--page-padding)); +} +#menu-bar { + position: relative; + display: flex; + flex-wrap: wrap; + background-color: var(--bg); + border-block-end-color: var(--bg); + border-block-end-width: 1px; + border-block-end-style: solid; +} +#menu-bar.sticky, +#menu-bar-hover-placeholder:hover + #menu-bar, +#menu-bar:hover, +html.sidebar-visible #menu-bar { + position: -webkit-sticky; + position: sticky; + top: 0 !important; +} +#menu-bar-hover-placeholder { + position: sticky; + position: -webkit-sticky; + top: 0; + height: var(--menu-bar-height); +} +#menu-bar.bordered { + border-block-end-color: var(--table-border-color); +} +#menu-bar i, #menu-bar .icon-button { + position: relative; + padding: 0 8px; + z-index: 10; + line-height: var(--menu-bar-height); + cursor: pointer; + transition: color 0.5s; +} +@media only screen and (max-width: 420px) { + #menu-bar i, #menu-bar .icon-button { + padding: 0 5px; + } +} + +.icon-button { + border: none; + background: none; + padding: 0; + color: inherit; +} +.icon-button i { + margin: 0; +} + +.right-buttons { + margin: 0 15px; +} +.right-buttons a { + text-decoration: none; +} + +.left-buttons { + display: flex; + margin: 0 5px; +} +html:not(.js) .left-buttons button { + display: none; +} + +.menu-title { + display: inline-block; + font-weight: 200; + font-size: 2.4rem; + line-height: var(--menu-bar-height); + text-align: center; + margin: 0; + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.menu-title { + cursor: pointer; +} + +.menu-bar, +.menu-bar:visited, +.nav-chapters, +.nav-chapters:visited, +.mobile-nav-chapters, +.mobile-nav-chapters:visited, +.menu-bar .icon-button, +.menu-bar a i { + color: var(--icons); +} + +.menu-bar i:hover, +.menu-bar .icon-button:hover, +.nav-chapters:hover, +.mobile-nav-chapters i:hover { + color: var(--icons-hover); +} + +/* Nav Icons */ + +.nav-chapters { + font-size: 2.5em; + text-align: center; + text-decoration: none; + + position: fixed; + top: 0; + bottom: 0; + margin: 0; + max-width: 150px; + min-width: 90px; + + display: flex; + justify-content: center; + align-content: center; + flex-direction: column; + + transition: color 0.5s, background-color 0.5s; +} + +.nav-chapters:hover { + text-decoration: none; + background-color: var(--theme-hover); + transition: background-color 0.15s, color 0.15s; +} + +.nav-wrapper { + margin-block-start: 50px; + display: none; +} + +.mobile-nav-chapters { + font-size: 2.5em; + text-align: center; + text-decoration: none; + width: 90px; + border-radius: 5px; + background-color: var(--sidebar-bg); +} + +/* Only Firefox supports flow-relative values */ +.previous { float: left; } +[dir=rtl] .previous { float: right; } + +/* Only Firefox supports flow-relative values */ +.next { + float: right; + right: var(--page-padding); +} +[dir=rtl] .next { + float: left; + right: unset; + left: var(--page-padding); +} + +/* Use the correct buttons for RTL layouts*/ +[dir=rtl] .previous i.fa-angle-left:before {content:"\f105";} +[dir=rtl] .next i.fa-angle-right:before { content:"\f104"; } + +@media only screen and (max-width: 1080px) { + .nav-wide-wrapper { display: none; } + .nav-wrapper { display: block; } +} + +/* sidebar-visible */ +@media only screen and (max-width: 1380px) { + #sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wide-wrapper { display: none; } + #sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wrapper { display: block; } +} + +/* Inline code */ + +:not(pre) > .hljs { + display: inline; + padding: 0.1em 0.3em; + border-radius: 3px; +} + +:not(pre):not(a) > .hljs { + color: var(--inline-code-color); + overflow-x: initial; +} + +a:hover > .hljs { + text-decoration: underline; +} + +pre { + position: relative; +} +pre > .buttons { + position: absolute; + z-index: 100; + right: 0px; + top: 2px; + margin: 0px; + padding: 2px 0px; + + color: var(--sidebar-fg); + cursor: pointer; + visibility: hidden; + opacity: 0; + transition: visibility 0.1s linear, opacity 0.1s linear; +} +pre:hover > .buttons { + visibility: visible; + opacity: 1 +} +pre > .buttons :hover { + color: var(--sidebar-active); + border-color: var(--icons-hover); + background-color: var(--theme-hover); +} +pre > .buttons i { + margin-inline-start: 8px; +} +pre > .buttons button { + cursor: inherit; + margin: 0px 5px; + padding: 4px 4px 3px 5px; + font-size: 23px; + + border-style: solid; + border-width: 1px; + border-radius: 4px; + border-color: var(--icons); + background-color: var(--theme-popup-bg); + transition: 100ms; + transition-property: color,border-color,background-color; + color: var(--icons); +} + +pre > .buttons button.clip-button { + padding: 2px 4px 0px 6px; +} +pre > .buttons button.clip-button::before { + /* clipboard image from octicons (https://github.com/primer/octicons/tree/v2.0.0) MIT license + */ + content: url('data:image/svg+xml,\ +\ +\ +'); + filter: var(--copy-button-filter); +} +pre > .buttons button.clip-button:hover::before { + filter: var(--copy-button-filter-hover); +} + +@media (pointer: coarse) { + pre > .buttons button { + /* On mobile, make it easier to tap buttons. */ + padding: 0.3rem 1rem; + } + + .sidebar-resize-indicator { + /* Hide resize indicator on devices with limited accuracy */ + display: none; + } +} +pre > code { + display: block; + padding: 1rem; +} + +/* FIXME: ACE editors overlap their buttons because ACE does absolute + positioning within the code block which breaks padding. The only solution I + can think of is to move the padding to the outer pre tag (or insert a div + wrapper), but that would require fixing a whole bunch of CSS rules. +*/ +.hljs.ace_editor { + padding: 0rem 0rem; +} + +pre > .result { + margin-block-start: 10px; +} + +/* Search */ + +#searchresults a { + text-decoration: none; +} + +mark { + border-radius: 2px; + padding-block-start: 0; + padding-block-end: 1px; + padding-inline-start: 3px; + padding-inline-end: 3px; + margin-block-start: 0; + margin-block-end: -1px; + margin-inline-start: -3px; + margin-inline-end: -3px; + background-color: var(--search-mark-bg); + transition: background-color 300ms linear; + cursor: pointer; +} + +mark.fade-out { + background-color: rgba(0,0,0,0) !important; + cursor: auto; +} + +.searchbar-outer { + margin-inline-start: auto; + margin-inline-end: auto; + max-width: var(--content-max-width); +} + +#searchbar { + width: 100%; + margin-block-start: 5px; + margin-block-end: 0; + margin-inline-start: auto; + margin-inline-end: auto; + padding: 10px 16px; + transition: box-shadow 300ms ease-in-out; + border: 1px solid var(--searchbar-border-color); + border-radius: 3px; + background-color: var(--searchbar-bg); + color: var(--searchbar-fg); +} +#searchbar:focus, +#searchbar.active { + box-shadow: 0 0 3px var(--searchbar-shadow-color); +} + +.searchresults-header { + font-weight: bold; + font-size: 1em; + padding-block-start: 18px; + padding-block-end: 0; + padding-inline-start: 5px; + padding-inline-end: 0; + color: var(--searchresults-header-fg); +} + +.searchresults-outer { + margin-inline-start: auto; + margin-inline-end: auto; + max-width: var(--content-max-width); + border-block-end: 1px dashed var(--searchresults-border-color); +} + +ul#searchresults { + list-style: none; + padding-inline-start: 20px; +} +ul#searchresults li { + margin: 10px 0px; + padding: 2px; + border-radius: 2px; +} +ul#searchresults li.focus { + background-color: var(--searchresults-li-bg); +} +ul#searchresults span.teaser { + display: block; + clear: both; + margin-block-start: 5px; + margin-block-end: 0; + margin-inline-start: 20px; + margin-inline-end: 0; + font-size: 0.8em; +} +ul#searchresults span.teaser em { + font-weight: bold; + font-style: normal; +} + +/* Sidebar */ + +.sidebar { + position: fixed; + left: 0; + top: 0; + bottom: 0; + width: var(--sidebar-width); + font-size: 0.875em; + box-sizing: border-box; + -webkit-overflow-scrolling: touch; + overscroll-behavior-y: contain; + background-color: var(--sidebar-bg); + color: var(--sidebar-fg); +} +.sidebar-iframe-inner { + --padding: 10px; + + background-color: var(--sidebar-bg); + padding: var(--padding); + margin: 0; + font-size: 1.4rem; + color: var(--sidebar-fg); + min-height: calc(100vh - var(--padding) * 2); +} +.sidebar-iframe-outer { + border: none; + height: 100%; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; +} +[dir=rtl] .sidebar { left: unset; right: 0; } +.sidebar-resizing { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} +html:not(.sidebar-resizing) .sidebar { + transition: transform 0.3s; /* Animation: slide away */ +} +.sidebar code { + line-height: 2em; +} +.sidebar .sidebar-scrollbox { + overflow-y: auto; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + padding: 10px 10px; +} +.sidebar .sidebar-resize-handle { + position: absolute; + cursor: col-resize; + width: 0; + right: calc(var(--sidebar-resize-indicator-width) * -1); + top: 0; + bottom: 0; + display: flex; + align-items: center; +} + +.sidebar-resize-handle .sidebar-resize-indicator { + width: 100%; + height: 16px; + color: var(--icons); + margin-inline-start: var(--sidebar-resize-indicator-space); + display: flex; + align-items: center; + justify-content: flex-start; +} +.sidebar-resize-handle .sidebar-resize-indicator::before { + content: ""; + width: 2px; + height: 12px; + border-left: dotted 2px currentColor; +} +.sidebar-resize-handle .sidebar-resize-indicator::after { + content: ""; + width: 2px; + height: 16px; + border-left: dotted 2px currentColor; +} + +[dir=rtl] .sidebar .sidebar-resize-handle { + left: calc(var(--sidebar-resize-indicator-width) * -1); + right: unset; +} +.js .sidebar .sidebar-resize-handle { + cursor: col-resize; + width: calc(var(--sidebar-resize-indicator-width) - var(--sidebar-resize-indicator-space)); +} +/* sidebar-hidden */ +#sidebar-toggle-anchor:not(:checked) ~ .sidebar { + transform: translateX(calc(0px - var(--sidebar-width) - var(--sidebar-resize-indicator-width))); + z-index: -1; +} +[dir=rtl] #sidebar-toggle-anchor:not(:checked) ~ .sidebar { + transform: translateX(calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width))); +} +.sidebar::-webkit-scrollbar { + background: var(--sidebar-bg); +} +.sidebar::-webkit-scrollbar-thumb { + background: var(--scrollbar); +} + +/* sidebar-visible */ +#sidebar-toggle-anchor:checked ~ .page-wrapper { + transform: translateX(calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width))); +} +[dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper { + transform: translateX(calc(0px - var(--sidebar-width) - var(--sidebar-resize-indicator-width))); +} +@media only screen and (min-width: 620px) { + #sidebar-toggle-anchor:checked ~ .page-wrapper { + transform: none; + margin-inline-start: calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width)); + } + [dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper { + transform: none; + } +} + +.chapter { + list-style: none outside none; + padding-inline-start: 0; + line-height: 2.2em; +} + +.chapter ol { + width: 100%; +} + +.chapter li { + display: flex; + color: var(--sidebar-non-existant); +} +.chapter li a { + display: block; + padding: 0; + text-decoration: none; + color: var(--sidebar-fg); +} + +.chapter li a:hover { + color: var(--sidebar-active); +} + +.chapter li a.active { + color: var(--sidebar-active); +} + +.chapter li > a.toggle { + cursor: pointer; + display: block; + margin-inline-start: auto; + padding: 0 10px; + user-select: none; + opacity: 0.68; +} + +.chapter li > a.toggle div { + transition: transform 0.5s; +} + +/* collapse the section */ +.chapter li:not(.expanded) + li > ol { + display: none; +} + +.chapter li.chapter-item { + line-height: 1.5em; + margin-block-start: 0.6em; +} + +.chapter li.expanded > a.toggle div { + transform: rotate(90deg); +} + +.spacer { + width: 100%; + height: 3px; + margin: 5px 0px; +} +.chapter .spacer { + background-color: var(--sidebar-spacer); +} + +@media (-moz-touch-enabled: 1), (pointer: coarse) { + .chapter li a { padding: 5px 0; } + .spacer { margin: 10px 0; } +} + +.section { + list-style: none outside none; + padding-inline-start: 20px; + line-height: 1.9em; +} + +/* Theme Menu Popup */ + +.theme-popup { + position: absolute; + left: 10px; + top: var(--menu-bar-height); + z-index: 1000; + border-radius: 4px; + font-size: 0.7em; + color: var(--fg); + background: var(--theme-popup-bg); + border: 1px solid var(--theme-popup-border); + margin: 0; + padding: 0; + list-style: none; + display: none; + /* Don't let the children's background extend past the rounded corners. */ + overflow: hidden; +} +[dir=rtl] .theme-popup { left: unset; right: 10px; } +.theme-popup .default { + color: var(--icons); +} +.theme-popup .theme { + width: 100%; + border: 0; + margin: 0; + padding: 2px 20px; + line-height: 25px; + white-space: nowrap; + text-align: start; + cursor: pointer; + color: inherit; + background: inherit; + font-size: inherit; +} +.theme-popup .theme:hover { + background-color: var(--theme-hover); +} + +.theme-selected::before { + display: inline-block; + content: "✓"; + margin-inline-start: -14px; + width: 14px; +} + +/* The container for the help popup that covers the whole window. */ +#mdbook-help-container { + /* Position and size for the whole window. */ + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + /* This uses flex layout (which is set in book.js), and centers the popup + in the window.*/ + display: none; + align-items: center; + justify-content: center; + z-index: 1000; + /* Dim out the book while the popup is visible. */ + background: var(--overlay-bg); +} + +/* The popup help box. */ +#mdbook-help-popup { + box-shadow: 0 4px 24px rgba(0,0,0,0.15); + min-width: 300px; + max-width: 500px; + width: 100%; + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: center; + background-color: var(--bg); + color: var(--fg); + border-width: 1px; + border-color: var(--theme-popup-border); + border-style: solid; + border-radius: 8px; + padding: 10px; +} + +.mdbook-help-title { + text-align: center; + /* mdbook's margin for h2 is way too large. */ + margin: 10px; +} diff --git a/en/css/general.css b/en/css/general.css new file mode 100644 index 000000000..9946cfc01 --- /dev/null +++ b/en/css/general.css @@ -0,0 +1,279 @@ +/* Base styles and content styles */ + +:root { + /* Browser default font-size is 16px, this way 1 rem = 10px */ + font-size: 62.5%; + color-scheme: var(--color-scheme); +} + +html { + font-family: "Open Sans", sans-serif; + color: var(--fg); + background-color: var(--bg); + text-size-adjust: none; + -webkit-text-size-adjust: none; +} + +body { + margin: 0; + font-size: 1.6rem; + overflow-x: hidden; +} + +code { + font-family: var(--mono-font) !important; + font-size: var(--code-font-size); + direction: ltr !important; +} + +/* make long words/inline code not x overflow */ +main { + overflow-wrap: break-word; +} + +/* make wide tables scroll if they overflow */ +.table-wrapper { + overflow-x: auto; +} + +/* Don't change font size in headers. */ +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + font-size: unset; +} + +.left { float: left; } +.right { float: right; } +.boring { opacity: 0.6; } +.hide-boring .boring { display: none; } +.hidden { display: none !important; } + +h2, h3 { margin-block-start: 2.5em; } +h4, h5 { margin-block-start: 2em; } + +.header + .header h3, +.header + .header h4, +.header + .header h5 { + margin-block-start: 1em; +} + +h1:target::before, +h2:target::before, +h3:target::before, +h4:target::before, +h5:target::before, +h6:target::before { + display: inline-block; + content: "»"; + margin-inline-start: -30px; + width: 30px; +} + +/* This is broken on Safari as of version 14, but is fixed + in Safari Technology Preview 117 which I think will be Safari 14.2. + https://bugs.webkit.org/show_bug.cgi?id=218076 +*/ +:target { + /* Safari does not support logical properties */ + scroll-margin-top: calc(var(--menu-bar-height) + 0.5em); +} + +.page { + outline: 0; + padding: 0 var(--page-padding); + margin-block-start: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ +} +.page-wrapper { + box-sizing: border-box; + background-color: var(--bg); +} +.no-js .page-wrapper, +.js:not(.sidebar-resizing) .page-wrapper { + transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ +} +[dir=rtl] .js:not(.sidebar-resizing) .page-wrapper { + transition: margin-right 0.3s ease, transform 0.3s ease; /* Animation: slide away */ +} + +.content { + overflow-y: auto; + padding: 0 5px 50px 5px; +} +.content main { + margin-inline-start: auto; + margin-inline-end: auto; + max-width: var(--content-max-width); +} +.content p { line-height: 1.45em; } +.content ol { line-height: 1.45em; } +.content ul { line-height: 1.45em; } +.content a { text-decoration: none; } +.content a:hover { text-decoration: underline; } +.content img, .content video { max-width: 100%; } +.content .header:link, +.content .header:visited { + color: var(--fg); +} +.content .header:link, +.content .header:visited:hover { + text-decoration: none; +} + +table { + margin: 0 auto; + border-collapse: collapse; +} +table td { + padding: 3px 20px; + border: 1px var(--table-border-color) solid; +} +table thead { + background: var(--table-header-bg); +} +table thead td { + font-weight: 700; + border: none; +} +table thead th { + padding: 3px 20px; +} +table thead tr { + border: 1px var(--table-header-bg) solid; +} +/* Alternate background colors for rows */ +table tbody tr:nth-child(2n) { + background: var(--table-alternate-bg); +} + + +blockquote { + margin: 20px 0; + padding: 0 20px; + color: var(--fg); + background-color: var(--quote-bg); + border-block-start: .1em solid var(--quote-border); + border-block-end: .1em solid var(--quote-border); +} + +.warning { + margin: 20px; + padding: 0 20px; + border-inline-start: 2px solid var(--warning-border); +} + +.warning:before { + position: absolute; + width: 3rem; + height: 3rem; + margin-inline-start: calc(-1.5rem - 21px); + content: "ⓘ"; + text-align: center; + background-color: var(--bg); + color: var(--warning-border); + font-weight: bold; + font-size: 2rem; +} + +blockquote .warning:before { + background-color: var(--quote-bg); +} + +kbd { + background-color: var(--table-border-color); + border-radius: 4px; + border: solid 1px var(--theme-popup-border); + box-shadow: inset 0 -1px 0 var(--theme-hover); + display: inline-block; + font-size: var(--code-font-size); + font-family: var(--mono-font); + line-height: 10px; + padding: 4px 5px; + vertical-align: middle; +} + +sup { + /* Set the line-height for superscript and footnote references so that there + isn't an awkward space appearing above lines that contain the footnote. + + See https://github.com/rust-lang/mdBook/pull/2443#discussion_r1813773583 + for an explanation. + */ + line-height: 0; +} + +.footnote-definition { + font-size: 0.9em; +} +/* The default spacing for a list is a little too large. */ +.footnote-definition ul, +.footnote-definition ol { + padding-left: 20px; +} +.footnote-definition > li { + /* Required to position the ::before target */ + position: relative; +} +.footnote-definition > li:target { + scroll-margin-top: 50vh; +} +.footnote-reference:target { + scroll-margin-top: 50vh; +} +/* Draws a border around the footnote (including the marker) when it is selected. + TODO: If there are multiple linkbacks, highlight which one you just came + from so you know which one to click. +*/ +.footnote-definition > li:target::before { + border: 2px solid var(--footnote-highlight); + border-radius: 6px; + position: absolute; + top: -8px; + right: -8px; + bottom: -8px; + left: -32px; + pointer-events: none; + content: ""; +} +/* Pulses the footnote reference so you can quickly see where you left off reading. + This could use some improvement. +*/ +@media not (prefers-reduced-motion) { + .footnote-reference:target { + animation: fn-highlight 0.8s; + border-radius: 2px; + } + + @keyframes fn-highlight { + from { + background-color: var(--footnote-highlight); + } + } +} + +.tooltiptext { + position: absolute; + visibility: hidden; + color: #fff; + background-color: #333; + transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ + left: -8px; /* Half of the width of the icon */ + top: -35px; + font-size: 0.8em; + text-align: center; + border-radius: 6px; + padding: 5px 8px; + margin: 5px; + z-index: 1000; +} +.tooltipped .tooltiptext { + visibility: visible; +} + +.chapter li.part-title { + color: var(--sidebar-fg); + margin: 5px 0px; + font-weight: bold; +} + +.result-no-output { + font-style: italic; +} diff --git a/en/css/print.css b/en/css/print.css new file mode 100644 index 000000000..80ec3a544 --- /dev/null +++ b/en/css/print.css @@ -0,0 +1,50 @@ + +#sidebar, +#menu-bar, +.nav-chapters, +.mobile-nav-chapters { + display: none; +} + +#page-wrapper.page-wrapper { + transform: none !important; + margin-inline-start: 0px; + overflow-y: initial; +} + +#content { + max-width: none; + margin: 0; + padding: 0; +} + +.page { + overflow-y: initial; +} + +code { + direction: ltr !important; +} + +pre > .buttons { + z-index: 2; +} + +a, a:visited, a:active, a:hover { + color: #4183c4; + text-decoration: none; +} + +h1, h2, h3, h4, h5, h6 { + page-break-inside: avoid; + page-break-after: avoid; +} + +pre, code { + page-break-inside: avoid; + white-space: pre-wrap; +} + +.fa { + display: none !important; +} diff --git a/en/css/variables.css b/en/css/variables.css new file mode 100644 index 000000000..5742d2414 --- /dev/null +++ b/en/css/variables.css @@ -0,0 +1,330 @@ + +/* Globals */ + +:root { + --sidebar-target-width: 300px; + --sidebar-width: min(var(--sidebar-target-width), 80vw); + --sidebar-resize-indicator-width: 8px; + --sidebar-resize-indicator-space: 2px; + --page-padding: 15px; + --content-max-width: 750px; + --menu-bar-height: 50px; + --mono-font: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace; + --code-font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */ +} + +/* Themes */ + +.ayu { + --bg: hsl(210, 25%, 8%); + --fg: #c5c5c5; + + --sidebar-bg: #14191f; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #5c6773; + --sidebar-active: #ffb454; + --sidebar-spacer: #2d334f; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #b7b9cc; + + --links: #0096cf; + + --inline-code-color: #ffb454; + + --theme-popup-bg: #14191f; + --theme-popup-border: #5c6773; + --theme-hover: #191f26; + + --quote-bg: hsl(226, 15%, 17%); + --quote-border: hsl(226, 15%, 22%); + + --warning-border: #ff8e00; + + --table-border-color: hsl(210, 25%, 13%); + --table-header-bg: hsl(210, 25%, 28%); + --table-alternate-bg: hsl(210, 25%, 11%); + + --searchbar-border-color: #848484; + --searchbar-bg: #424242; + --searchbar-fg: #fff; + --searchbar-shadow-color: #d4c89f; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #252932; + --search-mark-bg: #e3b171; + + --color-scheme: dark; + + /* Same as `--icons` */ + --copy-button-filter: invert(45%) sepia(6%) saturate(621%) hue-rotate(198deg) brightness(99%) contrast(85%); + /* Same as `--sidebar-active` */ + --copy-button-filter-hover: invert(68%) sepia(55%) saturate(531%) hue-rotate(341deg) brightness(104%) contrast(101%); + + --footnote-highlight: #2668a6; + + --overlay-bg: rgba(33, 40, 48, 0.4); +} + +.coal { + --bg: hsl(200, 7%, 8%); + --fg: #98a3ad; + + --sidebar-bg: #292c2f; + --sidebar-fg: #a1adb8; + --sidebar-non-existant: #505254; + --sidebar-active: #3473ad; + --sidebar-spacer: #393939; + + --scrollbar: var(--sidebar-fg); + + --icons: #43484d; + --icons-hover: #b3c0cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6; + + --theme-popup-bg: #141617; + --theme-popup-border: #43484d; + --theme-hover: #1f2124; + + --quote-bg: hsl(234, 21%, 18%); + --quote-border: hsl(234, 21%, 23%); + + --warning-border: #ff8e00; + + --table-border-color: hsl(200, 7%, 13%); + --table-header-bg: hsl(200, 7%, 28%); + --table-alternate-bg: hsl(200, 7%, 11%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #b7b7b7; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #98a3ad; + --searchresults-li-bg: #2b2b2f; + --search-mark-bg: #355c7d; + + --color-scheme: dark; + + /* Same as `--icons` */ + --copy-button-filter: invert(26%) sepia(8%) saturate(575%) hue-rotate(169deg) brightness(87%) contrast(82%); + /* Same as `--sidebar-active` */ + --copy-button-filter-hover: invert(36%) sepia(70%) saturate(503%) hue-rotate(167deg) brightness(98%) contrast(89%); + + --footnote-highlight: #4079ae; + + --overlay-bg: rgba(33, 40, 48, 0.4); +} + +.light, html:not(.js) { + --bg: hsl(0, 0%, 100%); + --fg: hsl(0, 0%, 0%); + + --sidebar-bg: #fafafa; + --sidebar-fg: hsl(0, 0%, 0%); + --sidebar-non-existant: #aaaaaa; + --sidebar-active: #1f1fff; + --sidebar-spacer: #f4f4f4; + + --scrollbar: #8F8F8F; + + --icons: #747474; + --icons-hover: #000000; + + --links: #20609f; + + --inline-code-color: #301900; + + --theme-popup-bg: #fafafa; + --theme-popup-border: #cccccc; + --theme-hover: #e6e6e6; + + --quote-bg: hsl(197, 37%, 96%); + --quote-border: hsl(197, 37%, 91%); + + --warning-border: #ff8e00; + + --table-border-color: hsl(0, 0%, 95%); + --table-header-bg: hsl(0, 0%, 80%); + --table-alternate-bg: hsl(0, 0%, 97%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #fafafa; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #e4f2fe; + --search-mark-bg: #a2cff5; + + --color-scheme: light; + + /* Same as `--icons` */ + --copy-button-filter: invert(45.49%); + /* Same as `--sidebar-active` */ + --copy-button-filter-hover: invert(14%) sepia(93%) saturate(4250%) hue-rotate(243deg) brightness(99%) contrast(130%); + + --footnote-highlight: #7e7eff; + + --overlay-bg: rgba(200, 200, 205, 0.4); +} + +.navy { + --bg: hsl(226, 23%, 11%); + --fg: #bcbdd0; + + --sidebar-bg: #282d3f; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #505274; + --sidebar-active: #2b79a2; + --sidebar-spacer: #2d334f; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #b7b9cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6; + + --theme-popup-bg: #161923; + --theme-popup-border: #737480; + --theme-hover: #282e40; + + --quote-bg: hsl(226, 15%, 17%); + --quote-border: hsl(226, 15%, 22%); + + --warning-border: #ff8e00; + + --table-border-color: hsl(226, 23%, 16%); + --table-header-bg: hsl(226, 23%, 31%); + --table-alternate-bg: hsl(226, 23%, 14%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #aeaec6; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #5f5f71; + --searchresults-border-color: #5c5c68; + --searchresults-li-bg: #242430; + --search-mark-bg: #a2cff5; + + --color-scheme: dark; + + /* Same as `--icons` */ + --copy-button-filter: invert(51%) sepia(10%) saturate(393%) hue-rotate(198deg) brightness(86%) contrast(87%); + /* Same as `--sidebar-active` */ + --copy-button-filter-hover: invert(46%) sepia(20%) saturate(1537%) hue-rotate(156deg) brightness(85%) contrast(90%); + + --footnote-highlight: #4079ae; + + --overlay-bg: rgba(33, 40, 48, 0.4); +} + +.rust { + --bg: hsl(60, 9%, 87%); + --fg: #262625; + + --sidebar-bg: #3b2e2a; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #505254; + --sidebar-active: #e69f67; + --sidebar-spacer: #45373a; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #262625; + + --links: #2b79a2; + + --inline-code-color: #6e6b5e; + + --theme-popup-bg: #e1e1db; + --theme-popup-border: #b38f6b; + --theme-hover: #99908a; + + --quote-bg: hsl(60, 5%, 75%); + --quote-border: hsl(60, 5%, 70%); + + --warning-border: #ff8e00; + + --table-border-color: hsl(60, 9%, 82%); + --table-header-bg: #b3a497; + --table-alternate-bg: hsl(60, 9%, 84%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #fafafa; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #dec2a2; + --search-mark-bg: #e69f67; + + /* Same as `--icons` */ + --copy-button-filter: invert(51%) sepia(10%) saturate(393%) hue-rotate(198deg) brightness(86%) contrast(87%); + /* Same as `--sidebar-active` */ + --copy-button-filter-hover: invert(77%) sepia(16%) saturate(1798%) hue-rotate(328deg) brightness(98%) contrast(83%); + + --footnote-highlight: #d3a17a; + + --overlay-bg: rgba(150, 150, 150, 0.25); +} + +@media (prefers-color-scheme: dark) { + html:not(.js) { + --bg: hsl(200, 7%, 8%); + --fg: #98a3ad; + + --sidebar-bg: #292c2f; + --sidebar-fg: #a1adb8; + --sidebar-non-existant: #505254; + --sidebar-active: #3473ad; + --sidebar-spacer: #393939; + + --scrollbar: var(--sidebar-fg); + + --icons: #43484d; + --icons-hover: #b3c0cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6; + + --theme-popup-bg: #141617; + --theme-popup-border: #43484d; + --theme-hover: #1f2124; + + --quote-bg: hsl(234, 21%, 18%); + --quote-border: hsl(234, 21%, 23%); + + --warning-border: #ff8e00; + + --table-border-color: hsl(200, 7%, 13%); + --table-header-bg: hsl(200, 7%, 28%); + --table-alternate-bg: hsl(200, 7%, 11%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #b7b7b7; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #98a3ad; + --searchresults-li-bg: #2b2b2f; + --search-mark-bg: #355c7d; + + --color-scheme: dark; + + /* Same as `--icons` */ + --copy-button-filter: invert(26%) sepia(8%) saturate(575%) hue-rotate(169deg) brightness(87%) contrast(82%); + /* Same as `--sidebar-active` */ + --copy-button-filter-hover: invert(36%) sepia(70%) saturate(503%) hue-rotate(167deg) brightness(98%) contrast(89%); + } +} diff --git a/en/cursor_connections.html b/en/cursor_connections.html new file mode 100644 index 000000000..da701898d --- /dev/null +++ b/en/cursor_connections.html @@ -0,0 +1,263 @@ + + + + + + Cursor connections - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Cursor connections

+

Relay's cursor connection specification is designed to provide a consistent method for query paging. For more details on the specification see the GraphQL Cursor Connections Specification

+

Defining a cursor connection in async-graphql is very simple, you just call the connection::query function and query data in the closure.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::types::connection::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn numbers(&self,
+        after: Option<String>,
+        before: Option<String>,
+        first: Option<i32>,
+        last: Option<i32>,
+    ) -> Result<Connection<usize, i32, EmptyFields, EmptyFields>> {
+        query(after, before, first, last, |after, before, first, last| async move {
+            let mut start = after.map(|after| after + 1).unwrap_or(0);
+            let mut end = before.unwrap_or(10000);
+            if let Some(first) = first {
+                end = (start + first).min(end);
+            }
+            if let Some(last) = last {
+                start = if last > end - start {
+                     end
+                } else {
+                    end - last
+                };
+            }
+            let mut connection = Connection::new(start > 0, end < 10000);
+            connection.edges.extend(
+                (start..end).into_iter().map(|n|
+                    Edge::with_additional_fields(n, n as i32, EmptyFields)
+            ));
+            Ok::<_, async_graphql::Error>(connection)
+        }).await
+    }
+}
+
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/custom_directive.html b/en/custom_directive.html new file mode 100644 index 000000000..3a0576375 --- /dev/null +++ b/en/custom_directive.html @@ -0,0 +1,311 @@ + + + + + + Custom directive - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Custom directive

+

There are two types of directives in GraphQL: executable and type system. Executable directives are used by the client within an operation to modify the behavior (like the built-in @include and @skip directives). Type system directives provide additional information about the types, potentially modifying how the server behaves (like @deprecated and @oneOf). async-graphql allows you to declare both types of custom directives, with different limitations on each.

+

Executable directives

+

To create a custom executable directive, you need to implement the CustomDirective trait, and then use the Directive macro to +generate a factory function that receives the parameters of the directive and returns an instance of the directive.

+

Currently async-graphql only supports custom executable directives located at FIELD.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct ConcatDirective {
+    value: String,
+}
+
+#[async_trait::async_trait]
+impl CustomDirective for ConcatDirective {
+    async fn resolve_field(&self, _ctx: &Context<'_>, resolve: ResolveFut<'_>) -> ServerResult<Option<Value>> {
+        resolve.await.map(|value| {
+            value.map(|value| match value {
+                Value::String(str) => Value::String(str + &self.value),
+                _ => value,
+            })
+        })
+    }
+}
+
+#[Directive(location = "Field")]
+fn concat(value: String) -> impl CustomDirective {
+    ConcatDirective { value }
+}
+}
+

Register the directive when building the schema:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+struct ConcatDirective { value: String, }
+#[async_trait::async_trait]
+impl CustomDirective for ConcatDirective {
+  async fn resolve_field(&self, _ctx: &Context<'_>, resolve: ResolveFut<'_>) -> ServerResult<Option<Value>> { todo!() }
+}
+#[Directive(location = "Field")]
+fn concat(value: String) -> impl CustomDirective { ConcatDirective { value } }
+let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
+    .directive(concat)
+    .finish();
+}
+

Type system directives

+

To create a custom type system directive, you can use the #[TypeDirective] macro on a function:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[TypeDirective(
+    location = "FieldDefinition",
+    location = "Object",
+)]
+fn testDirective(scope: String, input: u32, opt: Option<u64>) {}
+}
+

Current only the FieldDefinition and Object locations are supported, you can select one or both. After declaring the directive, you can apply it to a relevant location (after importing the function) like this:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[TypeDirective(
+location = "FieldDefinition",
+location = "Object",
+)]
+fn testDirective(scope: String, input: u32, opt: Option<u64>) {}
+#[derive(SimpleObject)]
+#[graphql(
+    directive = testDirective::apply("simple object type".to_string(), 1, Some(3))
+)]
+struct SimpleValue {
+    #[graphql(
+        directive = testDirective::apply("field and param with \" symbol".to_string(), 2, Some(3))
+    )]
+    some_data: String,
+}
+}
+

This example produces a schema like this:

+
type SimpleValue @testDirective(scope: "simple object type", input: 1, opt: 3) {
+	someData: String! @testDirective(scope: "field and param with \" symbol", input: 2, opt: 3)
+}
+
+directive @testDirective(scope: String!, input: Int!, opt: Int) on FIELD_DEFINITION | OBJECT
+
+

Note: To use a type-system directive with Apollo Federation's @composeDirective, see the federation docs

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/custom_scalars.html b/en/custom_scalars.html new file mode 100644 index 000000000..4af503b19 --- /dev/null +++ b/en/custom_scalars.html @@ -0,0 +1,271 @@ + + + + + + Custom scalars - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Custom scalars

+

In Async-graphql most common scalar types are built in, but you can also create your own scalar types.

+

Using async-graphql::Scalar, you can add support for a scalar when you implement it. You only need to implement parsing and output functions.

+

The following example defines a 64-bit integer scalar where its input and output are strings.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct StringNumber(i64);
+
+#[Scalar]
+impl ScalarType for StringNumber {
+    fn parse(value: Value) -> InputValueResult<Self> {
+        if let Value::String(value) = &value {
+            // Parse the integer value
+            Ok(value.parse().map(StringNumber)?)
+        } else {
+            // If the type does not match
+            Err(InputValueError::expected_type(value))
+        }
+    }
+
+    fn to_value(&self) -> Value {
+        Value::String(self.0.to_string())
+    }
+}
+}
+

Use scalar! macro to define scalar

+

If your type implemented serde::Serialize and serde::Deserialize, then you can use this macro to define a scalar more simply.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate serde;
+use async_graphql::*;
+use serde::{Serialize, Deserialize};
+use std::collections::HashMap;
+#[derive(Serialize, Deserialize)]
+struct MyValue {
+    a: i32,
+    b: HashMap<String, i32>,     
+}
+
+scalar!(MyValue);
+
+// Rename to `MV`.
+// scalar!(MyValue, "MV");
+
+// Rename to `MV` and add description.
+// scalar!(MyValue, "MV", "This is my value");
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/dataloader.html b/en/dataloader.html new file mode 100644 index 000000000..9256c5084 --- /dev/null +++ b/en/dataloader.html @@ -0,0 +1,347 @@ + + + + + + Optimizing N+1 queries - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Optimizing N+1 queries

+

Have you noticed some GraphQL queries end can make hundreds of database queries, often with mostly repeated data? Lets take a look why and how to fix it.

+

Query Resolution

+

Imagine if you have a simple query like this:

+
query {
+  todos {
+    users {
+      name
+    }
+  }
+}
+
+

and User resolver is like this:

+
struct User {
+    id: u64,
+}
+
+#[Object]
+impl User {
+    async fn name(&self, ctx: &Context<'_>) -> Result<String> {
+        let pool = ctx.data_unchecked::<Pool<Postgres>>();
+        let (name,): (String,) = sqlx::query_as("SELECT name FROM user WHERE id = $1")
+            .bind(self.id)
+            .fetch_one(pool)
+            .await?;
+        Ok(name)
+    }
+}
+

The query executor will call the Todos resolver which does a select * from todo and return N todos. Then for each +of the todos, concurrently, call the User resolver, SELECT from USER where id = todo.user_id.

+

eg:

+
SELECT id, todo, user_id FROM todo
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+
+

After executing SELECT name FROM user WHERE id = $1 many times, and most Todo objects belong to the same user, we +need to optimize these codes!

+

Dataloader

+

We need to group queries and exclude duplicate queries. Dataloader can do this. +facebook gives a request-scope batch and caching solution.

+

The following is a simplified example of using DataLoader to optimize queries, there is also a full code example available in GitHub.

+
use async_graphql::*;
+use async_graphql::dataloader::*;
+use std::sync::Arc;
+
+struct UserNameLoader {
+    pool: sqlx::PgPool,
+}
+
+impl Loader<u64> for UserNameLoader {
+    type Value = String;
+    type Error = Arc<sqlx::Error>;
+
+    async fn load(&self, keys: &[u64]) -> Result<HashMap<u64, Self::Value>, Self::Error> {
+        Ok(sqlx::query_as("SELECT name FROM user WHERE id = ANY($1)")
+            .bind(keys)
+            .fetch(&self.pool)
+            .map_ok(|name: String| name)
+            .map_err(Arc::new)
+            .try_collect().await?)
+    }
+}
+
+#[derive(SimpleObject)]
+#[graphql(complex)]
+struct User {
+    id: u64,
+}
+
+#[ComplexObject]
+impl User {
+    async fn name(&self, ctx: &Context<'_>) -> Result<String> {
+        let loader = ctx.data_unchecked::<DataLoader<UserNameLoader>>();
+        let name: Option<String> = loader.load_one(self.id).await?;
+        name.ok_or_else(|| "Not found".into())
+    }
+}
+

To expose UserNameLoader in the ctx, you have to register it with the schema, along with a task spawner, e.g. async_std::task::spawn:

+
let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
+    .data(DataLoader::new(
+        UserNameLoader,
+        async_std::task::spawn, // or `tokio::spawn`
+    ))
+    .finish();
+

In the end, only two SQLs are needed to query the results we want!

+
SELECT id, todo, user_id FROM todo
+SELECT name FROM user WHERE id IN (1, 2, 3, 4)
+
+

Implement multiple data types

+

You can implement multiple data types for the same Loader, like this:

+
extern crate async_graphql;
+use async_graphql::*;
+struct PostgresLoader {
+    pool: sqlx::PgPool,
+}
+
+impl Loader<UserId> for PostgresLoader {
+    type Value = User;
+    type Error = Arc<sqlx::Error>;
+
+    async fn load(&self, keys: &[UserId]) -> Result<HashMap<UserId, Self::Value>, Self::Error> {
+        // Load users from database
+    }
+}
+
+impl Loader<TodoId> for PostgresLoader {
+    type Value = Todo;
+    type Error = sqlx::Error;
+
+    async fn load(&self, keys: &[TodoId]) -> Result<HashMap<TodoId, Self::Value>, Self::Error> {
+        // Load todos from database
+    }
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/default_value.html b/en/default_value.html new file mode 100644 index 000000000..f5045ca84 --- /dev/null +++ b/en/default_value.html @@ -0,0 +1,290 @@ + + + + + + Default value - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Default value

+

You can define default values for input value types. +Below are some examples.

+

Object field

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+fn my_default() -> i32 {
+    30
+}
+
+#[Object]
+impl Query {
+    // The default value of the value parameter is 0, it will call i32::default()
+    async fn test1(&self, #[graphql(default)] value: i32) -> i32 { todo!() }
+
+    // The default value of the value parameter is 10
+    async fn test2(&self, #[graphql(default = 10)] value: i32) -> i32 { todo!() }
+    
+    // The default value of the value parameter uses the return result of the my_default function, the value is 30.
+    async fn test3(&self, #[graphql(default_with = "my_default()")] value: i32) -> i32 { todo!() }
+}
+}
+

Interface field

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+fn my_default() -> i32 { 5 }
+struct MyObj;
+#[Object]
+impl MyObj {
+   async fn test1(&self, value: i32) -> i32 { todo!() }
+   async fn test2(&self, value: i32) -> i32 { todo!() }
+   async fn test3(&self, value: i32) -> i32 { todo!() }
+}
+use async_graphql::*;
+
+#[derive(Interface)]
+#[graphql(
+    field(name = "test1", ty = "i32", arg(name = "value", ty = "i32", default)),
+    field(name = "test2", ty = "i32", arg(name = "value", ty = "i32", default = 10)),
+    field(name = "test3", ty = "i32", arg(name = "value", ty = "i32", default_with = "my_default()")),
+)]
+enum MyInterface {
+    MyObj(MyObj),
+}
+}
+

Input object field

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+fn my_default() -> i32 { 5 }
+use async_graphql::*;
+
+#[derive(InputObject)]
+struct MyInputObject {
+    #[graphql(default)]
+    value1: i32,
+
+    #[graphql(default = 10)]
+    value2: i32,
+
+    #[graphql(default_with = "my_default()")]
+    value3: i32,
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/define_complex_object.html b/en/define_complex_object.html new file mode 100644 index 000000000..bdb0285bd --- /dev/null +++ b/en/define_complex_object.html @@ -0,0 +1,261 @@ + + + + + + Object - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Object

+

Different from SimpleObject, Object must have a resolver defined for each field in its impl.

+

A resolver function has to be asynchronous. The first argument has to be &self, the second is an optional Context and it is followed by field arguments.

+

The resolver is used to get the value of the field. For example, you can query a database and return the result. The return type of the function is the type of the field. You can also return a async_graphql::Result to return an error if it occurs. The error message will then be sent as query result.

+

You may need access to global data in your query, for example a database connection pool. +When creating your Schema, you can use SchemaBuilder::data to configure the global data, and Context::data to configure Context data. +The following value_from_db function shows how to retrieve a database connection from Context.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+struct Data { pub name: String }
+struct DbConn {}
+impl DbConn {
+  fn query_something(&self, id: i64) -> std::result::Result<Data, String> { Ok(Data {name:"".into()})}
+}
+struct DbPool {}
+impl DbPool {
+  fn take(&self) -> DbConn { DbConn {} }    
+}
+use async_graphql::*;
+
+struct MyObject {
+    value: i32,
+}
+
+#[Object]
+impl MyObject {
+    async fn value(&self) -> String {
+        self.value.to_string()
+    }
+
+    async fn value_from_db(
+        &self,
+        ctx: &Context<'_>,
+        #[graphql(desc = "Id of object")] id: i64
+    ) -> Result<String> {
+        let conn = ctx.data::<DbPool>()?.take();
+        Ok(conn.query_something(id)?.name)
+    }
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/define_enum.html b/en/define_enum.html new file mode 100644 index 000000000..6a131873d --- /dev/null +++ b/en/define_enum.html @@ -0,0 +1,286 @@ + + + + + + Enum - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Enum

+

It's easy to define an Enum, here we have an example:

+

Async-graphql will automatically change the name of each item to GraphQL's CONSTANT_CASE convention. You can use name to rename.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+/// One of the films in the Star Wars Trilogy
+#[derive(Enum, Copy, Clone, Eq, PartialEq)]
+pub enum Episode {
+    /// Released in 1977.
+    NewHope,
+
+    /// Released in 1980.
+    Empire,
+
+    /// Released in 1983.
+    #[graphql(name="AAA")]
+    Jedi,
+}
+}
+

Wrapping a remote enum

+

Rust's orphan rule requires that either the +trait or the type for which you are implementing the trait must be defined in the same crate as the impl, so you cannot +expose remote enumeration types to GraphQL. In order to provide an Enum type, a common workaround is to create a new +enum that has parity with the existing, remote enum type.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+mod remote_crate { pub enum RemoteEnum { A, B, C } }
+use async_graphql::*;
+
+/// Provides parity with a remote enum type
+#[derive(Enum, Copy, Clone, Eq, PartialEq)]
+pub enum LocalEnum {
+    A,
+    B,
+    C,
+}
+
+/// Conversion interface from remote type to our local GraphQL enum type
+impl From<remote_crate::RemoteEnum> for LocalEnum {
+    fn from(e: remote_crate::RemoteEnum) -> Self {
+        match e {
+            remote_crate::RemoteEnum::A => Self::A,
+            remote_crate::RemoteEnum::B => Self::B,
+            remote_crate::RemoteEnum::C => Self::C,
+        }
+    }
+}
+}
+

The process is tedious and requires multiple steps to keep the local and remote enums in sync. Async_graphql provides a handy feature to generate the From<remote_crate::RemoteEnum> for LocalEnum as well as an opposite direction of From<LocalEnum> for remote_crate::RemoteEnum via an additional attribute after deriving Enum:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+mod remote_crate { pub enum RemoteEnum { A, B, C } }
+#[derive(Enum, Copy, Clone, Eq, PartialEq)]
+#[graphql(remote = "remote_crate::RemoteEnum")]
+enum LocalEnum {
+    A,
+    B,
+    C,
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/define_input_object.html b/en/define_input_object.html new file mode 100644 index 000000000..1d96515e7 --- /dev/null +++ b/en/define_input_object.html @@ -0,0 +1,361 @@ + + + + + + InputObject - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

InputObject

+

You can use an Object as an argument, and GraphQL calls it an InputObject.

+

The definition of InputObject is similar to SimpleObject, but +SimpleObject can only be used as output and InputObject can only be used as input.

+

You can add optional #[graphql] attributes to add descriptions or rename the field.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+#[derive(SimpleObject)]
+struct User { a: i32 }
+use async_graphql::*;
+
+#[derive(InputObject)]
+struct Coordinate {
+    latitude: f64,
+    longitude: f64
+}
+
+struct Mutation;
+
+#[Object]
+impl Mutation {
+    async fn users_at_location(&self, coordinate: Coordinate, radius: f64) -> Vec<User> {
+        // Writes coordination to database.
+        // ...
+      todo!()
+    }
+}
+}
+

Generic InputObjects

+

If you want to reuse an InputObject for other types, you can define a generic InputObject +and specify how its concrete types should be implemented.

+

In the following example, two InputObject types are created:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(InputObject)]
+struct SomeType { a: i32 }
+#[derive(InputObject)]
+struct SomeOtherType { a: i32 }
+#[derive(InputObject)]
+#[graphql(concrete(name = "SomeName", params(SomeType)))]
+#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))]
+pub struct SomeGenericInput<T: InputType> {
+    field1: Option<T>,
+    field2: String
+}
+}
+

Note: Each generic parameter must implement InputType, as shown above.

+

The schema generated is:

+
input SomeName {
+  field1: SomeType
+  field2: String!
+}
+
+input SomeOtherName {
+  field1: SomeOtherType
+  field2: String!
+}
+
+

In your resolver method or field of another input object, use as a normal generic type:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(InputObject)]
+struct SomeType { a: i32 }
+#[derive(InputObject)]
+struct SomeOtherType { a: i32 }
+#[derive(InputObject)]
+#[graphql(concrete(name = "SomeName", params(SomeType)))]
+#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))]
+pub struct SomeGenericInput<T: InputType> {
+    field1: Option<T>,
+    field2: String
+}
+#[derive(InputObject)]
+pub struct YetAnotherInput {
+    a: SomeGenericInput<SomeType>,
+    b: SomeGenericInput<SomeOtherType>,
+}
+}
+

You can pass multiple generic types to params(), separated by a comma.

+

If you also want to implement OutputType, then you will need to explicitly declare the input and output type names of the concrete types like so:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject, InputObject)]
+#[graphql(concrete(
+    name = "SomeGenericTypeOut",
+    input_name = "SomeGenericTypeIn",
+    params(i32),
+))]
+pub struct SomeGenericType<T: InputType + OutputType> {
+    field1: Option<T>,
+    field2: String
+}
+}
+

Redacting sensitive data

+

If any part of your input is considered sensitive and you wish to redact it, you can mark it with secret directive. For example:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(InputObject)]
+pub struct CredentialsInput {
+    username: String,
+    #[graphql(secret)]
+    password: String,
+}
+}
+

Flattening fields

+

You can add #[graphql(flatten)] to a field to inline keys from the field type into it's parent. For example:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(InputObject)]
+pub struct ChildInput {
+    b: String,
+    c: String,
+}
+
+#[derive(InputObject)]
+pub struct ParentInput {
+    a: String,
+    #[graphql(flatten)]
+    child: ChildInput,
+}
+
+// Is the same as
+
+#[derive(InputObject)]
+pub struct Input {
+    a: String,
+    b: String,
+    c: String,
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/define_interface.html b/en/define_interface.html new file mode 100644 index 000000000..d5e5d4cde --- /dev/null +++ b/en/define_interface.html @@ -0,0 +1,338 @@ + + + + + + Interface - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Interface

+

Interface is used to abstract Objects with common fields. +Async-graphql implements it as a wrapper. +The wrapper will forward field resolution to the Object that implements this Interface. +Therefore, the Object's fields' type and arguments must match with the Interface's.

+

Async-graphql implements auto conversion from Object to Interface, you only need to call Into::into.

+

Interface field names are transformed to camelCase for the schema definition. +If you need e.g. a snake_cased GraphQL field name, you can use both the name and method attributes.

+
    +
  • When name and method exist together, name is the GraphQL field name and the method is the resolver function name.
  • +
  • When only name exists, name.to_camel_case() is the GraphQL field name and the name is the resolver function name.
  • +
+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Circle {
+    radius: f32,
+}
+
+#[Object]
+impl Circle {
+    async fn area(&self) -> f32 {
+        std::f32::consts::PI * self.radius * self.radius
+    }
+
+    async fn scale(&self, s: f32) -> Shape {
+        Circle { radius: self.radius * s }.into()
+    }
+
+    #[graphql(name = "short_description")]
+    async fn short_description(&self) -> String {
+        "Circle".to_string()
+    }
+}
+
+struct Square {
+    width: f32,
+}
+
+#[Object]
+impl Square {
+    async fn area(&self) -> f32 {
+        self.width * self.width
+    }
+
+    async fn scale(&self, s: f32) -> Shape {
+        Square { width: self.width * s }.into()
+    }
+
+    #[graphql(name = "short_description")]
+    async fn short_description(&self) -> String {
+        "Square".to_string()
+    }
+}
+
+#[derive(Interface)]
+#[graphql(
+    field(name = "area", ty = "f32"),
+    field(name = "scale", ty = "Shape", arg(name = "s", ty = "f32")),
+    field(name = "short_description", method = "short_description", ty = "String")
+)]
+enum Shape {
+    Circle(Circle),
+    Square(Square),
+}
+}
+

Register the interface manually

+

Async-graphql traverses and registers all directly or indirectly referenced types from Schema in the initialization phase. +If an interface is not referenced, it will not exist in the registry, as in the following example , even if MyObject implements MyInterface, +because MyInterface is not referenced in Schema, the MyInterface type will not exist in the registry.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Interface)]
+#[graphql(
+    field(name = "name", ty = "String"),
+)]
+enum MyInterface {
+    MyObject(MyObject),
+}
+
+#[derive(SimpleObject)]
+struct MyObject {
+    name: String,
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn obj(&self) -> MyObject {
+        todo!()
+    }
+}
+
+type MySchema = Schema<Query, EmptyMutation, EmptySubscription>;
+}
+

You need to manually register the MyInterface type when constructing the Schema:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Interface)]
+#[graphql(field(name = "name", ty = "String"))]
+enum MyInterface { MyObject(MyObject) }
+#[derive(SimpleObject)]
+struct MyObject { name: String, }
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+
+Schema::build(Query, EmptyMutation, EmptySubscription)
+    .register_output_type::<MyInterface>()
+    .finish();
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/define_one_of_object.html b/en/define_one_of_object.html new file mode 100644 index 000000000..7a3cef6e8 --- /dev/null +++ b/en/define_one_of_object.html @@ -0,0 +1,257 @@ + + + + + + OneofObject - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

OneofObject

+

A OneofObject is a special type of InputObject, in which only one of its fields must be set and is not-null. +It is especially useful when you want a user to be able to choose between several potential input types.

+

This feature is still an RFC and therefore not yet officially part of the GraphQL spec, but Async-graphql already supports it!

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+#[derive(SimpleObject)]
+struct User { a: i32 }
+use async_graphql::*;
+
+#[derive(OneofObject)]
+enum UserBy {
+    Email(String),
+    RegistrationNumber(i64),
+    Address(Address)
+}
+
+#[derive(InputObject)]
+struct Address {
+    street: String,
+    house_number: String,
+    city: String,
+    zip: String,
+}
+
+struct Query {}
+
+#[Object]
+impl Query {
+    async fn search_users(&self, by: Vec<UserBy>) -> Vec<User> {
+        // ... Searches and returns a list of users ...
+        todo!()
+    }
+}
+}
+

As you can see, a OneofObject is represented by an enum in which each variant contains another InputType. This means that you can use InputObject as variant too.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/define_schema.html b/en/define_schema.html new file mode 100644 index 000000000..893409101 --- /dev/null +++ b/en/define_schema.html @@ -0,0 +1,223 @@ + + + + + + Schema - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Schema

+

After defining the basic types, you need to define a schema to combine them. The schema consists of three types: a query object, a mutation object, and a subscription object, where the mutation object and subscription object are optional.

+

When the schema is created, Async-graphql will traverse all object graphs and register all types. This means that if a GraphQL object is defined but never referenced, this object will not be exposed in the schema.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/define_simple_object.html b/en/define_simple_object.html new file mode 100644 index 000000000..403a95a1e --- /dev/null +++ b/en/define_simple_object.html @@ -0,0 +1,305 @@ + + + + + + SimpleObject - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

SimpleObject

+

SimpleObject directly maps all the fields of a struct to GraphQL object. +If you don't require automatic mapping of fields, see Object.

+

The example below defines an object MyObject which includes the fields a and b. c will be not mapped to GraphQL as it is labelled as #[graphql(skip)]

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+#[derive(SimpleObject)]
+struct MyObject {
+    /// Value a
+    a: i32,
+
+    /// Value b
+    b: i32,
+
+    #[graphql(skip)]
+    c: i32,
+}
+}
+

User-defined resolvers

+

Sometimes most of the fields of a GraphQL object simply return the value of the structure member, but a few +fields are calculated. In this case, the Object macro cannot be used unless you hand-write all the resolvers.

+

The ComplexObject macro works in conjunction with the SimpleObject macro. The SimpleObject derive macro defines +the non-calculated fields, where as the ComplexObject macro let's you write user-defined resolvers for the calculated fields.

+

Resolvers added to ComplexObject adhere to the same rules as resolvers of Object.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+#[graphql(complex)] // NOTE: If you want the `ComplexObject` macro to take effect, this `complex` attribute is required.
+struct MyObj {
+    a: i32,
+    b: i32,
+}
+
+#[ComplexObject]
+impl MyObj {
+    async fn c(&self) -> i32 {
+        self.a + self.b
+    }
+}
+}
+

Used for both input and output

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject, InputObject)]
+#[graphql(input_name = "MyObjInput")] // Note: You must use the input_name attribute to define a new name for the input type, otherwise a runtime error will occur.
+struct MyObj {
+    a: i32,
+    b: i32,
+}
+}
+

Flatten fields

+

You can flatten fields by adding #[graphql(flatten)], i.e.:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+pub struct ChildObject {
+    b: String,
+    c: String,
+}
+
+#[derive(SimpleObject)]
+pub struct ParentObject {
+    a: String,
+    #[graphql(flatten)]
+    child: ChildObject,
+}
+
+// Is the same as
+
+#[derive(SimpleObject)]
+pub struct Object {
+    a: String,
+    b: String,
+    c: String,
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/define_union.html b/en/define_union.html new file mode 100644 index 000000000..531e3113f --- /dev/null +++ b/en/define_union.html @@ -0,0 +1,324 @@ + + + + + + Union - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Union

+

The definition of a Union is similar to an Interface, but with no fields allowed.. +The implementation is quite similar for Async-graphql; from Async-graphql's perspective, Union is a subset of Interface.

+

The following example modified the definition of Interface a little bit and removed fields.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Circle {
+    radius: f32,
+}
+
+#[Object]
+impl Circle {
+    async fn area(&self) -> f32 {
+        std::f32::consts::PI * self.radius * self.radius
+    }
+
+    async fn scale(&self, s: f32) -> Shape {
+        Circle { radius: self.radius * s }.into()
+    }
+}
+
+struct Square {
+    width: f32,
+}
+
+#[Object]
+impl Square {
+    async fn area(&self) -> f32 {
+        self.width * self.width
+    }
+
+    async fn scale(&self, s: f32) -> Shape {
+        Square { width: self.width * s }.into()
+    }
+}
+
+#[derive(Union)]
+enum Shape {
+    Circle(Circle),
+    Square(Square),
+}
+}
+

Flattening nested unions

+

A restriction in GraphQL is the inability to create a union type out of +other union types. All members must be Object. To support nested +unions, we can "flatten" members that are unions, bringing their members up +into the parent union. This is done by applying #[graphql(flatten)] on each +member we want to flatten.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+#[derive(async_graphql::Union)]
+pub enum TopLevelUnion {
+    A(A),
+
+    // Will fail to compile unless we flatten the union member
+    #[graphql(flatten)]
+    B(B),
+}
+
+#[derive(async_graphql::SimpleObject)]
+pub struct A {
+    a: i32,
+    // ...
+}
+
+#[derive(async_graphql::Union)]
+pub enum B {
+    C(C),
+    D(D),
+}
+
+#[derive(async_graphql::SimpleObject)]
+pub struct C {
+    c: i32,
+    // ...
+}
+
+#[derive(async_graphql::SimpleObject)]
+pub struct D {
+    d: i32,
+    // ...
+}
+}
+

The above example transforms the top-level union into this equivalent:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+#[derive(async_graphql::SimpleObject)]
+struct A { a: i32 }
+#[derive(async_graphql::SimpleObject)]
+struct C { c: i32 }
+#[derive(async_graphql::SimpleObject)]
+struct D { d: i32 }
+#[derive(async_graphql::Union)]
+pub enum TopLevelUnion {
+    A(A),
+    C(C),
+    D(D),
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/depth_and_complexity.html b/en/depth_and_complexity.html new file mode 100644 index 000000000..546f36686 --- /dev/null +++ b/en/depth_and_complexity.html @@ -0,0 +1,325 @@ + + + + + + Query complexity and depth - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Query complexity and depth

+

⚠️GraphQL provides a powerful way to query your data, but putting great +power in the hands of your API clients also exposes you to a risk of denial +of service attacks. You can mitigate that risk with Async-graphql by limiting the +complexity and depth of the queries you allow.

+

Expensive Queries

+

Consider a schema that allows listing blog posts. Each blog post is also related to other posts.

+
type Query {
+	posts(count: Int = 10): [Post!]!
+}
+
+type Post {
+	title: String!
+	text: String!
+	related(count: Int = 10): [Post!]!
+}
+
+

It's not too hard to craft a query that will cause a very large response:

+
{
+    posts(count: 100) {
+        related(count: 100) {
+            related(count: 100) {
+                related(count: 100) {
+                    title
+                }
+            }
+        }
+    }
+}
+
+

The size of the response increases exponentially with every other level of the related field. Fortunately, Async-graphql provides +a way to prevent such queries.

+

Limiting Query depth

+

The depth is the number of nesting levels of the field, and the following is a query with a depth of 3.

+
{
+    a {
+        b {
+            c
+        }
+    }
+}
+
+

You can limit the depth when creating Schema. If the query exceeds this limit, an error will occur and the +message Query is nested too deep will be returned.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
+    .limit_depth(5) // Limit the maximum depth to 5
+    .finish();
+}
+

Limiting Query complexity

+

The complexity is the number of fields in the query. The default complexity of each field is 1. Below is a +query with a complexity of 6.

+
{
+    a b c {
+        d {
+            e f
+        }
+    }
+}
+
+

You can limit the complexity when creating the Schema. If the query exceeds this limit, an error will occur +and Query is too complex will be returned.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
+    .limit_complexity(5) // Limit the maximum complexity to 5
+    .finish();
+}
+

Custom Complexity Calculation

+

There are two ways to customize the complexity for non-list type and list type fields.

+

In the following code, the complexity of the value field is 5. The complexity of the values field is count * child_complexity, +child_complexity is a special variable that represents the complexity of the subquery, and count is the parameter of the field, +used to calculate the complexity of the values field, and the type of the return value must be usize.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+
+#[Object]
+impl Query {
+    #[graphql(complexity = 5)]
+    async fn value(&self) -> i32 {
+        todo!()
+    }
+
+    #[graphql(complexity = "count * child_complexity")]
+    async fn values(&self, count: usize) -> i32 {
+        todo!()
+    }
+}
+}
+

Note: The complexity calculation is done in the validation phase and not the execution phase, +so you don't have to worry about partial execution of over-limit queries.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/derived_fields.html b/en/derived_fields.html new file mode 100644 index 000000000..bc4767c75 --- /dev/null +++ b/en/derived_fields.html @@ -0,0 +1,312 @@ + + + + + + Derived fields - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Derived fields

+

Sometimes two fields have the same query logic, but the output type is different. In async-graphql, you can create a derived field for it.

+

In the following example, you already have a date_rfc2822 field outputting the time format in RFC2822 format, and then reuse it to derive a new date_rfc3339 field.

+
#![allow(unused)]
+fn main() {
+extern crate chrono;
+use chrono::Utc;
+extern crate async_graphql;
+use async_graphql::*;
+struct DateRFC3339(chrono::DateTime<Utc>);
+struct DateRFC2822(chrono::DateTime<Utc>);
+
+#[Scalar]
+impl ScalarType for DateRFC3339 {
+  fn parse(value: Value) -> InputValueResult<Self> { todo!() } 
+
+  fn to_value(&self) -> Value {
+    Value::String(self.0.to_rfc3339())
+  }
+}
+
+#[Scalar]
+impl ScalarType for DateRFC2822 {
+  fn parse(value: Value) -> InputValueResult<Self> { todo!() } 
+
+  fn to_value(&self) -> Value {
+    Value::String(self.0.to_rfc2822())
+  }
+}
+
+impl From<DateRFC2822> for DateRFC3339 {
+    fn from(value: DateRFC2822) -> Self {
+      DateRFC3339(value.0)
+    }
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    #[graphql(derived(name = "date_rfc3339", into = "DateRFC3339"))]
+    async fn date_rfc2822(&self, arg: String) -> DateRFC2822 {
+        todo!()
+    }
+}
+}
+

It will render a GraphQL like:

+
type Query {
+	date_rfc2822(arg: String): DateRFC2822!
+	date_rfc3339(arg: String): DateRFC3339!
+}
+
+

Wrapper types

+

A derived field won't be able to manage everything easily: Rust's orphan rule requires that either the +trait or the type for which you are implementing the trait must be defined in the same crate as the impl, so the following code cannot be compiled:

+
impl From<Vec<U>> for Vec<T> {
+  ...
+}
+

So you wouldn't be able to generate derived fields for existing wrapper type structures like Vec or Option. But when you implement a From<U> for T you should be able to derived a From<Vec<U>> for Vec<T> and a From<Option<U>> for Option<T>. +We included a with parameter to help you define a function to call instead of using the Into trait implementation between wrapper structures.

+

Example

+
#![allow(unused)]
+fn main() {
+extern crate serde;
+use serde::{Serialize, Deserialize};
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Serialize, Deserialize, Clone)]
+struct ValueDerived(String);
+
+#[derive(Serialize, Deserialize, Clone)]
+struct ValueDerived2(String);
+
+scalar!(ValueDerived);
+scalar!(ValueDerived2);
+
+impl From<ValueDerived> for ValueDerived2 {
+    fn from(value: ValueDerived) -> Self {
+        ValueDerived2(value.0)
+    }
+}
+
+fn option_to_option<T, U: From<T>>(value: Option<T>) -> Option<U> {
+    value.map(|x| x.into())
+}
+
+#[derive(SimpleObject)]
+struct TestObj {
+    #[graphql(derived(owned, name = "value2", into = "Option<ValueDerived2>", with = "option_to_option"))]
+    pub value1: Option<ValueDerived>,
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/elasticlunr.min.js b/en/elasticlunr.min.js new file mode 100644 index 000000000..94b20dd2e --- /dev/null +++ b/en/elasticlunr.min.js @@ -0,0 +1,10 @@ +/** + * elasticlunr - http://weixsong.github.io + * Lightweight full-text search engine in Javascript for browser search and offline search. - 0.9.5 + * + * Copyright (C) 2017 Oliver Nightingale + * Copyright (C) 2017 Wei Song + * MIT Licensed + * @license + */ +!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o + + + + + Error extensions - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Error extensions

+

To quote the graphql-spec:

+
+

GraphQL services may provide an additional entry to errors with key extensions. +This entry, if set, must have a map as its value. This entry is reserved for implementer to add +additional information to errors however they see fit, and there are no additional restrictions on +its contents.

+
+

Example

+

I would recommend on checking out this async-graphql example as a quickstart.

+

General Concept

+

In async-graphql all user-facing errors are cast to the Error type which by default provides +the error message exposed by std::fmt::Display. However, Error actually provides an additional information that can extend the error.

+

A resolver looks like this:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query {
+async fn parse_with_extensions(&self) -> Result<i32, Error> {
+    Err(Error::new("MyMessage").extend_with(|_, e| e.set("details", "CAN_NOT_FETCH")))
+}
+}
+}
+

may then return a response like this:

+
{
+  "errors": [
+    {
+      "message": "MyMessage",
+      "locations": [ ... ],
+      "path": [ ... ],
+      "extensions": {
+        "details": "CAN_NOT_FETCH",
+      }
+    }
+  ]
+}
+
+

ErrorExtensions

+

Constructing new Errors by hand quickly becomes tedious. That is why async-graphql provides +two convenience traits for casting your errors to the appropriate Error with +extensions.

+

The easiest way to provide extensions to any error is by calling extend_with on the error. +This will on the fly convert any error into a Error with the given extension.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+use std::num::ParseIntError;
+#[Object]
+impl Query {
+async fn parse_with_extensions(&self) -> Result<i32> {
+     Ok("234a"
+         .parse()
+         .map_err(|err: ParseIntError| err.extend_with(|_err, e| e.set("code", 404)))?)
+}
+}
+}
+

Implementing ErrorExtensions for custom errors.

+

If you find yourself attaching extensions to your errors all over the place you might want to consider +implementing the trait on your custom error type directly.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate thiserror;
+use async_graphql::*;
+#[derive(Debug, thiserror::Error)]
+pub enum MyError {
+    #[error("Could not find resource")]
+    NotFound,
+
+    #[error("ServerError")]
+    ServerError(String),
+
+    #[error("No Extensions")]
+    ErrorWithoutExtensions,
+}
+
+impl ErrorExtensions for MyError {
+    // lets define our base extensions
+    fn extend(&self) -> Error {
+        Error::new(format!("{}", self)).extend_with(|err, e| 
+            match self {
+              MyError::NotFound => e.set("code", "NOT_FOUND"),
+              MyError::ServerError(reason) => e.set("reason", reason.clone()),
+              MyError::ErrorWithoutExtensions => {}
+          })
+    }
+}
+}
+

This way you only need to call extend on your error to deliver the error message alongside the provided extensions. +Or further extend your error through extend_with.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate thiserror;
+use async_graphql::*;
+#[derive(Debug, thiserror::Error)]
+pub enum MyError {
+    #[error("Could not find resource")]
+    NotFound,
+
+    #[error("ServerError")]
+    ServerError(String),
+
+    #[error("No Extensions")]
+    ErrorWithoutExtensions,
+}
+struct Query;
+#[Object]
+impl Query {
+async fn parse_with_extensions_result(&self) -> Result<i32> {
+    // Err(MyError::NotFound.extend())
+    // OR
+    Err(MyError::NotFound.extend_with(|_, e| e.set("on_the_fly", "some_more_info")))
+}
+}
+}
+
{
+  "errors": [
+    {
+      "message": "NotFound",
+      "locations": [ ... ],
+      "path": [ ... ],
+      "extensions": {
+        "code": "NOT_FOUND",
+        "on_the_fly": "some_more_info"
+      }
+    }
+  ]
+}
+
+

ResultExt

+

This trait enables you to call extend_err directly on results. So the above code becomes less verbose.

+
// @todo figure out why this example does not compile!
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query {
+async fn parse_with_extensions(&self) -> Result<i32> {
+     Ok("234a"
+         .parse()
+         .extend_err(|_, e| e.set("code", 404))?)
+}
+}
+

Chained extensions

+

Since ErrorExtensions and ResultExt are implemented for any type &E where E: std::fmt::Display +we can chain the extension together.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query {
+async fn parse_with_extensions(&self) -> Result<i32> {
+    match "234a".parse() {
+        Ok(n) => Ok(n),
+        Err(e) => Err(e
+            .extend_with(|_, e| e.set("code", 404))
+            .extend_with(|_, e| e.set("details", "some more info.."))
+            // keys may also overwrite previous keys...
+            .extend_with(|_, e| e.set("code", 500))),
+    }
+}
+}
+}
+

Expected response:

+
{
+  "errors": [
+    {
+      "message": "MyMessage",
+      "locations": [ ... ],
+      "path": [ ... ],
+      "extensions": {
+      	"details": "some more info...",
+        "code": 500,
+      }
+    }
+  ]
+}
+
+

Pitfalls

+

Rust does not provide stable trait specialization yet. +That is why ErrorExtensions is actually implemented for &E where E: std::fmt::Display +instead of E: std::fmt::Display. Some specialization is provided through +Autoref-based stable specialization. +The disadvantage is that the below code does NOT compile:

+
async fn parse_with_extensions_result(&self) -> Result<i32> {
+    // the trait `error::ErrorExtensions` is not implemented
+    // for `std::num::ParseIntError`
+    "234a".parse().extend_err(|_, e| e.set("code", 404))
+}
+

however this does:

+
async fn parse_with_extensions_result(&self) -> Result<i32> {
+    // does work because ErrorExtensions is implemented for &ParseIntError
+    "234a"
+      .parse()
+      .map_err(|ref e: ParseIntError| e.extend_with(|_, e| e.set("code", 404)))
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/error_handling.html b/en/error_handling.html new file mode 100644 index 000000000..4372b8553 --- /dev/null +++ b/en/error_handling.html @@ -0,0 +1,247 @@ + + + + + + Error handling - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Error handling

+

Resolve can return a Result, which has the following definition:

+
type Result<T> = std::result::Result<T, Error>;
+

Any Error that implements std::fmt::Display can be converted to Error and you can extend the error message.

+

The following example shows how to parse an input string to an integer. When parsing fails, it will return an error and attach an error message. +See the Error Extensions section of this book for more details.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use std::num::ParseIntError;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn parse_with_extensions(&self, input: String) -> Result<i32> {
+        Ok("234a"
+            .parse()
+            .map_err(|err: ParseIntError| err.extend_with(|_, e| e.set("code", 400)))?)
+    }
+}
+}
+

Errors in subscriptions

+

Errors can be returned from subscription resolvers as well, using a return type of the form:

+
async fn my_subscription_resolver(&self) -> impl Stream<Item = Result<MyItem, MyError>> { ... }
+

Note however that the MyError struct must have Clone implemented, due to the restrictions placed by the Subscription macro. One way to accomplish this is by creating a custom error type, with #[derive(Clone)], as seen here.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/extensions.html b/en/extensions.html new file mode 100644 index 000000000..f3cefb4ce --- /dev/null +++ b/en/extensions.html @@ -0,0 +1,222 @@ + + + + + + Extensions - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Extensions

+

async-graphql has the capability to be extended with extensions without having to modify the original source code. A lot of features can be added this way, and a lot of extensions already exist.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/extensions_available.html b/en/extensions_available.html new file mode 100644 index 000000000..3f78b0011 --- /dev/null +++ b/en/extensions_available.html @@ -0,0 +1,257 @@ + + + + + + Available extensions - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Extensions available

+

There are a lot of available extensions in the async-graphql to empower your GraphQL Server, some of these documentations are documented here.

+

Analyzer

+

Available in the repository

+

The analyzer extension will output a field containing complexity and depth in the response extension field of each query.

+

Apollo Persisted Queries

+

Available in the repository

+

To improve network performance for large queries, you can enable this Persisted Queries extension. With this extension enabled, each unique query is associated to a unique identifier, so clients can send this identifier instead of the corresponding query string to reduce requests sizes.

+

This extension doesn't force you to use some cache strategy, you can choose the caching strategy you want, you'll just have to implement the CacheStorage trait:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[async_trait::async_trait]
+pub trait CacheStorage: Send + Sync + Clone + 'static {
+    /// Load the query by `key`.
+    async fn get(&self, key: String) -> Option<String>;
+    /// Save the query by `key`.
+    async fn set(&self, key: String, query: String);
+}
+}
+

References: Apollo doc - Persisted Queries

+

Apollo Tracing

+

Available in the repository

+

Apollo Tracing is an extension which includes analytics data for your queries. This extension works to follow the old and now deprecated Apollo Tracing Spec. If you want to check the newer Apollo Reporting Protocol, it's implemented by async-graphql Apollo studio extension for Apollo Studio.

+

Apollo Studio

+

Available at async-graphql/async_graphql_apollo_studio_extension

+

Apollo Studio is a cloud platform that helps you build, validate, and secure your organization's graph (description from the official documentation). It's a service allowing you to monitor & work with your team around your GraphQL Schema. async-graphql provides an extension implementing the official Apollo Specification available at async-graphql-extension-apollo-tracing and Crates.io.

+

Logger

+

Available in the repository

+

Logger is a simple extension allowing you to add some logging feature to async-graphql. It's also a good example to learn how to create your own extension.

+

OpenTelemetry

+

Available in the repository

+

OpenTelemetry is an extension providing an integration with the opentelemetry crate to allow your application to capture distributed traces and metrics from async-graphql.

+

Tracing

+

Available in the repository

+

Tracing is a simple extension allowing you to add some tracing feature to async-graphql. A little like the Logger extension.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/extensions_inner_working.html b/en/extensions_inner_working.html new file mode 100644 index 000000000..95b8d804c --- /dev/null +++ b/en/extensions_inner_working.html @@ -0,0 +1,411 @@ + + + + + + How extensions are working - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

How extensions are defined

+

An async-graphql extension is defined by implementing the trait Extension associated. The Extension trait allows you to insert custom code to some several steps used to respond to GraphQL's queries through async-graphql. With Extensions, your application can hook into the GraphQL's requests lifecycle to add behaviors about incoming requests or outgoing response.

+

Extensions are a lot like middleware from other frameworks, be careful when using those: when you use an extension it'll be run for every GraphQL request.

+

Across every step, you'll have the ExtensionContext supplied with data about your current request execution. Feel free to check how it's constructed in the code, documentation about it will soon come.

+

A word about middleware

+

For those who don't know, let's dig deeper into what is a middleware:

+
async fn middleware(&self, ctx: &ExtensionContext<'_>, next: NextMiddleware<'_>) -> MiddlewareResult {
+  // Logic to your middleware.
+
+  /*
+   * Final step to your middleware, we call the next function which will trigger
+   * the execution of the next middleware. It's like a `callback` in JavaScript.
+   */
+  next.run(ctx).await
+}
+

As you have seen, a Middleware is only a function calling the next function at the end, but we could also do a middleware with the next.run function at the start. This is where it's becoming tricky: depending on where you put your logic and where is the next.run call, your logic won't have the same execution order.

+

Depending on your logic code, you'll want to process it before or after the next.run call. If you need more information about middlewares, there are a lot of things on the web.

+

Processing of a query

+

There are several steps to go to process a query to completion, you'll be able to create extension based on these hooks.

+

request

+

First, when we receive a request, if it's not a subscription, the first function to be called will be request, it's the first step, it's the function called at the incoming request, and it's also the function which will output the response to the user.

+

Default implementation for request:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
+    next.run(ctx).await
+}
+}
+}
+

Depending on where you put your logic code, it'll be executed at the beginning or at the end of the query being processed.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
+    // The code here will be run before the prepare_request is executed.
+    let result = next.run(ctx).await;
+    // The code after the completion of this future will be after the processing, just before sending the result to the user.
+    result
+}
+}
+}
+

prepare_request

+

Just after the request, we will have the prepare_request lifecycle, which will be hooked.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+async fn prepare_request(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    request: Request,
+    next: NextPrepareRequest<'_>,
+) -> ServerResult<Request> {
+    // The code here will be run before the prepare_request is executed, just after the request lifecycle hook.
+    let result = next.run(ctx, request).await;
+    // The code here will be run just after the prepare_request
+    result
+}
+}
+}
+

parse_query

+

The parse_query will create a GraphQL ExecutableDocument on your query, it'll check if the query is valid for the GraphQL Spec. Usually the implemented spec in async-graphql tends to be the last stable one (October2021).

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+use async_graphql::parser::types::ExecutableDocument;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+/// Called at parse query.
+async fn parse_query(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    // The raw query
+    query: &str,
+    // The variables
+    variables: &Variables,
+    next: NextParseQuery<'_>,
+) -> ServerResult<ExecutableDocument> {
+    next.run(ctx, query, variables).await
+}
+}
+}
+

validation

+

The validation step will check (depending on your validation_mode) rules the query should abide to and give the client data about why the query is not valid.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+/// Called at validation query.
+async fn validation(
+  &self,
+  ctx: &ExtensionContext<'_>,
+  next: NextValidation<'_>,
+) -> Result<ValidationResult, Vec<ServerError>> {
+  next.run(ctx).await
+}
+}
+}
+

execute

+

The execution step is a huge one, it'll start the execution of the query by calling each resolver concurrently for a Query and serially for a Mutation.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+/// Called at execute query.
+async fn execute(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    operation_name: Option<&str>,
+    next: NextExecute<'_>,
+) -> Response {
+    // Before starting resolving the whole query
+    let result = next.run(ctx, operation_name).await;
+    // After resolving the whole query
+    result
+}
+}
+}
+

resolve

+

The resolve step is launched for each field.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware { 
+/// Called at resolve field.
+async fn resolve(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    info: ResolveInfo<'_>,
+    next: NextResolve<'_>,
+) -> ServerResult<Option<Value>> {
+    // Logic before resolving the field
+    let result = next.run(ctx, info).await;
+    // Logic after resolving the field
+    result
+}
+}
+}
+

subscribe

+

The subscribe lifecycle has the same behavior as the request but for a Subscription.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+use futures_util::stream::BoxStream;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+/// Called at subscribe request.
+fn subscribe<'s>(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    stream: BoxStream<'s, Response>,
+    next: NextSubscribe<'_>,
+) -> BoxStream<'s, Response> {
+    next.run(ctx, stream)
+}
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/favicon.png b/en/favicon.png new file mode 100644 index 000000000..a5b1aa16c Binary files /dev/null and b/en/favicon.png differ diff --git a/en/favicon.svg b/en/favicon.svg new file mode 100644 index 000000000..90e0ea58b --- /dev/null +++ b/en/favicon.svg @@ -0,0 +1,22 @@ + + + + + diff --git a/en/field_guard.html b/en/field_guard.html new file mode 100644 index 000000000..95c837f98 --- /dev/null +++ b/en/field_guard.html @@ -0,0 +1,309 @@ + + + + + + Field guard - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Field Guard

+

You can define a guard for the fields of Object, SimpleObject, ComplexObject and Subscription, it will be executed before calling the resolver function, and an error will be returned if it fails.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Eq, PartialEq, Copy, Clone)]
+enum Role {
+    Admin,
+    Guest,
+}
+
+struct RoleGuard {
+    role: Role,
+}
+
+impl RoleGuard {
+    fn new(role: Role) -> Self {
+        Self { role }
+    }
+}
+
+impl Guard for RoleGuard {
+    async fn check(&self, ctx: &Context<'_>) -> Result<()> {
+        if ctx.data_opt::<Role>() == Some(&self.role) {
+            Ok(())
+        } else {
+            Err("Forbidden".into())
+        }
+    }
+}
+}
+

Use it with the guard attribute:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Eq, PartialEq, Copy, Clone)]
+enum Role { Admin, Guest, }
+struct RoleGuard { role: Role, }
+impl RoleGuard { fn new(role: Role) -> Self { Self { role } } }
+impl Guard for RoleGuard { async fn check(&self, ctx: &Context<'_>) -> Result<()> { todo!() } }
+#[derive(SimpleObject)]
+struct Query {
+    /// Only allow Admin
+    #[graphql(guard = "RoleGuard::new(Role::Admin)")]
+    value1: i32,
+    /// Allow Admin or Guest
+    #[graphql(guard = "RoleGuard::new(Role::Admin).or(RoleGuard::new(Role::Guest))")]
+    value2: i32,
+}
+}
+

Use parameter value

+

Sometimes guards need to use field parameters, you need to pass the parameter value when creating the guard like this:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct EqGuard {
+    expect: i32,
+    actual: i32,
+}
+
+impl EqGuard {
+    fn new(expect: i32, actual: i32) -> Self {
+        Self { expect, actual }
+    }
+}
+
+impl Guard for EqGuard {
+    async fn check(&self, _ctx: &Context<'_>) -> Result<()> {
+        if self.expect != self.actual {
+            Err("Forbidden".into())
+        } else {
+            Ok(())
+        }
+    }
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    #[graphql(guard = "EqGuard::new(100, value)")]
+    async fn get(&self, value: i32) -> i32 {
+        value
+    }
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/fonts/OPEN-SANS-LICENSE.txt b/en/fonts/OPEN-SANS-LICENSE.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/en/fonts/OPEN-SANS-LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/en/fonts/SOURCE-CODE-PRO-LICENSE.txt b/en/fonts/SOURCE-CODE-PRO-LICENSE.txt new file mode 100644 index 000000000..366206f54 --- /dev/null +++ b/en/fonts/SOURCE-CODE-PRO-LICENSE.txt @@ -0,0 +1,93 @@ +Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/en/fonts/fonts.css b/en/fonts/fonts.css new file mode 100644 index 000000000..698e1e19e --- /dev/null +++ b/en/fonts/fonts.css @@ -0,0 +1,100 @@ +/* Open Sans is licensed under the Apache License, Version 2.0. See http://www.apache.org/licenses/LICENSE-2.0 */ +/* Source Code Pro is under the Open Font License. See https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL */ + +/* open-sans-300 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + src: local('Open Sans Light'), local('OpenSans-Light'), + url('../fonts/open-sans-v17-all-charsets-300.woff2') format('woff2'); +} + +/* open-sans-300italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + src: local('Open Sans Light Italic'), local('OpenSans-LightItalic'), + url('../fonts/open-sans-v17-all-charsets-300italic.woff2') format('woff2'); +} + +/* open-sans-regular - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + src: local('Open Sans Regular'), local('OpenSans-Regular'), + url('../fonts/open-sans-v17-all-charsets-regular.woff2') format('woff2'); +} + +/* open-sans-italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + src: local('Open Sans Italic'), local('OpenSans-Italic'), + url('../fonts/open-sans-v17-all-charsets-italic.woff2') format('woff2'); +} + +/* open-sans-600 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), + url('../fonts/open-sans-v17-all-charsets-600.woff2') format('woff2'); +} + +/* open-sans-600italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + src: local('Open Sans SemiBold Italic'), local('OpenSans-SemiBoldItalic'), + url('../fonts/open-sans-v17-all-charsets-600italic.woff2') format('woff2'); +} + +/* open-sans-700 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 700; + src: local('Open Sans Bold'), local('OpenSans-Bold'), + url('../fonts/open-sans-v17-all-charsets-700.woff2') format('woff2'); +} + +/* open-sans-700italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 700; + src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'), + url('../fonts/open-sans-v17-all-charsets-700italic.woff2') format('woff2'); +} + +/* open-sans-800 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 800; + src: local('Open Sans ExtraBold'), local('OpenSans-ExtraBold'), + url('../fonts/open-sans-v17-all-charsets-800.woff2') format('woff2'); +} + +/* open-sans-800italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 800; + src: local('Open Sans ExtraBold Italic'), local('OpenSans-ExtraBoldItalic'), + url('../fonts/open-sans-v17-all-charsets-800italic.woff2') format('woff2'); +} + +/* source-code-pro-500 - latin_vietnamese_latin-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Source Code Pro'; + font-style: normal; + font-weight: 500; + src: url('../fonts/source-code-pro-v11-all-charsets-500.woff2') format('woff2'); +} diff --git a/en/fonts/open-sans-v17-all-charsets-300.woff2 b/en/fonts/open-sans-v17-all-charsets-300.woff2 new file mode 100644 index 000000000..9f51be370 Binary files /dev/null and b/en/fonts/open-sans-v17-all-charsets-300.woff2 differ diff --git a/en/fonts/open-sans-v17-all-charsets-300italic.woff2 b/en/fonts/open-sans-v17-all-charsets-300italic.woff2 new file mode 100644 index 000000000..2f5454484 Binary files /dev/null and b/en/fonts/open-sans-v17-all-charsets-300italic.woff2 differ diff --git a/en/fonts/open-sans-v17-all-charsets-600.woff2 b/en/fonts/open-sans-v17-all-charsets-600.woff2 new file mode 100644 index 000000000..f503d558d Binary files /dev/null and b/en/fonts/open-sans-v17-all-charsets-600.woff2 differ diff --git a/en/fonts/open-sans-v17-all-charsets-600italic.woff2 b/en/fonts/open-sans-v17-all-charsets-600italic.woff2 new file mode 100644 index 000000000..c99aabe80 Binary files /dev/null and b/en/fonts/open-sans-v17-all-charsets-600italic.woff2 differ diff --git a/en/fonts/open-sans-v17-all-charsets-700.woff2 b/en/fonts/open-sans-v17-all-charsets-700.woff2 new file mode 100644 index 000000000..421a1ab25 Binary files /dev/null and b/en/fonts/open-sans-v17-all-charsets-700.woff2 differ diff --git a/en/fonts/open-sans-v17-all-charsets-700italic.woff2 b/en/fonts/open-sans-v17-all-charsets-700italic.woff2 new file mode 100644 index 000000000..12ce3d20d Binary files /dev/null and b/en/fonts/open-sans-v17-all-charsets-700italic.woff2 differ diff --git a/en/fonts/open-sans-v17-all-charsets-800.woff2 b/en/fonts/open-sans-v17-all-charsets-800.woff2 new file mode 100644 index 000000000..c94a223b0 Binary files /dev/null and b/en/fonts/open-sans-v17-all-charsets-800.woff2 differ diff --git a/en/fonts/open-sans-v17-all-charsets-800italic.woff2 b/en/fonts/open-sans-v17-all-charsets-800italic.woff2 new file mode 100644 index 000000000..eed7d3c63 Binary files /dev/null and b/en/fonts/open-sans-v17-all-charsets-800italic.woff2 differ diff --git a/en/fonts/open-sans-v17-all-charsets-italic.woff2 b/en/fonts/open-sans-v17-all-charsets-italic.woff2 new file mode 100644 index 000000000..398b68a08 Binary files /dev/null and b/en/fonts/open-sans-v17-all-charsets-italic.woff2 differ diff --git a/en/fonts/open-sans-v17-all-charsets-regular.woff2 b/en/fonts/open-sans-v17-all-charsets-regular.woff2 new file mode 100644 index 000000000..8383e94c6 Binary files /dev/null and b/en/fonts/open-sans-v17-all-charsets-regular.woff2 differ diff --git a/en/fonts/source-code-pro-v11-all-charsets-500.woff2 b/en/fonts/source-code-pro-v11-all-charsets-500.woff2 new file mode 100644 index 000000000..722245682 Binary files /dev/null and b/en/fonts/source-code-pro-v11-all-charsets-500.woff2 differ diff --git a/en/generic.html b/en/generic.html new file mode 100644 index 000000000..79571c015 --- /dev/null +++ b/en/generic.html @@ -0,0 +1,312 @@ + + + + + + Generics - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Generics

+

It is possible to define reusable objects using generics; however +each concrete instantiation of a generic object must be given a unique GraphQL type name. +There are two ways of specifying these concrete names: concrete instantiation and the TypeName trait.

+

Concrete Instantiation

+

In the following example, two SimpleObject types are created:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct SomeType { a: i32 }
+#[derive(SimpleObject)]
+struct SomeOtherType { a: i32 }
+#[derive(SimpleObject)]
+#[graphql(concrete(name = "SomeName", params(SomeType)))]
+#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))]
+pub struct SomeGenericObject<T: OutputType> {
+    field1: Option<T>,
+    field2: String
+}
+}
+

Note: Each generic parameter must implement OutputType, as shown above.

+

The schema generated is:

+
# SomeGenericObject<SomeType>
+type SomeName {
+  field1: SomeType
+  field2: String!
+}
+
+# SomeGenericObject<SomeOtherType>
+type SomeOtherName {
+  field1: SomeOtherType
+  field2: String!
+}
+
+

In your resolver method or field of another object, use as a normal generic type:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct SomeType { a: i32 }
+#[derive(SimpleObject)]
+struct SomeOtherType { a: i32 }
+#[derive(SimpleObject)]
+#[graphql(concrete(name = "SomeName", params(SomeType)))]
+#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))]
+pub struct SomeGenericObject<T: OutputType> {
+    field1: Option<T>,
+    field2: String,
+}
+#[derive(SimpleObject)]
+pub struct YetAnotherObject {
+    a: SomeGenericObject<SomeType>,
+    b: SomeGenericObject<SomeOtherType>,
+}
+}
+

You can pass multiple generic types to params(), separated by a comma.

+

TypeName trait

+

Some type names can be derived.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use std::borrow::Cow;
+#[derive(SimpleObject)]
+#[graphql(name_type)] // Use `TypeName` trait
+struct Bag<T: OutputType> {
+    content: Vec<T>,
+    len: usize,
+}
+
+impl<T: OutputType> TypeName for Bag<T> {
+    fn type_name() -> Cow<'static, str> {
+        format!("{}Bag", <T as OutputType>::type_name()).into()
+    }
+}
+}
+

Using bool and String the generated schema is:

+
# Bag<bool>
+type BooleanBag {
+    content: [Boolean!]!
+    len: Int!
+}
+
+# Bag<String>
+type StringBag {
+    content: [String!]!
+    len: Int!
+}
+
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/highlight.css b/en/highlight.css new file mode 100644 index 000000000..352c79b96 --- /dev/null +++ b/en/highlight.css @@ -0,0 +1,83 @@ +/* + * An increased contrast highlighting scheme loosely based on the + * "Base16 Atelier Dune Light" theme by Bram de Haan + * (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) + * Original Base16 color scheme by Chris Kempson + * (https://github.com/chriskempson/base16) + */ + +/* Comment */ +.hljs-comment, +.hljs-quote { + color: #575757; +} + +/* Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-attr, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #d70025; +} + +/* Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #b21e00; +} + +/* Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #008200; +} + +/* Blue */ +.hljs-title, +.hljs-section { + color: #0030f2; +} + +/* Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #9d00ec; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f6f7f6; + color: #000; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-addition { + color: #22863a; + background-color: #f0fff4; +} + +.hljs-deletion { + color: #b31d28; + background-color: #ffeef0; +} diff --git a/en/highlight.js b/en/highlight.js new file mode 100644 index 000000000..18d24345b --- /dev/null +++ b/en/highlight.js @@ -0,0 +1,54 @@ +/* + Highlight.js 10.1.1 (93fd0d73) + License: BSD-3-Clause + Copyright (c) 2006-2020, Ivan Sagalaev +*/ +var hljs=function(){"use strict";function e(n){Object.freeze(n);var t="function"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!Object.hasOwnProperty.call(n,r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r])})),n}class n{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data}ignoreMatch(){this.ignore=!0}}function t(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function r(e,...n){var t={};for(const n in e)t[n]=e[n];return n.forEach((function(e){for(const n in e)t[n]=e[n]})),t}function a(e){return e.nodeName.toLowerCase()}var i=Object.freeze({__proto__:null,escapeHTML:t,inherit:r,nodeStream:function(e){var n=[];return function e(t,r){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?r+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:r,node:i}),r=e(i,r),a(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:r,node:i}));return r}(e,0),n},mergeStreams:function(e,n,r){var i=0,s="",o=[];function l(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function u(e){s+=""}function d(e){("start"===e.event?c:u)(e.node)}for(;e.length||n.length;){var g=l();if(s+=t(r.substring(i,g[0].offset)),i=g[0].offset,g===e){o.reverse().forEach(u);do{d(g.splice(0,1)[0]),g=l()}while(g===e&&g.length&&g[0].offset===i);o.reverse().forEach(c)}else"start"===g[0].event?o.push(g[0].node):o.pop(),d(g.splice(0,1)[0])}return s+t(r.substr(i))}});const s="",o=e=>!!e.kind;class l{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=t(e)}openNode(e){if(!o(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){o(e)&&(this.buffer+=s)}value(){return this.buffer}span(e){this.buffer+=``}}class c{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every(e=>"string"==typeof e)?e.children=[e.children.join("")]:e.children.forEach(e=>{c._collapse(e)}))}}class u extends c{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){const t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new l(this,this.options).value()}finalize(){return!0}}function d(e){return e?"string"==typeof e?e:e.source:null}const g="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",h={begin:"\\\\[\\s\\S]",relevance:0},f={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[h]},p={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[h]},b={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},m=function(e,n,t={}){var a=r({className:"comment",begin:e,end:n,contains:[]},t);return a.contains.push(b),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a},v=m("//","$"),x=m("/\\*","\\*/"),E=m("#","$");var _=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:g,BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const n=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map(e=>d(e)).join("")}(n,/.*\b/,e.binary,/\b.*/)),r({className:"meta",begin:n,end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)},BACKSLASH_ESCAPE:h,APOS_STRING_MODE:f,QUOTE_STRING_MODE:p,PHRASAL_WORDS_MODE:b,COMMENT:m,C_LINE_COMMENT_MODE:v,C_BLOCK_COMMENT_MODE:x,HASH_COMMENT_MODE:E,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:g,relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[h,{begin:/\[/,end:/\]/,relevance:0,contains:[h]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{n.data._beginMatch!==e[1]&&n.ignoreMatch()}})}}),N="of and for in not or if then".split(" ");function w(e,n){return n?+n:function(e){return N.includes(e.toLowerCase())}(e)?0:1}const R=t,y=r,{nodeStream:k,mergeStreams:O}=i,M=Symbol("nomatch");return function(t){var a=[],i={},s={},o=[],l=!0,c=/(^(<[^>]+>|\t|)+|\n)/gm,g="Could not find the language '{}', did you forget to load/include a language module?";const h={disableAutodetect:!0,name:"Plain text",contains:[]};var f={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:u};function p(e){return f.noHighlightRe.test(e)}function b(e,n,t,r){var a={code:n,language:e};S("before:highlight",a);var i=a.result?a.result:m(a.language,a.code,t,r);return i.code=a.code,S("after:highlight",i),i}function m(e,t,a,s){var o=t;function c(e,n){var t=E.case_insensitive?n[0].toLowerCase():n[0];return Object.prototype.hasOwnProperty.call(e.keywords,t)&&e.keywords[t]}function u(){null!=y.subLanguage?function(){if(""!==A){var e=null;if("string"==typeof y.subLanguage){if(!i[y.subLanguage])return void O.addText(A);e=m(y.subLanguage,A,!0,k[y.subLanguage]),k[y.subLanguage]=e.top}else e=v(A,y.subLanguage.length?y.subLanguage:null);y.relevance>0&&(I+=e.relevance),O.addSublanguage(e.emitter,e.language)}}():function(){if(!y.keywords)return void O.addText(A);let e=0;y.keywordPatternRe.lastIndex=0;let n=y.keywordPatternRe.exec(A),t="";for(;n;){t+=A.substring(e,n.index);const r=c(y,n);if(r){const[e,a]=r;O.addText(t),t="",I+=a,O.addKeyword(n[0],e)}else t+=n[0];e=y.keywordPatternRe.lastIndex,n=y.keywordPatternRe.exec(A)}t+=A.substr(e),O.addText(t)}(),A=""}function h(e){return e.className&&O.openNode(e.className),y=Object.create(e,{parent:{value:y}})}function p(e){return 0===y.matcher.regexIndex?(A+=e[0],1):(L=!0,0)}var b={};function x(t,r){var i=r&&r[0];if(A+=t,null==i)return u(),0;if("begin"===b.type&&"end"===r.type&&b.index===r.index&&""===i){if(A+=o.slice(r.index,r.index+1),!l){const n=Error("0 width match regex");throw n.languageName=e,n.badRule=b.rule,n}return 1}if(b=r,"begin"===r.type)return function(e){var t=e[0],r=e.rule;const a=new n(r),i=[r.__beforeBegin,r["on:begin"]];for(const n of i)if(n&&(n(e,a),a.ignore))return p(t);return r&&r.endSameAsBegin&&(r.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),r.skip?A+=t:(r.excludeBegin&&(A+=t),u(),r.returnBegin||r.excludeBegin||(A=t)),h(r),r.returnBegin?0:t.length}(r);if("illegal"===r.type&&!a){const e=Error('Illegal lexeme "'+i+'" for mode "'+(y.className||"")+'"');throw e.mode=y,e}if("end"===r.type){var s=function(e){var t=e[0],r=o.substr(e.index),a=function e(t,r,a){let i=function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(t.endRe,a);if(i){if(t["on:end"]){const e=new n(t);t["on:end"](r,e),e.ignore&&(i=!1)}if(i){for(;t.endsParent&&t.parent;)t=t.parent;return t}}if(t.endsWithParent)return e(t.parent,r,a)}(y,e,r);if(!a)return M;var i=y;i.skip?A+=t:(i.returnEnd||i.excludeEnd||(A+=t),u(),i.excludeEnd&&(A=t));do{y.className&&O.closeNode(),y.skip||y.subLanguage||(I+=y.relevance),y=y.parent}while(y!==a.parent);return a.starts&&(a.endSameAsBegin&&(a.starts.endRe=a.endRe),h(a.starts)),i.returnEnd?0:t.length}(r);if(s!==M)return s}if("illegal"===r.type&&""===i)return 1;if(B>1e5&&B>3*r.index)throw Error("potential infinite loop, way more iterations than matches");return A+=i,i.length}var E=T(e);if(!E)throw console.error(g.replace("{}",e)),Error('Unknown language: "'+e+'"');var _=function(e){function n(n,t){return RegExp(d(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class t{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n="|"){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i0&&(a+=n),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"===l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("===l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const n=this.matcherRe.exec(e);if(!n)return null;const t=n.findIndex((e,n)=>n>0&&void 0!==e),r=this.matchIndexes[t];return n.splice(0,t),Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;const t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e,n){const t=e.input[e.index-1],r=e.input[e.index+e[0].length];"."!==t&&"."!==r||n.ignoreMatch()}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return function t(s,o){const l=s;if(s.compiled)return l;s.compiled=!0,s.__beforeBegin=null,s.keywords=s.keywords||s.beginKeywords;let c=null;if("object"==typeof s.keywords&&(c=s.keywords.$pattern,delete s.keywords.$pattern),s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,w(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemes&&c)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l.keywordPatternRe=n(s.lexemes||c||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__beforeBegin=i),s.begin||(s.begin=/\B|\b/),l.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(l.endRe=n(s.end)),l.terminator_end=d(s.end)||"",s.endsWithParent&&o.terminator_end&&(l.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(l.illegalRe=n(s.illegal)),void 0===s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return r(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?r(e,{starts:e.starts?r(e.starts):null}):Object.isFrozen(e)?r(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){t(e,l)})),s.starts&&t(s.starts,o),l.matcher=function(e){const n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(l),l}(e)}(E),N="",y=s||_,k={},O=new f.__emitter(f);!function(){for(var e=[],n=y;n!==E;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>O.openNode(e))}();var A="",I=0,S=0,B=0,L=!1;try{for(y.matcher.considerAll();;){B++,L?L=!1:(y.matcher.lastIndex=S,y.matcher.considerAll());const e=y.matcher.exec(o);if(!e)break;const n=x(o.substring(S,e.index),e);S=e.index+n}return x(o.substr(S)),O.closeAllNodes(),O.finalize(),N=O.toHTML(),{relevance:I,value:N,language:e,illegal:!1,emitter:O,top:y}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:o.slice(S-100,S+100),mode:n.mode},sofar:N,relevance:0,value:R(o),emitter:O};if(l)return{illegal:!1,relevance:0,value:R(o),emitter:O,language:e,top:y,errorRaised:n};throw n}}function v(e,n){n=n||f.languages||Object.keys(i);var t=function(e){const n={relevance:0,emitter:new f.__emitter(f),value:R(e),illegal:!1,top:h};return n.emitter.addText(e),n}(e),r=t;return n.filter(T).filter(I).forEach((function(n){var a=m(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function x(e){return f.tabReplace||f.useBR?e.replace(c,e=>"\n"===e?f.useBR?"
":e:f.tabReplace?e.replace(/\t/g,f.tabReplace):e):e}function E(e){let n=null;const t=function(e){var n=e.className+" ";n+=e.parentNode?e.parentNode.className:"";const t=f.languageDetectRe.exec(n);if(t){var r=T(t[1]);return r||(console.warn(g.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?t[1]:"no-highlight"}return n.split(/\s+/).find(e=>p(e)||T(e))}(e);if(p(t))return;S("before:highlightBlock",{block:e,language:t}),f.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"):n=e;const r=n.textContent,a=t?b(t,r,!0):v(r),i=k(n);if(i.length){const e=document.createElement("div");e.innerHTML=a.value,a.value=O(i,k(e),r)}a.value=x(a.value),S("after:highlightBlock",{block:e,result:a}),e.innerHTML=a.value,e.className=function(e,n,t){var r=n?s[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,t,a.language),e.result={language:a.language,re:a.relevance,relavance:a.relevance},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.relevance,relavance:a.second_best.relevance})}const N=()=>{if(!N.called){N.called=!0;var e=document.querySelectorAll("pre code");a.forEach.call(e,E)}};function T(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]}function A(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach(e=>{s[e]=n})}function I(e){var n=T(e);return n&&!n.disableAutodetect}function S(e,n){var t=e;o.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(t,{highlight:b,highlightAuto:v,fixMarkup:x,highlightBlock:E,configure:function(e){f=y(f,e)},initHighlighting:N,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",N,!1)},registerLanguage:function(e,n){var r=null;try{r=n(t)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!l)throw n;console.error(n),r=h}r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&A(r.aliases,{languageName:e})},listLanguages:function(){return Object.keys(i)},getLanguage:T,registerAliases:A,requireLanguage:function(e){var n=T(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:I,inherit:y,addPlugin:function(e){o.push(e)}}),t.debugMode=function(){l=!1},t.safeMode=function(){l=!0},t.versionString="10.1.1";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(t,_),t}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs); +hljs.registerLanguage("apache",function(){"use strict";return function(e){var n={className:"number",begin:"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?"};return{name:"Apache config",aliases:["apacheconf"],case_insensitive:!0,contains:[e.HASH_COMMENT_MODE,{className:"section",begin:"",contains:[n,{className:"number",begin:":\\d{1,5}"},e.inherit(e.QUOTE_STRING_MODE,{relevance:0})]},{className:"attribute",begin:/\w+/,relevance:0,keywords:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{end:/$/,relevance:0,keywords:{literal:"on off all deny allow"},contains:[{className:"meta",begin:"\\s\\[",end:"\\]$"},{className:"variable",begin:"[\\$%]\\{",end:"\\}",contains:["self",{className:"number",begin:"[\\$%]\\d+"}]},n,{className:"number",begin:"\\d+"},e.QUOTE_STRING_MODE]}}],illegal:/\S/}}}()); +hljs.registerLanguage("bash",function(){"use strict";return function(e){const s={};Object.assign(s,{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:[{begin:/:-/,contains:[s]}]}]});const t={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},n={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,t]};t.contains.push(n);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]},i=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b-?[a-z\._]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[i,e.SHEBANG(),c,a,e.HASH_COMMENT_MODE,n,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}}()); +hljs.registerLanguage("c-like",function(){"use strict";return function(e){function t(e){return"(?:"+e+")?"}var n="(decltype\\(auto\\)|"+t("[a-zA-Z_]\\w*::")+"[a-zA-Z_]\\w*"+t("<.*?>")+")",r={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},a={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",end:"'",illegal:"."},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},i={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(a,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},o={className:"title",begin:t("[a-zA-Z_]\\w*::")+e.IDENT_RE,relevance:0},c=t("[a-zA-Z_]\\w*::")+e.IDENT_RE+"\\s*\\(",l={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},d=[r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,i,a],_={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:l,contains:d.concat([{begin:/\(/,end:/\)/,keywords:l,contains:d.concat(["self"]),relevance:0}]),relevance:0},u={className:"function",begin:"("+n+"[\\*&\\s]+)+"+c,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:l,illegal:/[^\w\s\*&:<>]/,contains:[{begin:"decltype\\(auto\\)",keywords:l,relevance:0},{begin:c,returnBegin:!0,contains:[o],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r,{begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r]}]},r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],keywords:l,disableAutodetect:!0,illegal:"",keywords:l,contains:["self",r]},{begin:e.IDENT_RE+"::",keywords:l},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin://,contains:["self"]},e.TITLE_MODE]}]),exports:{preprocessor:s,strings:a,keywords:l}}}}()); +hljs.registerLanguage("c",function(){"use strict";return function(e){var n=e.getLanguage("c-like").rawDefinition();return n.name="C",n.aliases=["c","h"],n}}()); +hljs.registerLanguage("coffeescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={keyword:e.concat(["then","unless","until","loop","by","when","and","or","is","isnt","not"]).filter((e=>n=>!e.includes(n))(["var","const","let","function","static"])).join(" "),literal:n.concat(["yes","no","on","off"]).join(" "),built_in:a.concat(["npm","print"]).join(" ")},i="[A-Za-z$_][0-9A-Za-z$_]*",s={className:"subst",begin:/#\{/,end:/}/,keywords:t},o=[r.BINARY_NUMBER_MODE,r.inherit(r.C_NUMBER_MODE,{starts:{end:"(\\s*/)?",relevance:0}}),{className:"string",variants:[{begin:/'''/,end:/'''/,contains:[r.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[r.BACKSLASH_ESCAPE]},{begin:/"""/,end:/"""/,contains:[r.BACKSLASH_ESCAPE,s]},{begin:/"/,end:/"/,contains:[r.BACKSLASH_ESCAPE,s]}]},{className:"regexp",variants:[{begin:"///",end:"///",contains:[s,r.HASH_COMMENT_MODE]},{begin:"//[gim]{0,3}(?=\\W)",relevance:0},{begin:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{begin:"@"+i},{subLanguage:"javascript",excludeBegin:!0,excludeEnd:!0,variants:[{begin:"```",end:"```"},{begin:"`",end:"`"}]}];s.contains=o;var c=r.inherit(r.TITLE_MODE,{begin:i}),l={className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/,end:/\)/,keywords:t,contains:["self"].concat(o)}]};return{name:"CoffeeScript",aliases:["coffee","cson","iced"],keywords:t,illegal:/\/\*/,contains:o.concat([r.COMMENT("###","###"),r.HASH_COMMENT_MODE,{className:"function",begin:"^\\s*"+i+"\\s*=\\s*(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[c,l]},{begin:/[:\(,=]\s*/,relevance:0,contains:[{className:"function",begin:"(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[l]}]},{className:"class",beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[c]},c]},{begin:i+":",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}}()); +hljs.registerLanguage("cpp",function(){"use strict";return function(e){var t=e.getLanguage("c-like").rawDefinition();return t.disableAutodetect=!1,t.name="C++",t.aliases=["cc","c++","h++","hpp","hh","hxx","cxx"],t}}()); +hljs.registerLanguage("csharp",function(){"use strict";return function(e){var n={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},t=e.inherit(s,{illegal:/\n/}),l={className:"subst",begin:"{",end:"}",keywords:n},r=e.inherit(l,{illegal:/\n/}),c={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},e.BACKSLASH_ESCAPE,r]},o={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},l]},g=e.inherit(o,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},r]});l.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],r.contains=[g,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];var d={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},E={begin:"<",end:">",contains:[{beginKeywords:"in out"},i]},_=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",b={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:""}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,a,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},i,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+_+"\\s+)+"+e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{begin:e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,contains:[e.TITLE_MODE,E],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[d,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},b]}}}()); +hljs.registerLanguage("css",function(){"use strict";return function(e){var n={begin:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute",begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/},{begin:/\(/,end:/\)/,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]}}]};return{name:"CSS",case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",illegal:/:/,returnBegin:!0,contains:[{className:"keyword",begin:/@\-?\w[\w]*(\-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:"and or not only",contains:[{begin:/[a-z-]+:/,className:"attribute"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{begin:"{",end:"}",illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,n]}]}}}()); +hljs.registerLanguage("diff",function(){"use strict";return function(e){return{name:"Diff",aliases:["patch"],contains:[{className:"meta",relevance:10,variants:[{begin:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{begin:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{begin:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{className:"comment",variants:[{begin:/Index: /,end:/$/},{begin:/={3,}/,end:/$/},{begin:/^\-{3}/,end:/$/},{begin:/^\*{3} /,end:/$/},{begin:/^\+{3}/,end:/$/},{begin:/^\*{15}$/}]},{className:"addition",begin:"^\\+",end:"$"},{className:"deletion",begin:"^\\-",end:"$"},{className:"addition",begin:"^\\!",end:"$"}]}}}()); +hljs.registerLanguage("go",function(){"use strict";return function(e){var n={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{name:"Go",aliases:["golang"],keywords:n,illegal:"e(n)).join("")}return function(a){var s={className:"number",relevance:0,variants:[{begin:/([\+\-]+)?[\d]+_[\d_]+/},{begin:a.NUMBER_RE}]},i=a.COMMENT();i.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];var t={className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)}/}]},r={className:"literal",begin:/\bon|off|true|false|yes|no\b/},l={className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:"'''",end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'},{begin:"'",end:"'"}]},c={begin:/\[/,end:/\]/,contains:[i,r,t,l,s,"self"],relevance:0},g="("+[/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/].map(n=>e(n)).join("|")+")";return{name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/,contains:[i,{className:"section",begin:/\[+/,end:/\]+/},{begin:n(g,"(\\s*\\.\\s*",g,")*",n("(?=",/\s*=\s*[^#\s]/,")")),className:"attr",starts:{end:/$/,contains:[i,c,r,t,l,s]}}]}}}()); +hljs.registerLanguage("java",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(e){return a("(",e,")?")}function a(...n){return n.map(n=>e(n)).join("")}function s(...n){return"("+n.map(n=>e(n)).join("|")+")"}return function(e){var t="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",i={className:"meta",begin:"@[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",contains:[{begin:/\(/,end:/\)/,contains:["self"]}]},r=e=>a("[",e,"]+([",e,"_]*[",e,"]+)?"),c={className:"number",variants:[{begin:`\\b(0[bB]${r("01")})[lL]?`},{begin:`\\b(0${r("0-7")})[dDfFlL]?`},{begin:a(/\b0[xX]/,s(a(r("a-fA-F0-9"),/\./,r("a-fA-F0-9")),a(r("a-fA-F0-9"),/\.?/),a(/\./,r("a-fA-F0-9"))),/([pP][+-]?(\d+))?/,/[fFdDlL]?/)},{begin:a(/\b/,s(a(/\d*\./,r("\\d")),r("\\d")),/[eE][+-]?[\d]+[dDfF]?/)},{begin:a(/\b/,r(/\d/),n(/\.?/),n(r(/\d/)),/[dDfFlL]?/)}],relevance:0};return{name:"Java",aliases:["jsp"],keywords:t,illegal:/<\/|#/,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:t,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:t,relevance:0,contains:[i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},c,i]}}}()); +hljs.registerLanguage("javascript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function s(e){return r("(?=",e,")")}function r(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(t){var i="[A-Za-z$_][0-9A-Za-z$_]*",c={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/},o={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.join(" "),literal:n.join(" "),built_in:a.join(" ")},l={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:t.C_NUMBER_RE+"n?"}],relevance:0},E={className:"subst",begin:"\\$\\{",end:"\\}",keywords:o,contains:[]},d={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"xml"}},g={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"css"}},u={className:"string",begin:"`",end:"`",contains:[t.BACKSLASH_ESCAPE,E]};E.contains=[t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,l,t.REGEXP_MODE];var b=E.contains.concat([{begin:/\(/,end:/\)/,contains:["self"].concat(E.contains,[t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE])},t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE]),_={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:b};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:o,contains:[t.SHEBANG({binary:"node",relevance:5}),{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,t.C_LINE_COMMENT_MODE,t.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:i+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),t.C_BLOCK_COMMENT_MODE,l,{begin:r(/[{,\n]\s*/,s(r(/(((\/\/.*)|(\/\*(.|\n)*\*\/))\s*)*/,i+"\\s*:"))),relevance:0,contains:[{className:"attr",begin:i+s("\\s*:"),relevance:0}]},{begin:"("+t.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[t.C_LINE_COMMENT_MODE,t.C_BLOCK_COMMENT_MODE,t.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+t.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:t.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:o,contains:b}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:""},{begin:c.begin,end:c.end}],subLanguage:"xml",contains:[{begin:c.begin,end:c.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[t.inherit(t.TITLE_MODE,{begin:i}),_],illegal:/\[|%/},{begin:/\$[(.]/},t.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},t.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0},{begin:"(get|set)\\s+(?="+i+"\\()",end:/{/,keywords:"get set",contains:[t.inherit(t.TITLE_MODE,{begin:i}),{begin:/\(\)/},_]}],illegal:/#(?!!)/}}}()); +hljs.registerLanguage("json",function(){"use strict";return function(n){var e={literal:"true false null"},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],t=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],a={end:",",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:e},l={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(a,{begin:/:/})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(a)],illegal:"\\S"};return t.push(l,s),i.forEach((function(n){t.push(n)})),{name:"JSON",contains:t,keywords:e,illegal:"\\S"}}}()); +hljs.registerLanguage("kotlin",function(){"use strict";return function(e){var n={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual trait volatile transient native default",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},a={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@"},i={className:"subst",begin:"\\${",end:"}",contains:[e.C_NUMBER_MODE]},s={className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},t={className:"string",variants:[{begin:'"""',end:'"""(?=[^"])',contains:[s,i]},{begin:"'",end:"'",illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,contains:[e.BACKSLASH_ESCAPE,s,i]}]};i.contains.push(t);var r={className:"meta",begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?"},l={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,end:/\)/,contains:[e.inherit(t,{className:"meta-string"})]}]},c=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),o={variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,contains:[]}]},d=o;return d.variants[1].contains=[o],o.variants[1].contains=[d],{name:"Kotlin",aliases:["kt"],keywords:n,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,c,{className:"keyword",begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol",begin:/@\w+/}]}},a,r,l,{className:"function",beginKeywords:"fun",end:"[(]|$",returnBegin:!0,excludeEnd:!0,keywords:n,illegal:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin://,keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,endsWithParent:!0,contains:[o,e.C_LINE_COMMENT_MODE,c],relevance:0},e.C_LINE_COMMENT_MODE,c,r,l,t,e.C_NUMBER_MODE]},c]},{className:"class",beginKeywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,illegal:"extends implements",contains:[{beginKeywords:"public protected internal private constructor"},e.UNDERSCORE_TITLE_MODE,{className:"type",begin://,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,]|$/,excludeBegin:!0,returnEnd:!0},r,l]},t,{className:"meta",begin:"^#!/usr/bin/env",end:"$",illegal:"\n"},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0}]}}}()); +hljs.registerLanguage("less",function(){"use strict";return function(e){var n="([\\w-]+|@{[\\w-]+})",a=[],s=[],t=function(e){return{className:"string",begin:"~?"+e+".*?"+e}},r=function(e,n,a){return{className:e,begin:n,relevance:a}},i={begin:"\\(",end:"\\)",contains:s,relevance:0};s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t("'"),t('"'),e.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},r("number","#[0-9A-Fa-f]+\\b"),i,r("variable","@@?[\\w-]+",10),r("variable","@{[\\w-]+}"),r("built_in","~?`[^`]*?`"),{className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0},{className:"meta",begin:"!important"});var c=s.concat({begin:"{",end:"}",contains:a}),l={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(s)},o={begin:n+"\\s*:",returnBegin:!0,end:"[;}]",relevance:0,contains:[{className:"attribute",begin:n,end:":",excludeEnd:!0,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}]},g={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",returnEnd:!0,contains:s,relevance:0}},d={className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:c}},b={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:n,end:"{"}],returnBegin:!0,returnEnd:!0,illegal:"[<='$\"]",relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,l,r("keyword","all\\b"),r("variable","@{[\\w-]+}"),r("selector-tag",n+"%?",0),r("selector-id","#"+n),r("selector-class","\\."+n,0),r("selector-tag","&",0),{className:"selector-attr",begin:"\\[",end:"\\]"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"\\(",end:"\\)",contains:c},{begin:"!important"}]};return a.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,g,d,o,b),{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:a}}}()); +hljs.registerLanguage("lua",function(){"use strict";return function(e){var t={begin:"\\[=*\\[",end:"\\]=*\\]",contains:["self"]},a=[e.COMMENT("--(?!\\[=*\\[)","$"),e.COMMENT("--\\[=*\\[","\\]=*\\]",{contains:[t],relevance:10})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE,literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},contains:a.concat([{className:"function",beginKeywords:"function",end:"\\)",contains:[e.inherit(e.TITLE_MODE,{begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params",begin:"\\(",endsWithParent:!0,contains:a}].concat(a)},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",begin:"\\[=*\\[",end:"\\]=*\\]",contains:[t],relevance:5}])}}}()); +hljs.registerLanguage("makefile",function(){"use strict";return function(e){var i={className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)",contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"",relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"",contains:[a,s,i,t]}]}]},e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:")",end:">",keywords:{name:"style"},contains:[c],starts:{end:"",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:")",end:">",keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},c]}]}}}()); +hljs.registerLanguage("markdown",function(){"use strict";return function(n){const e={begin:"<",end:">",subLanguage:"xml",relevance:0},a={begin:"\\[.+?\\][\\(\\[].*?[\\)\\]]",returnBegin:!0,contains:[{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0,relevance:0},{className:"link",begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"symbol",begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}],relevance:10},i={className:"strong",contains:[],variants:[{begin:/_{2}/,end:/_{2}/},{begin:/\*{2}/,end:/\*{2}/}]},s={className:"emphasis",contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{begin:/_(?!_)/,end:/_/,relevance:0}]};i.contains.push(s),s.contains.push(i);var c=[e,a];return i.contains=i.contains.concat(c),s.contains=s.contains.concat(c),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:c=c.concat(i,s)},{begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",contains:c}]}]},e,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:c,end:"$"},{className:"code",variants:[{begin:"(`{3,})(.|\\n)*?\\1`*[ ]*"},{begin:"(~{3,})(.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}}()); +hljs.registerLanguage("nginx",function(){"use strict";return function(e){var n={className:"variable",variants:[{begin:/\$\d+/},{begin:/\$\{/,end:/}/},{begin:"[\\$\\@]"+e.UNDERSCORE_IDENT_RE}]},a={endsWithParent:!0,keywords:{$pattern:"[a-z/_]+",literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},relevance:0,illegal:"=>",contains:[e.HASH_COMMENT_MODE,{className:"string",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/}]},{begin:"([a-z]+):/",end:"\\s",endsWithParent:!0,excludeEnd:!0,contains:[n]},{className:"regexp",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:"\\s\\^",end:"\\s|{|;",returnEnd:!0},{begin:"~\\*?\\s+",end:"\\s|{|;",returnEnd:!0},{begin:"\\*(\\.[a-z\\-]+)+"},{begin:"([a-z\\-]+\\.)+\\*"}]},{className:"number",begin:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{className:"number",begin:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},n]};return{name:"Nginx config",aliases:["nginxconf"],contains:[e.HASH_COMMENT_MODE,{begin:e.UNDERSCORE_IDENT_RE+"\\s+{",returnBegin:!0,end:"{",contains:[{className:"section",begin:e.UNDERSCORE_IDENT_RE}],relevance:0},{begin:e.UNDERSCORE_IDENT_RE+"\\s",end:";|{",returnBegin:!0,contains:[{className:"attribute",begin:e.UNDERSCORE_IDENT_RE,starts:a}],relevance:0}],illegal:"[^\\s\\}]"}}}()); +hljs.registerLanguage("objectivec",function(){"use strict";return function(e){var n=/[a-zA-Z@][a-zA-Z0-9_]*/,_={$pattern:n,keyword:"@interface @class @protocol @implementation"};return{name:"Objective-C",aliases:["mm","objc","obj-c"],keywords:{$pattern:n,keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},illegal:"/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"class",begin:"("+_.keyword.split(" ").join("|")+")\\b",end:"({|$)",excludeEnd:!0,keywords:_,contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"\\."+e.UNDERSCORE_IDENT_RE,relevance:0}]}}}()); +hljs.registerLanguage("perl",function(){"use strict";return function(e){var n={$pattern:/[\w.]+/,keyword:"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qq fileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmget sub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedir ioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when"},t={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:n},s={begin:"->{",end:"}"},r={variants:[{begin:/\$\d/},{begin:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{begin:/[\$%@][^\s\w{]/,relevance:0}]},i=[e.BACKSLASH_ESCAPE,t,r],a=[r,e.HASH_COMMENT_MODE,e.COMMENT("^\\=\\w","\\=cut",{endsWithParent:!0}),s,{className:"string",contains:i,variants:[{begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*\\<",end:"\\>",relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'",contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE]},{begin:"{\\w+}",contains:[],relevance:0},{begin:"-?\\w+\\s*\\=\\>",contains:[],relevance:0}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*",keywords:"split return print reverse grep",relevance:0,contains:[e.HASH_COMMENT_MODE,{className:"regexp",begin:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{className:"regexp",begin:"(m|qr)?/",end:"/[a-z]*",contains:[e.BACKSLASH_ESCAPE],relevance:0}]},{className:"function",beginKeywords:"sub",end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$",subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}]}];return t.contains=a,s.contains=a,{name:"Perl",aliases:["pl","pm"],keywords:n,contains:a}}}()); +hljs.registerLanguage("php",function(){"use strict";return function(e){var r={begin:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},t={className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{begin:/\?>/}]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:'b"',end:'"'},{begin:"b'",end:"'"},e.inherit(e.APOS_STRING_MODE,{illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null})]},n={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]},i={keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list new object or private protected public real return string switch throw trait try unset use var void while xor yield",literal:"false null true",built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Throwable Traversable WeakReference Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass"};return{aliases:["php","php3","php4","php5","php6","php7"],case_insensitive:!0,keywords:i,contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t]}),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler"}),{className:"string",begin:/<<<['"]?\w+['"]?$/,end:/^\w+;?$/,contains:[e.BACKSLASH_ESCAPE,{className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]}]},t,{className:"keyword",begin:/\$this\b/},r,{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:i,contains:["self",r,e.C_BLOCK_COMMENT_MODE,a,n]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"=>"},a,n]}}}()); +hljs.registerLanguage("php-template",function(){"use strict";return function(n){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},n.inherit(n.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}}()); +hljs.registerLanguage("plaintext",function(){"use strict";return function(t){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}}()); +hljs.registerLanguage("properties",function(){"use strict";return function(e){var n="[ \\t\\f]*",t="("+n+"[:=]"+n+"|[ \\t\\f]+)",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",s={end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+"+t,returnBegin:!0,contains:[{className:"attr",begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:s},{begin:a+t,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:a,endsParent:!0,relevance:0}],starts:s},{className:"attr",relevance:0,begin:a+n+"$"}]}}}()); +hljs.registerLanguage("python",function(){"use strict";return function(e){var n={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10",built_in:"Ellipsis NotImplemented",literal:"False None True"},a={className:"meta",begin:/^(>>>|\.\.\.) /},i={className:"subst",begin:/\{/,end:/\}/,keywords:n,illegal:/#/},s={begin:/\{\{/,relevance:0},r={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,i]},{begin:/(fr|rf|f)"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,i]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l={className:"number",relevance:0,variants:[{begin:e.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:e.C_NUMBER_RE+"[lLjJ]?"}]},t={className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:["self",a,l,r,e.HASH_COMMENT_MODE]}]};return i.contains=[r,l,a],{name:"Python",aliases:["py","gyp","ipython"],keywords:n,illegal:/(<\/|->|\?)|=>/,contains:[a,l,{beginKeywords:"if",relevance:0},r,e.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,contains:[e.UNDERSCORE_TITLE_MODE,t,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}}}()); +hljs.registerLanguage("python-repl",function(){"use strict";return function(n){return{aliases:["pycon"],contains:[{className:"meta",starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}}}()); +hljs.registerLanguage("ruby",function(){"use strict";return function(e){var n="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",a={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},s={className:"doctag",begin:"@[A-Za-z]+"},i={begin:"#<",end:">"},r=[e.COMMENT("#","$",{contains:[s]}),e.COMMENT("^\\=begin","^\\=end",{contains:[s],relevance:10}),e.COMMENT("^__END__","\\n$")],c={className:"subst",begin:"#\\{",end:"}",keywords:a},t={className:"string",contains:[e.BACKSLASH_ESCAPE,c],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/",end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{begin:"%[qQwWx]?\\|",end:"\\|"},{begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{begin:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,returnBegin:!0,contains:[{begin:/<<[-~]?'?/},e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[e.BACKSLASH_ESCAPE,c]})]}]},b={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:a},d=[t,i,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<\\s*",contains:[{begin:"("+e.IDENT_RE+"::)?"+e.IDENT_RE}]}].concat(r)},{className:"function",beginKeywords:"def",end:"$|;",contains:[e.inherit(e.TITLE_MODE,{begin:n}),b].concat(r)},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[t,{begin:n}],relevance:0},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params",begin:/\|/,end:/\|/,keywords:a},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[i,{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(r),relevance:0}].concat(r);c.contains=d,b.contains=d;var g=[{begin:/^\s*=>/,starts:{end:"$",contains:d}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{end:"$",contains:d}}];return{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:a,illegal:/\/\*/,contains:r.concat(g).concat(d)}}}()); +hljs.registerLanguage("rust",function(){"use strict";return function(e){var n="([ui](8|16|32|64|128|size)|f(32|64))?",t="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:t},illegal:""}]}}}()); +hljs.registerLanguage("scss",function(){"use strict";return function(e){var t={className:"variable",begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={className:"number",begin:"#[0-9A-Fa-f]+"};return e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:"\\#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},{className:"selector-attr",begin:"\\[",end:"\\]",illegal:"$"},{className:"selector-tag",begin:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{className:"selector-pseudo",begin:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{className:"selector-pseudo",begin:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},t,{className:"attribute",begin:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",illegal:"[^\\s]"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{begin:":",end:";",contains:[t,i,e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"meta",begin:"!important"}]},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",returnBegin:!0,keywords:"and or not only",contains:[{begin:"@[a-z-]+",className:"keyword"},t,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,i,e.CSS_NUMBER_MODE]}]}}}()); +hljs.registerLanguage("shell",function(){"use strict";return function(s){return{name:"Shell Session",aliases:["console"],contains:[{className:"meta",begin:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{end:"$",subLanguage:"bash"}}]}}}()); +hljs.registerLanguage("sql",function(){"use strict";return function(e){var t=e.COMMENT("--","$");return{name:"SQL",case_insensitive:!0,illegal:/[<>{}*]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",end:/;/,endsWithParent:!0,keywords:{$pattern:/[\w\.]+/,keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:"`",end:"`"},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]},e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]}}}()); +hljs.registerLanguage("swift",function(){"use strict";return function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},n=e.COMMENT("/\\*","\\*/",{contains:["self"]}),t={className:"subst",begin:/\\\(/,end:"\\)",keywords:i,contains:[]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:/"""/,end:/"""/},{begin:/"/,end:/"/}]},r={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return t.contains=[r],{name:"Swift",keywords:i,contains:[a,e.C_LINE_COMMENT_MODE,n,{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},r,{className:"function",beginKeywords:"func",end:"{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin://},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:i,contains:["self",r,a,e.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:i,end:"\\{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta",begin:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)\\b"},{beginKeywords:"import",end:/$/,contains:[e.C_LINE_COMMENT_MODE,n]}]}}}()); +hljs.registerLanguage("typescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.concat(["type","namespace","typedef","interface","public","private","protected","implements","declare","abstract","readonly"]).join(" "),literal:n.join(" "),built_in:a.concat(["any","void","number","boolean","string","object","never","enum"]).join(" ")},s={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},i={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:r.C_NUMBER_RE+"n?"}],relevance:0},o={className:"subst",begin:"\\$\\{",end:"\\}",keywords:t,contains:[]},c={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"xml"}},l={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"css"}},E={className:"string",begin:"`",end:"`",contains:[r.BACKSLASH_ESCAPE,o]};o.contains=[r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,i,r.REGEXP_MODE];var d={begin:"\\(",end:/\)/,keywords:t,contains:["self",r.QUOTE_STRING_MODE,r.APOS_STRING_MODE,r.NUMBER_MODE]},u={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,s,d]};return{name:"TypeScript",aliases:["ts"],keywords:t,contains:[r.SHEBANG(),{className:"meta",begin:/^\s*['"]use strict['"]/},r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,i,{begin:"("+r.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,r.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+r.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:r.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:d.contains}]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[\{;]/,excludeEnd:!0,keywords:t,contains:["self",r.inherit(r.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),u],illegal:/%/,relevance:0},{beginKeywords:"constructor",end:/[\{;]/,excludeEnd:!0,contains:["self",u]},{begin:/module\./,keywords:{built_in:"module"},relevance:0},{beginKeywords:"module",end:/\{/,excludeEnd:!0},{beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:"interface extends"},{begin:/\$[(.]/},{begin:"\\."+r.IDENT_RE,relevance:0},s,d]}}}()); +hljs.registerLanguage("yaml",function(){"use strict";return function(e){var n="true false yes no null",a="[\\w#;/?:@&=+$,.~*\\'()[\\]]+",s={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:"{{",end:"}}"},{begin:"%{",end:"}"}]}]},i=e.inherit(s,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={end:",",endsWithParent:!0,excludeEnd:!0,contains:[],keywords:n,relevance:0},t={begin:"{",end:"}",contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]",contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---s*$",relevance:10},{className:"string",begin:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type",begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"\\-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},{className:"number",begin:e.C_NUMBER_RE+"\\b"},t,g,s],c=[...b];return c.pop(),c.push(i),l.contains=c,{name:"YAML",case_insensitive:!0,aliases:["yml","YAML"],contains:b}}}()); +hljs.registerLanguage("armasm",function(){"use strict";return function(s){const e={variants:[s.COMMENT("^[ \\t]*(?=#)","$",{relevance:0,excludeBegin:!0}),s.COMMENT("[;@]","$",{relevance:0}),s.C_LINE_COMMENT_MODE,s.C_BLOCK_COMMENT_MODE]};return{name:"ARM Assembly",case_insensitive:!0,aliases:["arm"],keywords:{$pattern:"\\.?"+s.IDENT_RE,meta:".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .arm .thumb .code16 .code32 .force_thumb .thumb_func .ltorg ALIAS ALIGN ARM AREA ASSERT ATTR CN CODE CODE16 CODE32 COMMON CP DATA DCB DCD DCDU DCDO DCFD DCFDU DCI DCQ DCQU DCW DCWU DN ELIF ELSE END ENDFUNC ENDIF ENDP ENTRY EQU EXPORT EXPORTAS EXTERN FIELD FILL FUNCTION GBLA GBLL GBLS GET GLOBAL IF IMPORT INCBIN INCLUDE INFO KEEP LCLA LCLL LCLS LTORG MACRO MAP MEND MEXIT NOFP OPT PRESERVE8 PROC QN READONLY RELOC REQUIRE REQUIRE8 RLIST FN ROUT SETA SETL SETS SN SPACE SUBT THUMB THUMBX TTL WHILE WEND ",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 pc lr sp ip sl sb fp a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 v7 v8 f0 f1 f2 f3 f4 f5 f6 f7 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 q0 q1 q2 q3 q4 q5 q6 q7 q8 q9 q10 q11 q12 q13 q14 q15 cpsr_c cpsr_x cpsr_s cpsr_f cpsr_cx cpsr_cxs cpsr_xs cpsr_xsf cpsr_sf cpsr_cxsf spsr_c spsr_x spsr_s spsr_f spsr_cx spsr_cxs spsr_xs spsr_xsf spsr_sf spsr_cxsf s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s30 s31 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 d16 d17 d18 d19 d20 d21 d22 d23 d24 d25 d26 d27 d28 d29 d30 d31 {PC} {VAR} {TRUE} {FALSE} {OPT} {CONFIG} {ENDIAN} {CODESIZE} {CPU} {FPU} {ARCHITECTURE} {PCSTOREOFFSET} {ARMASM_VERSION} {INTER} {ROPI} {RWPI} {SWST} {NOSWST} . @"},contains:[{className:"keyword",begin:"\\b(adc|(qd?|sh?|u[qh]?)?add(8|16)?|usada?8|(q|sh?|u[qh]?)?(as|sa)x|and|adrl?|sbc|rs[bc]|asr|b[lx]?|blx|bxj|cbn?z|tb[bh]|bic|bfc|bfi|[su]bfx|bkpt|cdp2?|clz|clrex|cmp|cmn|cpsi[ed]|cps|setend|dbg|dmb|dsb|eor|isb|it[te]{0,3}|lsl|lsr|ror|rrx|ldm(([id][ab])|f[ds])?|ldr((s|ex)?[bhd])?|movt?|mvn|mra|mar|mul|[us]mull|smul[bwt][bt]|smu[as]d|smmul|smmla|mla|umlaal|smlal?([wbt][bt]|d)|mls|smlsl?[ds]|smc|svc|sev|mia([bt]{2}|ph)?|mrr?c2?|mcrr2?|mrs|msr|orr|orn|pkh(tb|bt)|rbit|rev(16|sh)?|sel|[su]sat(16)?|nop|pop|push|rfe([id][ab])?|stm([id][ab])?|str(ex)?[bhd]?|(qd?)?sub|(sh?|q|u[qh]?)?sub(8|16)|[su]xt(a?h|a?b(16)?)|srs([id][ab])?|swpb?|swi|smi|tst|teq|wfe|wfi|yield)(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al|hs|lo)?[sptrx]?(?=\\s)"},e,s.QUOTE_STRING_MODE,{className:"string",begin:"'",end:"[^\\\\]'",relevance:0},{className:"title",begin:"\\|",end:"\\|",illegal:"\\n",relevance:0},{className:"number",variants:[{begin:"[#$=]?0x[0-9a-f]+"},{begin:"[#$=]?0b[01]+"},{begin:"[#$=]\\d+"},{begin:"\\b\\d+"}],relevance:0},{className:"symbol",variants:[{begin:"^[ \\t]*[a-z_\\.\\$][a-z0-9_\\.\\$]+:"},{begin:"^[a-z_\\.\\$][a-z0-9_\\.\\$]+"},{begin:"[=#]\\w+"}],relevance:0}]}}}()); +hljs.registerLanguage("d",function(){"use strict";return function(e){var a={$pattern:e.UNDERSCORE_IDENT_RE,keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},d="((0|[1-9][\\d_]*)|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))",n="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",t={className:"number",begin:"\\b"+d+"(L|u|U|Lu|LU|uL|UL)?",relevance:0},_={className:"number",begin:"\\b(((0[xX](([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)\\.([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)|\\.?([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))[pP][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))|((0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(\\.\\d*|([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)))|\\d+\\.(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)|\\.(0|[1-9][\\d_]*)([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))?))([fF]|L|i|[fF]i|Li)?|"+d+"(i|[fF]i|Li))",relevance:0},r={className:"string",begin:"'("+n+"|.)",end:"'",illegal:"."},i={className:"string",begin:'"',contains:[{begin:n,relevance:0}],end:'"[cwd]?'},s=e.COMMENT("\\/\\+","\\+\\/",{contains:["self"],relevance:10});return{name:"D",keywords:a,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,{className:"string",begin:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',relevance:10},i,{className:"string",begin:'[rq]"',end:'"[cwd]?',relevance:5},{className:"string",begin:"`",end:"`[cwd]?"},{className:"string",begin:'q"\\{',end:'\\}"'},_,t,r,{className:"meta",begin:"^#!",end:"$",relevance:5},{className:"meta",begin:"#(line)",end:"$",relevance:5},{className:"keyword",begin:"@[a-zA-Z_][a-zA-Z_\\d]*"}]}}}()); +hljs.registerLanguage("handlebars",function(){"use strict";function e(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(n){const a={"builtin-name":"action bindattr collection component concat debugger each each-in get hash if in input link-to loc log lookup mut outlet partial query-params render template textarea unbound unless view with yield"},t=/\[.*?\]/,s=/[^\s!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~]+/,i=e("(",/'.*?'/,"|",/".*?"/,"|",t,"|",s,"|",/\.|\//,")+"),r=e("(",t,"|",s,")(?==)"),l={begin:i,lexemes:/[\w.\/]+/},c=n.inherit(l,{keywords:{literal:"true false undefined null"}}),o={begin:/\(/,end:/\)/},m={className:"attr",begin:r,relevance:0,starts:{begin:/=/,end:/=/,starts:{contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,c,o]}}},d={contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,{begin:/as\s+\|/,keywords:{keyword:"as"},end:/\|/,contains:[{begin:/\w+/}]},m,c,o],returnEnd:!0},g=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/\)/})});o.contains=[g];const u=n.inherit(l,{keywords:a,className:"name",starts:n.inherit(d,{end:/}}/})}),b=n.inherit(l,{keywords:a,className:"name"}),h=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/}}/})});return{name:"Handlebars",aliases:["hbs","html.hbs","html.handlebars","htmlbars"],case_insensitive:!0,subLanguage:"xml",contains:[{begin:/\\\{\{/,skip:!0},{begin:/\\\\(?=\{\{)/,skip:!0},n.COMMENT(/\{\{!--/,/--\}\}/),n.COMMENT(/\{\{!/,/\}\}/),{className:"template-tag",begin:/\{\{\{\{(?!\/)/,end:/\}\}\}\}/,contains:[u],starts:{end:/\{\{\{\{\//,returnEnd:!0,subLanguage:"xml"}},{className:"template-tag",begin:/\{\{\{\{\//,end:/\}\}\}\}/,contains:[b]},{className:"template-tag",begin:/\{\{#/,end:/\}\}/,contains:[u]},{className:"template-tag",begin:/\{\{(?=else\}\})/,end:/\}\}/,keywords:"else"},{className:"template-tag",begin:/\{\{\//,end:/\}\}/,contains:[b]},{className:"template-variable",begin:/\{\{\{/,end:/\}\}\}/,contains:[h]},{className:"template-variable",begin:/\{\{/,end:/\}\}/,contains:[h]}]}}}()); +hljs.registerLanguage("haskell",function(){"use strict";return function(e){var n={variants:[e.COMMENT("--","$"),e.COMMENT("{-","-}",{contains:["self"]})]},i={className:"meta",begin:"{-#",end:"#-}"},a={className:"meta",begin:"^#",end:"$"},s={className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},l={begin:"\\(",end:"\\)",illegal:'"',contains:[i,a,{className:"type",begin:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},e.inherit(e.TITLE_MODE,{begin:"[_a-z][\\w']*"}),n]};return{name:"Haskell",aliases:["hs"],keywords:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",contains:[{beginKeywords:"module",end:"where",keywords:"module where",contains:[l,n],illegal:"\\W\\.|;"},{begin:"\\bimport\\b",end:"$",keywords:"import qualified as hiding",contains:[l,n],illegal:"\\W\\.|;"},{className:"class",begin:"^(\\s*)?(class|instance)\\b",end:"where",keywords:"class family instance where",contains:[s,l,n]},{className:"class",begin:"\\b(data|(new)?type)\\b",end:"$",keywords:"data family type newtype deriving",contains:[i,s,l,{begin:"{",end:"}",contains:l.contains},n]},{beginKeywords:"default",end:"$",contains:[s,l,n]},{beginKeywords:"infix infixl infixr",end:"$",contains:[e.C_NUMBER_MODE,n]},{begin:"\\bforeign\\b",end:"$",keywords:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",contains:[s,e.QUOTE_STRING_MODE,n]},{className:"meta",begin:"#!\\/usr\\/bin\\/env runhaskell",end:"$"},i,a,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,s,e.inherit(e.TITLE_MODE,{begin:"^[_a-z][\\w']*"}),n,{begin:"->|<-"}]}}}()); +hljs.registerLanguage("julia",function(){"use strict";return function(e){var r="[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*",t={$pattern:r,keyword:"in isa where baremodule begin break catch ccall const continue do else elseif end export false finally for function global if import importall let local macro module quote return true try using while type immutable abstract bitstype typealias ",literal:"true false ARGS C_NULL DevNull ENDIAN_BOM ENV I Inf Inf16 Inf32 Inf64 InsertionSort JULIA_HOME LOAD_PATH MergeSort NaN NaN16 NaN32 NaN64 PROGRAM_FILE QuickSort RoundDown RoundFromZero RoundNearest RoundNearestTiesAway RoundNearestTiesUp RoundToZero RoundUp STDERR STDIN STDOUT VERSION catalan e|0 eu|0 eulergamma golden im nothing pi γ π φ ",built_in:"ANY AbstractArray AbstractChannel AbstractFloat AbstractMatrix AbstractRNG AbstractSerializer AbstractSet AbstractSparseArray AbstractSparseMatrix AbstractSparseVector AbstractString AbstractUnitRange AbstractVecOrMat AbstractVector Any ArgumentError Array AssertionError Associative Base64DecodePipe Base64EncodePipe Bidiagonal BigFloat BigInt BitArray BitMatrix BitVector Bool BoundsError BufferStream CachingPool CapturedException CartesianIndex CartesianRange Cchar Cdouble Cfloat Channel Char Cint Cintmax_t Clong Clonglong ClusterManager Cmd CodeInfo Colon Complex Complex128 Complex32 Complex64 CompositeException Condition ConjArray ConjMatrix ConjVector Cptrdiff_t Cshort Csize_t Cssize_t Cstring Cuchar Cuint Cuintmax_t Culong Culonglong Cushort Cwchar_t Cwstring DataType Date DateFormat DateTime DenseArray DenseMatrix DenseVecOrMat DenseVector Diagonal Dict DimensionMismatch Dims DirectIndexString Display DivideError DomainError EOFError EachLine Enum Enumerate ErrorException Exception ExponentialBackOff Expr Factorization FileMonitor Float16 Float32 Float64 Function Future GlobalRef GotoNode HTML Hermitian IO IOBuffer IOContext IOStream IPAddr IPv4 IPv6 IndexCartesian IndexLinear IndexStyle InexactError InitError Int Int128 Int16 Int32 Int64 Int8 IntSet Integer InterruptException InvalidStateException Irrational KeyError LabelNode LinSpace LineNumberNode LoadError LowerTriangular MIME Matrix MersenneTwister Method MethodError MethodTable Module NTuple NewvarNode NullException Nullable Number ObjectIdDict OrdinalRange OutOfMemoryError OverflowError Pair ParseError PartialQuickSort PermutedDimsArray Pipe PollingFileWatcher ProcessExitedException Ptr QuoteNode RandomDevice Range RangeIndex Rational RawFD ReadOnlyMemoryError Real ReentrantLock Ref Regex RegexMatch RemoteChannel RemoteException RevString RoundingMode RowVector SSAValue SegmentationFault SerializationState Set SharedArray SharedMatrix SharedVector Signed SimpleVector Slot SlotNumber SparseMatrixCSC SparseVector StackFrame StackOverflowError StackTrace StepRange StepRangeLen StridedArray StridedMatrix StridedVecOrMat StridedVector String SubArray SubString SymTridiagonal Symbol Symmetric SystemError TCPSocket Task Text TextDisplay Timer Tridiagonal Tuple Type TypeError TypeMapEntry TypeMapLevel TypeName TypeVar TypedSlot UDPSocket UInt UInt128 UInt16 UInt32 UInt64 UInt8 UndefRefError UndefVarError UnicodeError UniformScaling Union UnionAll UnitRange Unsigned UpperTriangular Val Vararg VecElement VecOrMat Vector VersionNumber Void WeakKeyDict WeakRef WorkerConfig WorkerPool "},a={keywords:t,illegal:/<\//},n={className:"subst",begin:/\$\(/,end:/\)/,keywords:t},o={className:"variable",begin:"\\$"+r},i={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],variants:[{begin:/\w*"""/,end:/"""\w*/,relevance:10},{begin:/\w*"/,end:/"\w*/}]},l={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],begin:"`",end:"`"},s={className:"meta",begin:"@"+r};return a.name="Julia",a.contains=[{className:"number",begin:/(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/,relevance:0},{className:"string",begin:/'(.|\\[xXuU][a-zA-Z0-9]+)'/},i,l,s,{className:"comment",variants:[{begin:"#=",end:"=#",relevance:10},{begin:"#",end:"$"}]},e.HASH_COMMENT_MODE,{className:"keyword",begin:"\\b(((abstract|primitive)\\s+)type|(mutable\\s+)?struct)\\b"},{begin:/<:/}],n.contains=a.contains,a}}()); +hljs.registerLanguage("nim",function(){"use strict";return function(e){return{name:"Nim",aliases:["nim"],keywords:{keyword:"addr and as asm bind block break case cast const continue converter discard distinct div do elif else end enum except export finally for from func generic if import in include interface is isnot iterator let macro method mixin mod nil not notin object of or out proc ptr raise ref return shl shr static template try tuple type using var when while with without xor yield",literal:"shared guarded stdin stdout stderr result true false",built_in:"int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 float float32 float64 bool char string cstring pointer expr stmt void auto any range array openarray varargs seq set clong culong cchar cschar cshort cint csize clonglong cfloat cdouble clongdouble cuchar cushort cuint culonglong cstringarray semistatic"},contains:[{className:"meta",begin:/{\./,end:/\.}/,relevance:10},{className:"string",begin:/[a-zA-Z]\w*"/,end:/"/,contains:[{begin:/""/}]},{className:"string",begin:/([a-zA-Z]\w*)?"""/,end:/"""/},e.QUOTE_STRING_MODE,{className:"type",begin:/\b[A-Z]\w+\b/,relevance:0},{className:"number",relevance:0,variants:[{begin:/\b(0[xX][0-9a-fA-F][_0-9a-fA-F]*)('?[iIuU](8|16|32|64))?/},{begin:/\b(0o[0-7][_0-7]*)('?[iIuUfF](8|16|32|64))?/},{begin:/\b(0(b|B)[01][_01]*)('?[iIuUfF](8|16|32|64))?/},{begin:/\b(\d[_\d]*)('?[iIuUfF](8|16|32|64))?/}]},e.HASH_COMMENT_MODE]}}}()); +hljs.registerLanguage("nix",function(){"use strict";return function(e){var n={keyword:"rec with let in inherit assert if else then",literal:"true false or and null",built_in:"import abort baseNameOf dirOf isNull builtins map removeAttrs throw toString derivation"},i={className:"subst",begin:/\$\{/,end:/}/,keywords:n},t={className:"string",contains:[i],variants:[{begin:"''",end:"''"},{begin:'"',end:'"'}]},s=[e.NUMBER_MODE,e.HASH_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t,{begin:/[a-zA-Z0-9-_]+(\s*=)/,returnBegin:!0,relevance:0,contains:[{className:"attr",begin:/\S+/}]}];return i.contains=s,{name:"Nix",aliases:["nixos"],keywords:n,contains:s}}}()); +hljs.registerLanguage("r",function(){"use strict";return function(e){var n="([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*";return{name:"R",contains:[e.HASH_COMMENT_MODE,{begin:n,keywords:{$pattern:n,keyword:"function if in break next repeat else for return switch while try tryCatch stop warning require library attach detach source setMethod setGeneric setGroupGeneric setClass ...",literal:"NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10"},relevance:0},{className:"number",begin:"0[xX][0-9a-fA-F]+[Li]?\\b",relevance:0},{className:"number",begin:"\\d+(?:[eE][+\\-]?\\d*)?L\\b",relevance:0},{className:"number",begin:"\\d+\\.(?!\\d)(?:i\\b)?",relevance:0},{className:"number",begin:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{className:"number",begin:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{begin:"`",end:"`",relevance:0},{className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:'"',end:'"'},{begin:"'",end:"'"}]}]}}}()); +hljs.registerLanguage("scala",function(){"use strict";return function(e){var n={className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"},{begin:"\\${",end:"}"}]},a={className:"string",variants:[{begin:'"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:'"""',end:'"""',relevance:10},{begin:'[a-z]+"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE,n]},{className:"string",begin:'[a-z]+"""',end:'"""',contains:[n],relevance:10}]},s={className:"type",begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},t={className:"title",begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,relevance:0},i={className:"class",beginKeywords:"class object trait type",end:/[:={\[\n;]/,excludeEnd:!0,contains:[{beginKeywords:"extends with",relevance:10},{begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},t]},l={className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,excludeEnd:!0,contains:[t]};return{name:"Scala",keywords:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,{className:"symbol",begin:"'\\w[\\w\\d_]*(?!')"},s,l,i,e.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"}]}}}()); +hljs.registerLanguage("x86asm",function(){"use strict";return function(s){return{name:"Intel x86 Assembly",case_insensitive:!0,keywords:{$pattern:"[.%]?"+s.IDENT_RE,keyword:"lock rep repe repz repne repnz xaquire xrelease bnd nobnd aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63",built_in:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr",meta:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %if %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},contains:[s.COMMENT(";","$",{relevance:0}),{className:"number",variants:[{begin:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",relevance:0},{begin:"\\$[0-9][0-9A-Fa-f]*",relevance:0},{begin:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{begin:"\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"}]},s.QUOTE_STRING_MODE,{className:"string",variants:[{begin:"'",end:"[^\\\\]'"},{begin:"`",end:"[^\\\\]`"}],relevance:0},{className:"symbol",variants:[{begin:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)"},{begin:"^\\s*%%[A-Za-z0-9_$#@~.?]*:"}],relevance:0},{className:"subst",begin:"%[0-9]+",relevance:0},{className:"subst",begin:"%!S+",relevance:0},{className:"meta",begin:/^\s*\.[\w_-]+/}]}}}()); \ No newline at end of file diff --git a/en/index.html b/en/index.html new file mode 100644 index 000000000..b60be693e --- /dev/null +++ b/en/index.html @@ -0,0 +1,225 @@ + + + + + + Introduction - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Introduction

+

Async-graphql is a GraphQL server-side library implemented in Rust. It is fully compatible with the GraphQL specification and most of its extensions, and offers type safety and high performance.

+

You can define a Schema in Rust and procedural macros will automatically generate code for a GraphQL query. This library does not extend Rust's syntax, which means that Rustfmt can be used normally. I value this highly and it is one of the reasons why I developed Async-graphql.

+

Why do this?

+

I like GraphQL and Rust. I've been using Juniper, which solves the problem of implementing a GraphQL server with Rust. But Juniper had several problems, the most important of which is that it didn't support async/await at the time. So I decided to make this library for myself.

+

Benchmarks

+

Ensure that there is no CPU-heavy process in background!

+
cd benchmark
+cargo bench
+
+

Now a HTML report is available at benchmark/target/criterion/report.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/input_value_validators.html b/en/input_value_validators.html new file mode 100644 index 000000000..4f669a792 --- /dev/null +++ b/en/input_value_validators.html @@ -0,0 +1,307 @@ + + + + + + Input value validators - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Input value validators

+

Async-graphql has some common validators built-in, you can use them on the parameters of object fields or on the fields of InputObject.

+
    +
  • maximum=N the number cannot be greater than N.
  • +
  • minimum=N the number cannot be less than N.
  • +
  • multiple_of=N the number must be a multiple of N.
  • +
  • max_items=N the length of the list cannot be greater than N.
  • +
  • min_items=N the length of the list cannot be less than N.
  • +
  • max_length=N the length of the string cannot be greater than N.
  • +
  • min_length=N the length of the string cannot be less than N.
  • +
  • chars_max_length=N the count of the unicode chars cannot be greater than N.
  • +
  • chars_min_length=N the count of the unicode chars cannot be less than N.
  • +
  • email is valid email.
  • +
  • url is valid url.
  • +
  • ip is valid ip address.
  • +
  • regex=RE is match for the regex.
  • +
  • uuid=V the string or ID is a valid UUID with version V. You may omit V to accept any UUID version.
  • +
+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    /// The length of the name must be greater than or equal to 5 and less than or equal to 10.
+    async fn input(&self, #[graphql(validator(min_length = 5, max_length = 10))] name: String) -> Result<i32> {
+        todo!()
+    }
+}
+}
+

Check every member of the list

+

You can enable the list attribute, and the validator will check all members in list:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn input(&self, #[graphql(validator(list, max_length = 10))] names: Vec<String>) -> Result<i32> {
+       todo!()
+    }
+}
+}
+

Custom validator

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct MyValidator {
+    expect: i32,
+}
+
+impl MyValidator {
+    pub fn new(n: i32) -> Self {
+        MyValidator { expect: n }
+    }
+}
+
+impl CustomValidator<i32> for MyValidator {
+    fn check(&self, value: &i32) -> Result<(), InputValueError<i32>> {
+        if *value == self.expect {
+            Ok(())
+        } else {
+            Err(InputValueError::custom(format!("expect 100, actual {}", value)))
+        }
+    }
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    /// n must be equal to 100
+    async fn value(
+        &self,
+        #[graphql(validator(custom = "MyValidator::new(100)"))] n: i32,
+    ) -> i32 {
+        n
+    }
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/integrations.html b/en/integrations.html new file mode 100644 index 000000000..81a05b8e6 --- /dev/null +++ b/en/integrations.html @@ -0,0 +1,230 @@ + + + + + + Integrations - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Integrations

+

Async-graphql supports several common Rust web servers.

+ +

Even if the server you are currently using is not in the above list, it is quite simple to implement similar functionality yourself.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/integrations_to_actix_web.html b/en/integrations_to_actix_web.html new file mode 100644 index 000000000..b18c2e698 --- /dev/null +++ b/en/integrations_to_actix_web.html @@ -0,0 +1,264 @@ + + + + + + Actix-web - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Actix-web

+

Request example

+

When you define your actix_web::App you need to pass in the Schema as data.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_actix_web;
+extern crate async_graphql;
+extern crate actix_web;
+use async_graphql::*;
+#[derive(Default,SimpleObject)]
+struct Query { a: i32 }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+use actix_web::{web, HttpRequest, HttpResponse};
+use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse};
+async fn index(
+    // Schema now accessible here
+    schema: web::Data<Schema<Query, EmptyMutation, EmptySubscription>>,
+    request: GraphQLRequest,
+) -> web::Json<GraphQLResponse> {
+    web::Json(schema.execute(request.into_inner()).await.into())
+}
+}
+

Subscription example

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_actix_web;
+extern crate async_graphql;
+extern crate actix_web;
+use async_graphql::*;
+#[derive(Default,SimpleObject)]
+struct Query { a: i32 }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+use actix_web::{web, HttpRequest, HttpResponse};
+use async_graphql_actix_web::GraphQLSubscription;
+async fn index_ws(
+    schema: web::Data<Schema<Query, EmptyMutation, EmptySubscription>>,
+    req: HttpRequest,
+    payload: web::Payload,
+) -> actix_web::Result<HttpResponse> {
+    GraphQLSubscription::new(Schema::clone(&*schema)).start(&req, payload)
+}
+}
+

More examples

+

https://github.com/async-graphql/examples/tree/master/actix-web

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/integrations_to_poem.html b/en/integrations_to_poem.html new file mode 100644 index 000000000..f385e3c18 --- /dev/null +++ b/en/integrations_to_poem.html @@ -0,0 +1,255 @@ + + + + + + Poem - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Poem

+

Request example

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_poem;
+extern crate async_graphql;
+extern crate poem;
+use async_graphql::*;
+#[derive(Default, SimpleObject)]
+struct Query { a: i32 }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+use poem::Route;
+use async_graphql_poem::GraphQL;
+
+let app = Route::new()
+    .at("/ws", GraphQL::new(schema));
+}
+

Subscription example

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_poem;
+extern crate async_graphql;
+extern crate poem;
+use async_graphql::*;
+#[derive(Default, SimpleObject)]
+struct Query { a: i32 }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+use poem::{get, Route};
+use async_graphql_poem::GraphQLSubscription;
+
+let app = Route::new()
+    .at("/ws", get(GraphQLSubscription::new(schema)));
+}
+

More examples

+

https://github.com/async-graphql/examples/tree/master/poem

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/integrations_to_warp.html b/en/integrations_to_warp.html new file mode 100644 index 000000000..fd98e0212 --- /dev/null +++ b/en/integrations_to_warp.html @@ -0,0 +1,279 @@ + + + + + + Warp - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Warp

+

For Async-graphql-warp, two Filter integrations are provided: graphql and graphql_subscription.

+

The graphql filter is used for execution Query and Mutation requests. It extracts GraphQL request and outputs async_graphql::Schema and async_graphql::Request. +You can combine other filters later, or directly call Schema::execute to execute the query.

+

graphql_subscription is used to implement WebSocket subscriptions. It outputs warp::Reply.

+

Request example

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_warp;
+extern crate async_graphql;
+extern crate warp;
+use async_graphql::*;
+use std::convert::Infallible;
+use warp::Filter;
+struct QueryRoot;
+#[Object]
+impl QueryRoot { async fn version(&self) -> &str { "1.0" } }
+async fn other() {
+type MySchema = Schema<QueryRoot, EmptyMutation, EmptySubscription>;
+
+let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription);
+let filter = async_graphql_warp::graphql(schema).and_then(|(schema, request): (MySchema, async_graphql::Request)| async move {
+    // Execute query
+    let resp = schema.execute(request).await;
+
+    // Return result
+    Ok::<_, Infallible>(async_graphql_warp::GraphQLResponse::from(resp))
+});
+warp::serve(filter).run(([0, 0, 0, 0], 8000)).await;
+}
+}
+

Subscription example

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_warp;
+extern crate async_graphql;
+extern crate warp;
+use async_graphql::*;
+use futures_util::stream::{Stream, StreamExt};
+use std::convert::Infallible;
+use warp::Filter;
+struct SubscriptionRoot;
+#[Subscription]
+impl SubscriptionRoot {
+  async fn tick(&self) -> impl Stream<Item = i32> {
+    futures_util::stream::iter(0..10)
+  }
+}
+struct QueryRoot;
+#[Object]
+impl QueryRoot { async fn version(&self) -> &str { "1.0" } }
+async fn other() {
+let schema = Schema::new(QueryRoot, EmptyMutation, SubscriptionRoot);
+let filter = async_graphql_warp::graphql_subscription(schema);
+warp::serve(filter).run(([0, 0, 0, 0], 8000)).await;
+}
+}
+

More examples

+

https://github.com/async-graphql/examples/tree/master/warp

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/introduction.html b/en/introduction.html new file mode 100644 index 000000000..b60be693e --- /dev/null +++ b/en/introduction.html @@ -0,0 +1,225 @@ + + + + + + Introduction - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Introduction

+

Async-graphql is a GraphQL server-side library implemented in Rust. It is fully compatible with the GraphQL specification and most of its extensions, and offers type safety and high performance.

+

You can define a Schema in Rust and procedural macros will automatically generate code for a GraphQL query. This library does not extend Rust's syntax, which means that Rustfmt can be used normally. I value this highly and it is one of the reasons why I developed Async-graphql.

+

Why do this?

+

I like GraphQL and Rust. I've been using Juniper, which solves the problem of implementing a GraphQL server with Rust. But Juniper had several problems, the most important of which is that it didn't support async/await at the time. So I decided to make this library for myself.

+

Benchmarks

+

Ensure that there is no CPU-heavy process in background!

+
cd benchmark
+cargo bench
+
+

Now a HTML report is available at benchmark/target/criterion/report.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/mark.min.js b/en/mark.min.js new file mode 100644 index 000000000..163623188 --- /dev/null +++ b/en/mark.min.js @@ -0,0 +1,7 @@ +/*!*************************************************** +* mark.js v8.11.1 +* https://markjs.io/ +* Copyright (c) 2014–2018, Julian Kühnel +* Released under the MIT license https://git.io/vwTVl +*****************************************************/ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Mark=t()}(this,function(){"use strict";var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},n=function(){function e(e,t){for(var n=0;n1&&void 0!==arguments[1])||arguments[1],i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:5e3;t(this,e),this.ctx=n,this.iframes=r,this.exclude=i,this.iframesTimeout=o}return n(e,[{key:"getContexts",value:function(){var e=[];return(void 0!==this.ctx&&this.ctx?NodeList.prototype.isPrototypeOf(this.ctx)?Array.prototype.slice.call(this.ctx):Array.isArray(this.ctx)?this.ctx:"string"==typeof this.ctx?Array.prototype.slice.call(document.querySelectorAll(this.ctx)):[this.ctx]:[]).forEach(function(t){var n=e.filter(function(e){return e.contains(t)}).length>0;-1!==e.indexOf(t)||n||e.push(t)}),e}},{key:"getIframeContents",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){},r=void 0;try{var i=e.contentWindow;if(r=i.document,!i||!r)throw new Error("iframe inaccessible")}catch(e){n()}r&&t(r)}},{key:"isIframeBlank",value:function(e){var t="about:blank",n=e.getAttribute("src").trim();return e.contentWindow.location.href===t&&n!==t&&n}},{key:"observeIframeLoad",value:function(e,t,n){var r=this,i=!1,o=null,a=function a(){if(!i){i=!0,clearTimeout(o);try{r.isIframeBlank(e)||(e.removeEventListener("load",a),r.getIframeContents(e,t,n))}catch(e){n()}}};e.addEventListener("load",a),o=setTimeout(a,this.iframesTimeout)}},{key:"onIframeReady",value:function(e,t,n){try{"complete"===e.contentWindow.document.readyState?this.isIframeBlank(e)?this.observeIframeLoad(e,t,n):this.getIframeContents(e,t,n):this.observeIframeLoad(e,t,n)}catch(e){n()}}},{key:"waitForIframes",value:function(e,t){var n=this,r=0;this.forEachIframe(e,function(){return!0},function(e){r++,n.waitForIframes(e.querySelector("html"),function(){--r||t()})},function(e){e||t()})}},{key:"forEachIframe",value:function(t,n,r){var i=this,o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},a=t.querySelectorAll("iframe"),s=a.length,c=0;a=Array.prototype.slice.call(a);var u=function(){--s<=0&&o(c)};s||u(),a.forEach(function(t){e.matches(t,i.exclude)?u():i.onIframeReady(t,function(e){n(t)&&(c++,r(e)),u()},u)})}},{key:"createIterator",value:function(e,t,n){return document.createNodeIterator(e,t,n,!1)}},{key:"createInstanceOnIframe",value:function(t){return new e(t.querySelector("html"),this.iframes)}},{key:"compareNodeIframe",value:function(e,t,n){if(e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_PRECEDING){if(null===t)return!0;if(t.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_FOLLOWING)return!0}return!1}},{key:"getIteratorNode",value:function(e){var t=e.previousNode();return{prevNode:t,node:null===t?e.nextNode():e.nextNode()&&e.nextNode()}}},{key:"checkIframeFilter",value:function(e,t,n,r){var i=!1,o=!1;return r.forEach(function(e,t){e.val===n&&(i=t,o=e.handled)}),this.compareNodeIframe(e,t,n)?(!1!==i||o?!1===i||o||(r[i].handled=!0):r.push({val:n,handled:!0}),!0):(!1===i&&r.push({val:n,handled:!1}),!1)}},{key:"handleOpenIframes",value:function(e,t,n,r){var i=this;e.forEach(function(e){e.handled||i.getIframeContents(e.val,function(e){i.createInstanceOnIframe(e).forEachNode(t,n,r)})})}},{key:"iterateThroughNodes",value:function(e,t,n,r,i){for(var o,a=this,s=this.createIterator(t,e,r),c=[],u=[],l=void 0,h=void 0;void 0,o=a.getIteratorNode(s),h=o.prevNode,l=o.node;)this.iframes&&this.forEachIframe(t,function(e){return a.checkIframeFilter(l,h,e,c)},function(t){a.createInstanceOnIframe(t).forEachNode(e,function(e){return u.push(e)},r)}),u.push(l);u.forEach(function(e){n(e)}),this.iframes&&this.handleOpenIframes(c,e,n,r),i()}},{key:"forEachNode",value:function(e,t,n){var r=this,i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},o=this.getContexts(),a=o.length;a||i(),o.forEach(function(o){var s=function(){r.iterateThroughNodes(e,o,t,n,function(){--a<=0&&i()})};r.iframes?r.waitForIframes(o,s):s()})}}],[{key:"matches",value:function(e,t){var n="string"==typeof t?[t]:t,r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector;if(r){var i=!1;return n.every(function(t){return!r.call(e,t)||(i=!0,!1)}),i}return!1}}]),e}(),o=function(){function e(n){t(this,e),this.opt=r({},{diacritics:!0,synonyms:{},accuracy:"partially",caseSensitive:!1,ignoreJoiners:!1,ignorePunctuation:[],wildcards:"disabled"},n)}return n(e,[{key:"create",value:function(e){return"disabled"!==this.opt.wildcards&&(e=this.setupWildcardsRegExp(e)),e=this.escapeStr(e),Object.keys(this.opt.synonyms).length&&(e=this.createSynonymsRegExp(e)),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),this.opt.diacritics&&(e=this.createDiacriticsRegExp(e)),e=this.createMergedBlanksRegExp(e),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.createJoinersRegExp(e)),"disabled"!==this.opt.wildcards&&(e=this.createWildcardsRegExp(e)),e=this.createAccuracyRegExp(e),new RegExp(e,"gm"+(this.opt.caseSensitive?"":"i"))}},{key:"escapeStr",value:function(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}},{key:"createSynonymsRegExp",value:function(e){var t=this.opt.synonyms,n=this.opt.caseSensitive?"":"i",r=this.opt.ignoreJoiners||this.opt.ignorePunctuation.length?"\0":"";for(var i in t)if(t.hasOwnProperty(i)){var o=t[i],a="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(i):this.escapeStr(i),s="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(o):this.escapeStr(o);""!==a&&""!==s&&(e=e.replace(new RegExp("("+this.escapeStr(a)+"|"+this.escapeStr(s)+")","gm"+n),r+"("+this.processSynonyms(a)+"|"+this.processSynonyms(s)+")"+r))}return e}},{key:"processSynonyms",value:function(e){return(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),e}},{key:"setupWildcardsRegExp",value:function(e){return(e=e.replace(/(?:\\)*\?/g,function(e){return"\\"===e.charAt(0)?"?":""})).replace(/(?:\\)*\*/g,function(e){return"\\"===e.charAt(0)?"*":""})}},{key:"createWildcardsRegExp",value:function(e){var t="withSpaces"===this.opt.wildcards;return e.replace(/\u0001/g,t?"[\\S\\s]?":"\\S?").replace(/\u0002/g,t?"[\\S\\s]*?":"\\S*")}},{key:"setupIgnoreJoinersRegExp",value:function(e){return e.replace(/[^(|)\\]/g,function(e,t,n){var r=n.charAt(t+1);return/[(|)\\]/.test(r)||""===r?e:e+"\0"})}},{key:"createJoinersRegExp",value:function(e){var t=[],n=this.opt.ignorePunctuation;return Array.isArray(n)&&n.length&&t.push(this.escapeStr(n.join(""))),this.opt.ignoreJoiners&&t.push("\\u00ad\\u200b\\u200c\\u200d"),t.length?e.split(/\u0000+/).join("["+t.join("")+"]*"):e}},{key:"createDiacriticsRegExp",value:function(e){var t=this.opt.caseSensitive?"":"i",n=this.opt.caseSensitive?["aàáảãạăằắẳẵặâầấẩẫậäåāą","AÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćč","CÇĆČ","dđď","DĐĎ","eèéẻẽẹêềếểễệëěēę","EÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïī","IÌÍỈĨỊÎÏĪ","lł","LŁ","nñňń","NÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøō","OÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rř","RŘ","sšśșş","SŠŚȘŞ","tťțţ","TŤȚŢ","uùúủũụưừứửữựûüůū","UÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿ","YÝỲỶỸỴŸ","zžżź","ZŽŻŹ"]:["aàáảãạăằắẳẵặâầấẩẫậäåāąAÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćčCÇĆČ","dđďDĐĎ","eèéẻẽẹêềếểễệëěēęEÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïīIÌÍỈĨỊÎÏĪ","lłLŁ","nñňńNÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøōOÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rřRŘ","sšśșşSŠŚȘŞ","tťțţTŤȚŢ","uùúủũụưừứửữựûüůūUÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿYÝỲỶỸỴŸ","zžżźZŽŻŹ"],r=[];return e.split("").forEach(function(i){n.every(function(n){if(-1!==n.indexOf(i)){if(r.indexOf(n)>-1)return!1;e=e.replace(new RegExp("["+n+"]","gm"+t),"["+n+"]"),r.push(n)}return!0})}),e}},{key:"createMergedBlanksRegExp",value:function(e){return e.replace(/[\s]+/gim,"[\\s]+")}},{key:"createAccuracyRegExp",value:function(e){var t=this,n=this.opt.accuracy,r="string"==typeof n?n:n.value,i="";switch(("string"==typeof n?[]:n.limiters).forEach(function(e){i+="|"+t.escapeStr(e)}),r){case"partially":default:return"()("+e+")";case"complementary":return"()([^"+(i="\\s"+(i||this.escapeStr("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~¡¿")))+"]*"+e+"[^"+i+"]*)";case"exactly":return"(^|\\s"+i+")("+e+")(?=$|\\s"+i+")"}}}]),e}(),a=function(){function a(e){t(this,a),this.ctx=e,this.ie=!1;var n=window.navigator.userAgent;(n.indexOf("MSIE")>-1||n.indexOf("Trident")>-1)&&(this.ie=!0)}return n(a,[{key:"log",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"debug",r=this.opt.log;this.opt.debug&&"object"===(void 0===r?"undefined":e(r))&&"function"==typeof r[n]&&r[n]("mark.js: "+t)}},{key:"getSeparatedKeywords",value:function(e){var t=this,n=[];return e.forEach(function(e){t.opt.separateWordSearch?e.split(" ").forEach(function(e){e.trim()&&-1===n.indexOf(e)&&n.push(e)}):e.trim()&&-1===n.indexOf(e)&&n.push(e)}),{keywords:n.sort(function(e,t){return t.length-e.length}),length:n.length}}},{key:"isNumeric",value:function(e){return Number(parseFloat(e))==e}},{key:"checkRanges",value:function(e){var t=this;if(!Array.isArray(e)||"[object Object]"!==Object.prototype.toString.call(e[0]))return this.log("markRanges() will only accept an array of objects"),this.opt.noMatch(e),[];var n=[],r=0;return e.sort(function(e,t){return e.start-t.start}).forEach(function(e){var i=t.callNoMatchOnInvalidRanges(e,r),o=i.start,a=i.end;i.valid&&(e.start=o,e.length=a-o,n.push(e),r=a)}),n}},{key:"callNoMatchOnInvalidRanges",value:function(e,t){var n=void 0,r=void 0,i=!1;return e&&void 0!==e.start?(r=(n=parseInt(e.start,10))+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&r-t>0&&r-n>0?i=!0:(this.log("Ignoring invalid or overlapping range: "+JSON.stringify(e)),this.opt.noMatch(e))):(this.log("Ignoring invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:n,end:r,valid:i}}},{key:"checkWhitespaceRanges",value:function(e,t,n){var r=void 0,i=!0,o=n.length,a=t-o,s=parseInt(e.start,10)-a;return(r=(s=s>o?o:s)+parseInt(e.length,10))>o&&(r=o,this.log("End range automatically set to the max value of "+o)),s<0||r-s<0||s>o||r>o?(i=!1,this.log("Invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)):""===n.substring(s,r).replace(/\s+/g,"")&&(i=!1,this.log("Skipping whitespace only range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:s,end:r,valid:i}}},{key:"getTextNodes",value:function(e){var t=this,n="",r=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,function(e){r.push({start:n.length,end:(n+=e.textContent).length,node:e})},function(e){return t.matchesExclude(e.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT},function(){e({value:n,nodes:r})})}},{key:"matchesExclude",value:function(e){return i.matches(e,this.opt.exclude.concat(["script","style","title","head","html"]))}},{key:"wrapRangeInTextNode",value:function(e,t,n){var r=this.opt.element?this.opt.element:"mark",i=e.splitText(t),o=i.splitText(n-t),a=document.createElement(r);return a.setAttribute("data-markjs","true"),this.opt.className&&a.setAttribute("class",this.opt.className),a.textContent=i.textContent,i.parentNode.replaceChild(a,i),o}},{key:"wrapRangeInMappedTextNode",value:function(e,t,n,r,i){var o=this;e.nodes.every(function(a,s){var c=e.nodes[s+1];if(void 0===c||c.start>t){if(!r(a.node))return!1;var u=t-a.start,l=(n>a.end?a.end:n)-a.start,h=e.value.substr(0,a.start),f=e.value.substr(l+a.start);if(a.node=o.wrapRangeInTextNode(a.node,u,l),e.value=h+f,e.nodes.forEach(function(t,n){n>=s&&(e.nodes[n].start>0&&n!==s&&(e.nodes[n].start-=l),e.nodes[n].end-=l)}),n-=l,i(a.node.previousSibling,a.start),!(n>a.end))return!1;t=a.end}return!0})}},{key:"wrapGroups",value:function(e,t,n,r){return r((e=this.wrapRangeInTextNode(e,t,t+n)).previousSibling),e}},{key:"separateGroups",value:function(e,t,n,r,i){for(var o=t.length,a=1;a-1&&r(t[a],e)&&(e=this.wrapGroups(e,s,t[a].length,i))}return e}},{key:"wrapMatches",value:function(e,t,n,r,i){var o=this,a=0===t?0:t+1;this.getTextNodes(function(t){t.nodes.forEach(function(t){t=t.node;for(var i=void 0;null!==(i=e.exec(t.textContent))&&""!==i[a];){if(o.opt.separateGroups)t=o.separateGroups(t,i,a,n,r);else{if(!n(i[a],t))continue;var s=i.index;if(0!==a)for(var c=1;c + + + + + Merging Objects / Subscriptions - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Merging Objects

+

Usually we can create multiple implementations for the same type in Rust, but due to the limitation of procedural macros, we can not create multiple Object implementations for the same type. For example, the following code will fail to compile.

+
#[Object]
+impl Query {
+    async fn users(&self) -> Vec<User> {
+        todo!()
+    }
+}
+
+#[Object]
+impl Query {
+    async fn movies(&self) -> Vec<Movie> {
+        todo!()
+    }
+}
+

Instead, the #[derive(MergedObject)] macro allows you to split an object's resolvers across multiple modules or files by merging 2 or more #[Object] implementations into one.

+

Tip: Every #[Object] needs a unique name, even in a MergedObject, so make sure to give each object you're merging its own name.

+

Note: This works for queries and mutations. For subscriptions, see "Merging Subscriptions" below.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct User { a: i32 }
+#[derive(SimpleObject)]
+struct Movie { a: i32 }
+#[derive(Default)]
+struct UserQuery;
+
+#[Object]
+impl UserQuery {
+    async fn users(&self) -> Vec<User> {
+        todo!()
+    }
+}
+
+#[derive(Default)]
+struct MovieQuery;
+
+#[Object]
+impl MovieQuery {
+    async fn movies(&self) -> Vec<Movie> {
+        todo!()
+    }
+}
+
+#[derive(MergedObject, Default)]
+struct Query(UserQuery, MovieQuery);
+
+let schema = Schema::new(
+    Query::default(),
+    EmptyMutation,
+    EmptySubscription
+);
+}
+
+

⚠️ MergedObject cannot be used in Interface.

+
+

Merging Subscriptions

+

Along with MergedObject, you can derive MergedSubscription or use #[MergedSubscription] to merge separate #[Subscription] blocks.

+

Like merging Objects, each subscription block requires a unique name.

+

Example:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use futures_util::stream::{Stream};
+#[derive(Default,SimpleObject)]
+struct Query { a: i32 }
+#[derive(Default)]
+struct Subscription1;
+
+#[Subscription]
+impl Subscription1 {
+    async fn events1(&self) -> impl Stream<Item = i32> {
+        futures_util::stream::iter(0..10)
+    }
+}
+
+#[derive(Default)]
+struct Subscription2;
+
+#[Subscription]
+impl Subscription2 {
+    async fn events2(&self) -> impl Stream<Item = i32> {
+        futures_util::stream::iter(10..20)
+    }
+}
+
+#[derive(MergedSubscription, Default)]
+struct Subscription(Subscription1, Subscription2);
+
+let schema = Schema::new(
+    Query::default(),
+    EmptyMutation,
+    Subscription::default()
+);
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/print.html b/en/print.html new file mode 100644 index 000000000..18b477e93 --- /dev/null +++ b/en/print.html @@ -0,0 +1,3211 @@ + + + + + + Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Introduction

+

Async-graphql is a GraphQL server-side library implemented in Rust. It is fully compatible with the GraphQL specification and most of its extensions, and offers type safety and high performance.

+

You can define a Schema in Rust and procedural macros will automatically generate code for a GraphQL query. This library does not extend Rust's syntax, which means that Rustfmt can be used normally. I value this highly and it is one of the reasons why I developed Async-graphql.

+

Why do this?

+

I like GraphQL and Rust. I've been using Juniper, which solves the problem of implementing a GraphQL server with Rust. But Juniper had several problems, the most important of which is that it didn't support async/await at the time. So I decided to make this library for myself.

+

Benchmarks

+

Ensure that there is no CPU-heavy process in background!

+
cd benchmark
+cargo bench
+
+

Now a HTML report is available at benchmark/target/criterion/report.

+

Quickstart

+

Add dependency libraries

+
[dependencies]
+async-graphql = "4.0"
+async-graphql-actix-web = "4.0" # If you need to integrate into actix-web
+async-graphql-warp = "4.0" # If you need to integrate into warp
+async-graphql-tide = "4.0" # If you need to integrate into tide
+
+

Write a Schema

+

The Schema of a GraphQL contains a required Query, an optional Mutation, and an optional Subscription. These object types are described using the structure of the Rust language. The field of the structure corresponds to the field of the GraphQL object.

+

Async-graphql implements the mapping of common data types to GraphQL types, such as i32, f64, Option<T>, Vec<T>, etc. Also, you can extend these base types, which are called scalars in the GraphQL.

+

Here is a simple example where we provide just one query that returns the sum of a and b.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    /// Returns the sum of a and b
+    async fn add(&self, a: i32, b: i32) -> i32 {
+        a + b
+    }
+}
+}
+

Execute the query

+

In our example, there is only a Query without a Mutation or Subscription, so we create the Schema with EmptyMutation and EmptySubscription, and then call Schema::execute to execute the Query.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+#[Object]
+impl Query {
+  async fn version(&self) -> &str { "1.0" }    
+}
+async fn other() {
+let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
+let res = schema.execute("{ add(a: 10, b: 20) }").await;
+}
+}
+

Output the query results as JSON

+
let json = serde_json::to_string(&res);
+

Web server integration

+

All examples are in the sub-repository, located in the examples directory.

+
git submodule update # update the examples repo
+cd examples && cargo run --bin [name]
+
+

For more information, see the sub-repository README.md.

+

Type System

+

Async-graphql implements conversions from GraphQL Objects to Rust structs, and it's easy to use.

+

SimpleObject

+

SimpleObject directly maps all the fields of a struct to GraphQL object. +If you don't require automatic mapping of fields, see Object.

+

The example below defines an object MyObject which includes the fields a and b. c will be not mapped to GraphQL as it is labelled as #[graphql(skip)]

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+#[derive(SimpleObject)]
+struct MyObject {
+    /// Value a
+    a: i32,
+
+    /// Value b
+    b: i32,
+
+    #[graphql(skip)]
+    c: i32,
+}
+}
+

User-defined resolvers

+

Sometimes most of the fields of a GraphQL object simply return the value of the structure member, but a few +fields are calculated. In this case, the Object macro cannot be used unless you hand-write all the resolvers.

+

The ComplexObject macro works in conjunction with the SimpleObject macro. The SimpleObject derive macro defines +the non-calculated fields, where as the ComplexObject macro let's you write user-defined resolvers for the calculated fields.

+

Resolvers added to ComplexObject adhere to the same rules as resolvers of Object.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+#[graphql(complex)] // NOTE: If you want the `ComplexObject` macro to take effect, this `complex` attribute is required.
+struct MyObj {
+    a: i32,
+    b: i32,
+}
+
+#[ComplexObject]
+impl MyObj {
+    async fn c(&self) -> i32 {
+        self.a + self.b
+    }
+}
+}
+

Used for both input and output

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject, InputObject)]
+#[graphql(input_name = "MyObjInput")] // Note: You must use the input_name attribute to define a new name for the input type, otherwise a runtime error will occur.
+struct MyObj {
+    a: i32,
+    b: i32,
+}
+}
+

Flatten fields

+

You can flatten fields by adding #[graphql(flatten)], i.e.:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+pub struct ChildObject {
+    b: String,
+    c: String,
+}
+
+#[derive(SimpleObject)]
+pub struct ParentObject {
+    a: String,
+    #[graphql(flatten)]
+    child: ChildObject,
+}
+
+// Is the same as
+
+#[derive(SimpleObject)]
+pub struct Object {
+    a: String,
+    b: String,
+    c: String,
+}
+}
+

Object

+

Different from SimpleObject, Object must have a resolver defined for each field in its impl.

+

A resolver function has to be asynchronous. The first argument has to be &self, the second is an optional Context and it is followed by field arguments.

+

The resolver is used to get the value of the field. For example, you can query a database and return the result. The return type of the function is the type of the field. You can also return a async_graphql::Result to return an error if it occurs. The error message will then be sent as query result.

+

You may need access to global data in your query, for example a database connection pool. +When creating your Schema, you can use SchemaBuilder::data to configure the global data, and Context::data to configure Context data. +The following value_from_db function shows how to retrieve a database connection from Context.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+struct Data { pub name: String }
+struct DbConn {}
+impl DbConn {
+  fn query_something(&self, id: i64) -> std::result::Result<Data, String> { Ok(Data {name:"".into()})}
+}
+struct DbPool {}
+impl DbPool {
+  fn take(&self) -> DbConn { DbConn {} }    
+}
+use async_graphql::*;
+
+struct MyObject {
+    value: i32,
+}
+
+#[Object]
+impl MyObject {
+    async fn value(&self) -> String {
+        self.value.to_string()
+    }
+
+    async fn value_from_db(
+        &self,
+        ctx: &Context<'_>,
+        #[graphql(desc = "Id of object")] id: i64
+    ) -> Result<String> {
+        let conn = ctx.data::<DbPool>()?.take();
+        Ok(conn.query_something(id)?.name)
+    }
+}
+}
+

Context

+

The main goal of Context is to acquire global data attached to Schema and also data related to the actual query being processed.

+

Store Data

+

Inside the Context you can put global data, like environment variables, db connection pool, whatever you may need in every query.

+

The data must implement Send and Sync.

+

You can request the data inside a query by just calling ctx.data::<TypeOfYourData>().

+

Note that if the return value of resolver function is borrowed from Context, you will need to explicitly state the lifetime of the argument.

+

The following example shows how to borrow data in Context.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn borrow_from_context_data<'ctx>(
+        &self,
+        ctx: &Context<'ctx>
+    ) -> Result<&'ctx String> {
+        ctx.data::<String>()
+    }
+}
+}
+

Schema data

+

You can put data inside the context at the creation of the schema, it's useful for data that do not change, like a connection pool.

+

An instance of how it would be written inside an application:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Default,SimpleObject)]
+struct Query { version: i32}
+struct EnvStruct;
+let env_struct = EnvStruct;
+struct S3Object;
+let s3_storage = S3Object;
+struct DBConnection;
+let db_core = DBConnection;
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription)
+    .data(env_struct)
+    .data(s3_storage)
+    .data(db_core)
+    .finish();
+}
+

Request data

+

You can put data inside the context at the execution of the request, it's useful for authentication data for instance.

+

A little example with a warp route:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate async_graphql_warp;
+extern crate warp;
+use async_graphql::*;
+use warp::{Filter, Reply};
+use std::convert::Infallible;
+#[derive(Default, SimpleObject)]
+struct Query { name: String }
+struct AuthInfo { pub token: Option<String> }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+let schema_filter = async_graphql_warp::graphql(schema);
+let graphql_post = warp::post()
+  .and(warp::path("graphql"))
+  .and(warp::header::optional("Authorization"))
+  .and(schema_filter)
+  .and_then( |auth: Option<String>, (schema, mut request): (Schema<Query, EmptyMutation, EmptySubscription>, async_graphql::Request)| async move {
+    // Do something to get auth data from the header
+    let your_auth_data = AuthInfo { token: auth };
+    let response = schema
+      .execute(
+        request
+         .data(your_auth_data)
+      ).await;
+
+    Ok::<_, Infallible>(async_graphql_warp::GraphQLResponse::from(response))
+  });
+}
+

Headers

+

With the Context you can also insert and appends headers.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate http;
+use ::http::header::ACCESS_CONTROL_ALLOW_ORIGIN;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query {
+    async fn greet(&self, ctx: &Context<'_>) -> String {
+        // Headers can be inserted using the `http` constants
+        let was_in_headers = ctx.insert_http_header(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
+
+        // They can also be inserted using &str
+        let was_in_headers = ctx.insert_http_header("Custom-Header", "1234");
+
+        // If multiple headers with the same key are `inserted` then the most recent
+        // one overwrites the previous. If you want multiple headers for the same key, use
+        // `append_http_header` for subsequent headers
+        let was_in_headers = ctx.append_http_header("Custom-Header", "Hello World");
+
+        String::from("Hello world")
+    }
+}
+}
+

Selection / LookAhead

+

Sometimes you want to know what fields are requested in the subquery to optimize the processing of data. You can read fields across the query with ctx.field() which will give you a SelectionField which will allow you to navigate across the fields and subfields.

+

If you want to perform a search across the query or the subqueries, you do not have to do this by hand with the SelectionField, you can use the ctx.look_ahead() to perform a selection

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+#[derive(SimpleObject)]
+struct Detail {
+    c: i32,
+    d: i32,
+}
+
+#[derive(SimpleObject)]
+struct MyObj {
+    a: i32,
+    b: i32,
+    detail: Detail,
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn obj(&self, ctx: &Context<'_>) -> MyObj {
+        if ctx.look_ahead().field("a").exists() {
+            // This is a query like `obj { a }`
+        } else if ctx.look_ahead().field("detail").field("c").exists() {
+            // This is a query like `obj { detail { c } }`
+        } else {
+            // This query doesn't have `a`
+        }
+        unimplemented!()
+    }
+}
+}
+

Error handling

+

Resolve can return a Result, which has the following definition:

+
type Result<T> = std::result::Result<T, Error>;
+

Any Error that implements std::fmt::Display can be converted to Error and you can extend the error message.

+

The following example shows how to parse an input string to an integer. When parsing fails, it will return an error and attach an error message. +See the Error Extensions section of this book for more details.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use std::num::ParseIntError;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn parse_with_extensions(&self, input: String) -> Result<i32> {
+        Ok("234a"
+            .parse()
+            .map_err(|err: ParseIntError| err.extend_with(|_, e| e.set("code", 400)))?)
+    }
+}
+}
+

Errors in subscriptions

+

Errors can be returned from subscription resolvers as well, using a return type of the form:

+
async fn my_subscription_resolver(&self) -> impl Stream<Item = Result<MyItem, MyError>> { ... }
+

Note however that the MyError struct must have Clone implemented, due to the restrictions placed by the Subscription macro. One way to accomplish this is by creating a custom error type, with #[derive(Clone)], as seen here.

+

Merging Objects

+

Usually we can create multiple implementations for the same type in Rust, but due to the limitation of procedural macros, we can not create multiple Object implementations for the same type. For example, the following code will fail to compile.

+
#[Object]
+impl Query {
+    async fn users(&self) -> Vec<User> {
+        todo!()
+    }
+}
+
+#[Object]
+impl Query {
+    async fn movies(&self) -> Vec<Movie> {
+        todo!()
+    }
+}
+

Instead, the #[derive(MergedObject)] macro allows you to split an object's resolvers across multiple modules or files by merging 2 or more #[Object] implementations into one.

+

Tip: Every #[Object] needs a unique name, even in a MergedObject, so make sure to give each object you're merging its own name.

+

Note: This works for queries and mutations. For subscriptions, see "Merging Subscriptions" below.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct User { a: i32 }
+#[derive(SimpleObject)]
+struct Movie { a: i32 }
+#[derive(Default)]
+struct UserQuery;
+
+#[Object]
+impl UserQuery {
+    async fn users(&self) -> Vec<User> {
+        todo!()
+    }
+}
+
+#[derive(Default)]
+struct MovieQuery;
+
+#[Object]
+impl MovieQuery {
+    async fn movies(&self) -> Vec<Movie> {
+        todo!()
+    }
+}
+
+#[derive(MergedObject, Default)]
+struct Query(UserQuery, MovieQuery);
+
+let schema = Schema::new(
+    Query::default(),
+    EmptyMutation,
+    EmptySubscription
+);
+}
+
+

⚠️ MergedObject cannot be used in Interface.

+
+

Merging Subscriptions

+

Along with MergedObject, you can derive MergedSubscription or use #[MergedSubscription] to merge separate #[Subscription] blocks.

+

Like merging Objects, each subscription block requires a unique name.

+

Example:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use futures_util::stream::{Stream};
+#[derive(Default,SimpleObject)]
+struct Query { a: i32 }
+#[derive(Default)]
+struct Subscription1;
+
+#[Subscription]
+impl Subscription1 {
+    async fn events1(&self) -> impl Stream<Item = i32> {
+        futures_util::stream::iter(0..10)
+    }
+}
+
+#[derive(Default)]
+struct Subscription2;
+
+#[Subscription]
+impl Subscription2 {
+    async fn events2(&self) -> impl Stream<Item = i32> {
+        futures_util::stream::iter(10..20)
+    }
+}
+
+#[derive(MergedSubscription, Default)]
+struct Subscription(Subscription1, Subscription2);
+
+let schema = Schema::new(
+    Query::default(),
+    EmptyMutation,
+    Subscription::default()
+);
+}
+

Derived fields

+

Sometimes two fields have the same query logic, but the output type is different. In async-graphql, you can create a derived field for it.

+

In the following example, you already have a date_rfc2822 field outputting the time format in RFC2822 format, and then reuse it to derive a new date_rfc3339 field.

+
#![allow(unused)]
+fn main() {
+extern crate chrono;
+use chrono::Utc;
+extern crate async_graphql;
+use async_graphql::*;
+struct DateRFC3339(chrono::DateTime<Utc>);
+struct DateRFC2822(chrono::DateTime<Utc>);
+
+#[Scalar]
+impl ScalarType for DateRFC3339 {
+  fn parse(value: Value) -> InputValueResult<Self> { todo!() } 
+
+  fn to_value(&self) -> Value {
+    Value::String(self.0.to_rfc3339())
+  }
+}
+
+#[Scalar]
+impl ScalarType for DateRFC2822 {
+  fn parse(value: Value) -> InputValueResult<Self> { todo!() } 
+
+  fn to_value(&self) -> Value {
+    Value::String(self.0.to_rfc2822())
+  }
+}
+
+impl From<DateRFC2822> for DateRFC3339 {
+    fn from(value: DateRFC2822) -> Self {
+      DateRFC3339(value.0)
+    }
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    #[graphql(derived(name = "date_rfc3339", into = "DateRFC3339"))]
+    async fn date_rfc2822(&self, arg: String) -> DateRFC2822 {
+        todo!()
+    }
+}
+}
+

It will render a GraphQL like:

+
type Query {
+	date_rfc2822(arg: String): DateRFC2822!
+	date_rfc3339(arg: String): DateRFC3339!
+}
+
+

Wrapper types

+

A derived field won't be able to manage everything easily: Rust's orphan rule requires that either the +trait or the type for which you are implementing the trait must be defined in the same crate as the impl, so the following code cannot be compiled:

+
impl From<Vec<U>> for Vec<T> {
+  ...
+}
+

So you wouldn't be able to generate derived fields for existing wrapper type structures like Vec or Option. But when you implement a From<U> for T you should be able to derived a From<Vec<U>> for Vec<T> and a From<Option<U>> for Option<T>. +We included a with parameter to help you define a function to call instead of using the Into trait implementation between wrapper structures.

+

Example

+
#![allow(unused)]
+fn main() {
+extern crate serde;
+use serde::{Serialize, Deserialize};
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Serialize, Deserialize, Clone)]
+struct ValueDerived(String);
+
+#[derive(Serialize, Deserialize, Clone)]
+struct ValueDerived2(String);
+
+scalar!(ValueDerived);
+scalar!(ValueDerived2);
+
+impl From<ValueDerived> for ValueDerived2 {
+    fn from(value: ValueDerived) -> Self {
+        ValueDerived2(value.0)
+    }
+}
+
+fn option_to_option<T, U: From<T>>(value: Option<T>) -> Option<U> {
+    value.map(|x| x.into())
+}
+
+#[derive(SimpleObject)]
+struct TestObj {
+    #[graphql(derived(owned, name = "value2", into = "Option<ValueDerived2>", with = "option_to_option"))]
+    pub value1: Option<ValueDerived>,
+}
+}
+

Enum

+

It's easy to define an Enum, here we have an example:

+

Async-graphql will automatically change the name of each item to GraphQL's CONSTANT_CASE convention. You can use name to rename.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+/// One of the films in the Star Wars Trilogy
+#[derive(Enum, Copy, Clone, Eq, PartialEq)]
+pub enum Episode {
+    /// Released in 1977.
+    NewHope,
+
+    /// Released in 1980.
+    Empire,
+
+    /// Released in 1983.
+    #[graphql(name="AAA")]
+    Jedi,
+}
+}
+

Wrapping a remote enum

+

Rust's orphan rule requires that either the +trait or the type for which you are implementing the trait must be defined in the same crate as the impl, so you cannot +expose remote enumeration types to GraphQL. In order to provide an Enum type, a common workaround is to create a new +enum that has parity with the existing, remote enum type.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+mod remote_crate { pub enum RemoteEnum { A, B, C } }
+use async_graphql::*;
+
+/// Provides parity with a remote enum type
+#[derive(Enum, Copy, Clone, Eq, PartialEq)]
+pub enum LocalEnum {
+    A,
+    B,
+    C,
+}
+
+/// Conversion interface from remote type to our local GraphQL enum type
+impl From<remote_crate::RemoteEnum> for LocalEnum {
+    fn from(e: remote_crate::RemoteEnum) -> Self {
+        match e {
+            remote_crate::RemoteEnum::A => Self::A,
+            remote_crate::RemoteEnum::B => Self::B,
+            remote_crate::RemoteEnum::C => Self::C,
+        }
+    }
+}
+}
+

The process is tedious and requires multiple steps to keep the local and remote enums in sync. Async_graphql provides a handy feature to generate the From<remote_crate::RemoteEnum> for LocalEnum as well as an opposite direction of From<LocalEnum> for remote_crate::RemoteEnum via an additional attribute after deriving Enum:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+mod remote_crate { pub enum RemoteEnum { A, B, C } }
+#[derive(Enum, Copy, Clone, Eq, PartialEq)]
+#[graphql(remote = "remote_crate::RemoteEnum")]
+enum LocalEnum {
+    A,
+    B,
+    C,
+}
+}
+

Interface

+

Interface is used to abstract Objects with common fields. +Async-graphql implements it as a wrapper. +The wrapper will forward field resolution to the Object that implements this Interface. +Therefore, the Object's fields' type and arguments must match with the Interface's.

+

Async-graphql implements auto conversion from Object to Interface, you only need to call Into::into.

+

Interface field names are transformed to camelCase for the schema definition. +If you need e.g. a snake_cased GraphQL field name, you can use both the name and method attributes.

+
    +
  • When name and method exist together, name is the GraphQL field name and the method is the resolver function name.
  • +
  • When only name exists, name.to_camel_case() is the GraphQL field name and the name is the resolver function name.
  • +
+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Circle {
+    radius: f32,
+}
+
+#[Object]
+impl Circle {
+    async fn area(&self) -> f32 {
+        std::f32::consts::PI * self.radius * self.radius
+    }
+
+    async fn scale(&self, s: f32) -> Shape {
+        Circle { radius: self.radius * s }.into()
+    }
+
+    #[graphql(name = "short_description")]
+    async fn short_description(&self) -> String {
+        "Circle".to_string()
+    }
+}
+
+struct Square {
+    width: f32,
+}
+
+#[Object]
+impl Square {
+    async fn area(&self) -> f32 {
+        self.width * self.width
+    }
+
+    async fn scale(&self, s: f32) -> Shape {
+        Square { width: self.width * s }.into()
+    }
+
+    #[graphql(name = "short_description")]
+    async fn short_description(&self) -> String {
+        "Square".to_string()
+    }
+}
+
+#[derive(Interface)]
+#[graphql(
+    field(name = "area", ty = "f32"),
+    field(name = "scale", ty = "Shape", arg(name = "s", ty = "f32")),
+    field(name = "short_description", method = "short_description", ty = "String")
+)]
+enum Shape {
+    Circle(Circle),
+    Square(Square),
+}
+}
+

Register the interface manually

+

Async-graphql traverses and registers all directly or indirectly referenced types from Schema in the initialization phase. +If an interface is not referenced, it will not exist in the registry, as in the following example , even if MyObject implements MyInterface, +because MyInterface is not referenced in Schema, the MyInterface type will not exist in the registry.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Interface)]
+#[graphql(
+    field(name = "name", ty = "String"),
+)]
+enum MyInterface {
+    MyObject(MyObject),
+}
+
+#[derive(SimpleObject)]
+struct MyObject {
+    name: String,
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn obj(&self) -> MyObject {
+        todo!()
+    }
+}
+
+type MySchema = Schema<Query, EmptyMutation, EmptySubscription>;
+}
+

You need to manually register the MyInterface type when constructing the Schema:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Interface)]
+#[graphql(field(name = "name", ty = "String"))]
+enum MyInterface { MyObject(MyObject) }
+#[derive(SimpleObject)]
+struct MyObject { name: String, }
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+
+Schema::build(Query, EmptyMutation, EmptySubscription)
+    .register_output_type::<MyInterface>()
+    .finish();
+}
+

Union

+

The definition of a Union is similar to an Interface, but with no fields allowed.. +The implementation is quite similar for Async-graphql; from Async-graphql's perspective, Union is a subset of Interface.

+

The following example modified the definition of Interface a little bit and removed fields.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Circle {
+    radius: f32,
+}
+
+#[Object]
+impl Circle {
+    async fn area(&self) -> f32 {
+        std::f32::consts::PI * self.radius * self.radius
+    }
+
+    async fn scale(&self, s: f32) -> Shape {
+        Circle { radius: self.radius * s }.into()
+    }
+}
+
+struct Square {
+    width: f32,
+}
+
+#[Object]
+impl Square {
+    async fn area(&self) -> f32 {
+        self.width * self.width
+    }
+
+    async fn scale(&self, s: f32) -> Shape {
+        Square { width: self.width * s }.into()
+    }
+}
+
+#[derive(Union)]
+enum Shape {
+    Circle(Circle),
+    Square(Square),
+}
+}
+

Flattening nested unions

+

A restriction in GraphQL is the inability to create a union type out of +other union types. All members must be Object. To support nested +unions, we can "flatten" members that are unions, bringing their members up +into the parent union. This is done by applying #[graphql(flatten)] on each +member we want to flatten.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+#[derive(async_graphql::Union)]
+pub enum TopLevelUnion {
+    A(A),
+
+    // Will fail to compile unless we flatten the union member
+    #[graphql(flatten)]
+    B(B),
+}
+
+#[derive(async_graphql::SimpleObject)]
+pub struct A {
+    a: i32,
+    // ...
+}
+
+#[derive(async_graphql::Union)]
+pub enum B {
+    C(C),
+    D(D),
+}
+
+#[derive(async_graphql::SimpleObject)]
+pub struct C {
+    c: i32,
+    // ...
+}
+
+#[derive(async_graphql::SimpleObject)]
+pub struct D {
+    d: i32,
+    // ...
+}
+}
+

The above example transforms the top-level union into this equivalent:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+#[derive(async_graphql::SimpleObject)]
+struct A { a: i32 }
+#[derive(async_graphql::SimpleObject)]
+struct C { c: i32 }
+#[derive(async_graphql::SimpleObject)]
+struct D { d: i32 }
+#[derive(async_graphql::Union)]
+pub enum TopLevelUnion {
+    A(A),
+    C(C),
+    D(D),
+}
+}
+

InputObject

+

You can use an Object as an argument, and GraphQL calls it an InputObject.

+

The definition of InputObject is similar to SimpleObject, but +SimpleObject can only be used as output and InputObject can only be used as input.

+

You can add optional #[graphql] attributes to add descriptions or rename the field.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+#[derive(SimpleObject)]
+struct User { a: i32 }
+use async_graphql::*;
+
+#[derive(InputObject)]
+struct Coordinate {
+    latitude: f64,
+    longitude: f64
+}
+
+struct Mutation;
+
+#[Object]
+impl Mutation {
+    async fn users_at_location(&self, coordinate: Coordinate, radius: f64) -> Vec<User> {
+        // Writes coordination to database.
+        // ...
+      todo!()
+    }
+}
+}
+

Generic InputObjects

+

If you want to reuse an InputObject for other types, you can define a generic InputObject +and specify how its concrete types should be implemented.

+

In the following example, two InputObject types are created:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(InputObject)]
+struct SomeType { a: i32 }
+#[derive(InputObject)]
+struct SomeOtherType { a: i32 }
+#[derive(InputObject)]
+#[graphql(concrete(name = "SomeName", params(SomeType)))]
+#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))]
+pub struct SomeGenericInput<T: InputType> {
+    field1: Option<T>,
+    field2: String
+}
+}
+

Note: Each generic parameter must implement InputType, as shown above.

+

The schema generated is:

+
input SomeName {
+  field1: SomeType
+  field2: String!
+}
+
+input SomeOtherName {
+  field1: SomeOtherType
+  field2: String!
+}
+
+

In your resolver method or field of another input object, use as a normal generic type:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(InputObject)]
+struct SomeType { a: i32 }
+#[derive(InputObject)]
+struct SomeOtherType { a: i32 }
+#[derive(InputObject)]
+#[graphql(concrete(name = "SomeName", params(SomeType)))]
+#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))]
+pub struct SomeGenericInput<T: InputType> {
+    field1: Option<T>,
+    field2: String
+}
+#[derive(InputObject)]
+pub struct YetAnotherInput {
+    a: SomeGenericInput<SomeType>,
+    b: SomeGenericInput<SomeOtherType>,
+}
+}
+

You can pass multiple generic types to params(), separated by a comma.

+

If you also want to implement OutputType, then you will need to explicitly declare the input and output type names of the concrete types like so:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject, InputObject)]
+#[graphql(concrete(
+    name = "SomeGenericTypeOut",
+    input_name = "SomeGenericTypeIn",
+    params(i32),
+))]
+pub struct SomeGenericType<T: InputType + OutputType> {
+    field1: Option<T>,
+    field2: String
+}
+}
+

Redacting sensitive data

+

If any part of your input is considered sensitive and you wish to redact it, you can mark it with secret directive. For example:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(InputObject)]
+pub struct CredentialsInput {
+    username: String,
+    #[graphql(secret)]
+    password: String,
+}
+}
+

Flattening fields

+

You can add #[graphql(flatten)] to a field to inline keys from the field type into it's parent. For example:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(InputObject)]
+pub struct ChildInput {
+    b: String,
+    c: String,
+}
+
+#[derive(InputObject)]
+pub struct ParentInput {
+    a: String,
+    #[graphql(flatten)]
+    child: ChildInput,
+}
+
+// Is the same as
+
+#[derive(InputObject)]
+pub struct Input {
+    a: String,
+    b: String,
+    c: String,
+}
+}
+

OneofObject

+

A OneofObject is a special type of InputObject, in which only one of its fields must be set and is not-null. +It is especially useful when you want a user to be able to choose between several potential input types.

+

This feature is still an RFC and therefore not yet officially part of the GraphQL spec, but Async-graphql already supports it!

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+#[derive(SimpleObject)]
+struct User { a: i32 }
+use async_graphql::*;
+
+#[derive(OneofObject)]
+enum UserBy {
+    Email(String),
+    RegistrationNumber(i64),
+    Address(Address)
+}
+
+#[derive(InputObject)]
+struct Address {
+    street: String,
+    house_number: String,
+    city: String,
+    zip: String,
+}
+
+struct Query {}
+
+#[Object]
+impl Query {
+    async fn search_users(&self, by: Vec<UserBy>) -> Vec<User> {
+        // ... Searches and returns a list of users ...
+        todo!()
+    }
+}
+}
+

As you can see, a OneofObject is represented by an enum in which each variant contains another InputType. This means that you can use InputObject as variant too.

+

Default value

+

You can define default values for input value types. +Below are some examples.

+

Object field

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+fn my_default() -> i32 {
+    30
+}
+
+#[Object]
+impl Query {
+    // The default value of the value parameter is 0, it will call i32::default()
+    async fn test1(&self, #[graphql(default)] value: i32) -> i32 { todo!() }
+
+    // The default value of the value parameter is 10
+    async fn test2(&self, #[graphql(default = 10)] value: i32) -> i32 { todo!() }
+    
+    // The default value of the value parameter uses the return result of the my_default function, the value is 30.
+    async fn test3(&self, #[graphql(default_with = "my_default()")] value: i32) -> i32 { todo!() }
+}
+}
+

Interface field

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+fn my_default() -> i32 { 5 }
+struct MyObj;
+#[Object]
+impl MyObj {
+   async fn test1(&self, value: i32) -> i32 { todo!() }
+   async fn test2(&self, value: i32) -> i32 { todo!() }
+   async fn test3(&self, value: i32) -> i32 { todo!() }
+}
+use async_graphql::*;
+
+#[derive(Interface)]
+#[graphql(
+    field(name = "test1", ty = "i32", arg(name = "value", ty = "i32", default)),
+    field(name = "test2", ty = "i32", arg(name = "value", ty = "i32", default = 10)),
+    field(name = "test3", ty = "i32", arg(name = "value", ty = "i32", default_with = "my_default()")),
+)]
+enum MyInterface {
+    MyObj(MyObj),
+}
+}
+

Input object field

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+fn my_default() -> i32 { 5 }
+use async_graphql::*;
+
+#[derive(InputObject)]
+struct MyInputObject {
+    #[graphql(default)]
+    value1: i32,
+
+    #[graphql(default = 10)]
+    value2: i32,
+
+    #[graphql(default_with = "my_default()")]
+    value3: i32,
+}
+}
+

Generics

+

It is possible to define reusable objects using generics; however +each concrete instantiation of a generic object must be given a unique GraphQL type name. +There are two ways of specifying these concrete names: concrete instantiation and the TypeName trait.

+

Concrete Instantiation

+

In the following example, two SimpleObject types are created:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct SomeType { a: i32 }
+#[derive(SimpleObject)]
+struct SomeOtherType { a: i32 }
+#[derive(SimpleObject)]
+#[graphql(concrete(name = "SomeName", params(SomeType)))]
+#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))]
+pub struct SomeGenericObject<T: OutputType> {
+    field1: Option<T>,
+    field2: String
+}
+}
+

Note: Each generic parameter must implement OutputType, as shown above.

+

The schema generated is:

+
# SomeGenericObject<SomeType>
+type SomeName {
+  field1: SomeType
+  field2: String!
+}
+
+# SomeGenericObject<SomeOtherType>
+type SomeOtherName {
+  field1: SomeOtherType
+  field2: String!
+}
+
+

In your resolver method or field of another object, use as a normal generic type:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct SomeType { a: i32 }
+#[derive(SimpleObject)]
+struct SomeOtherType { a: i32 }
+#[derive(SimpleObject)]
+#[graphql(concrete(name = "SomeName", params(SomeType)))]
+#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))]
+pub struct SomeGenericObject<T: OutputType> {
+    field1: Option<T>,
+    field2: String,
+}
+#[derive(SimpleObject)]
+pub struct YetAnotherObject {
+    a: SomeGenericObject<SomeType>,
+    b: SomeGenericObject<SomeOtherType>,
+}
+}
+

You can pass multiple generic types to params(), separated by a comma.

+

TypeName trait

+

Some type names can be derived.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use std::borrow::Cow;
+#[derive(SimpleObject)]
+#[graphql(name_type)] // Use `TypeName` trait
+struct Bag<T: OutputType> {
+    content: Vec<T>,
+    len: usize,
+}
+
+impl<T: OutputType> TypeName for Bag<T> {
+    fn type_name() -> Cow<'static, str> {
+        format!("{}Bag", <T as OutputType>::type_name()).into()
+    }
+}
+}
+

Using bool and String the generated schema is:

+
# Bag<bool>
+type BooleanBag {
+    content: [Boolean!]!
+    len: Int!
+}
+
+# Bag<String>
+type StringBag {
+    content: [String!]!
+    len: Int!
+}
+
+

Schema

+

After defining the basic types, you need to define a schema to combine them. The schema consists of three types: a query object, a mutation object, and a subscription object, where the mutation object and subscription object are optional.

+

When the schema is created, Async-graphql will traverse all object graphs and register all types. This means that if a GraphQL object is defined but never referenced, this object will not be exposed in the schema.

+

Query and Mutation

+

Query root object

+

The query root object is a GraphQL object with a definition similar to other objects. Resolver functions for all fields of the query object are executed concurrently.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct User { a: i32 }
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn user(&self, username: String) -> Result<Option<User>> {
+        // Look up users from the database
+       todo!()
+    }
+}
+
+}
+

Mutation root object

+

The mutation root object is also a GraphQL object, but it executes sequentially. One mutation following from another will only be executed only after the first mutation is completed.

+

The following mutation root object provides an example of user registration and login:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Mutation;
+
+#[Object]
+impl Mutation {
+    async fn signup(&self, username: String, password: String) -> Result<bool> {
+        // User signup
+       todo!()
+    }
+
+    async fn login(&self, username: String, password: String) -> Result<String> {
+        // User login (generate token)
+       todo!()
+    }
+}
+}
+

Subscription

+

The definition of the subscription root object is slightly different from other root objects. Its resolver function always returns a Stream or Result<Stream>, and the field parameters are usually used as data filtering conditions.

+

The following example subscribes to an integer stream, which generates one integer per second. The parameter step specifies the integer step size with a default of 1.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use std::time::Duration;
+use async_graphql::futures_util::stream::Stream;
+use async_graphql::futures_util::StreamExt;
+extern crate tokio_stream;
+extern crate tokio;
+use async_graphql::*;
+
+struct Subscription;
+
+#[Subscription]
+impl Subscription {
+    async fn integers(&self, #[graphql(default = 1)] step: i32) -> impl Stream<Item = i32> {
+        let mut value = 0;
+        tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(Duration::from_secs(1)))
+            .map(move |_| {
+                value += step;
+                value
+            })
+    }
+}
+}
+

SDL Export

+

You can export your schema in Schema Definition Language (SDL) by using the Schema::sdl() method.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn add(&self, u: i32, v: i32) -> i32 {
+        u + v
+    }
+}
+
+let schema = Schema::build(Query, EmptyMutation, EmptySubscription).finish();
+    
+// Print the schema in SDL format
+println!("{}", &schema.sdl());
+}
+

Utilities

+

Field Guard

+

You can define a guard for the fields of Object, SimpleObject, ComplexObject and Subscription, it will be executed before calling the resolver function, and an error will be returned if it fails.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Eq, PartialEq, Copy, Clone)]
+enum Role {
+    Admin,
+    Guest,
+}
+
+struct RoleGuard {
+    role: Role,
+}
+
+impl RoleGuard {
+    fn new(role: Role) -> Self {
+        Self { role }
+    }
+}
+
+impl Guard for RoleGuard {
+    async fn check(&self, ctx: &Context<'_>) -> Result<()> {
+        if ctx.data_opt::<Role>() == Some(&self.role) {
+            Ok(())
+        } else {
+            Err("Forbidden".into())
+        }
+    }
+}
+}
+

Use it with the guard attribute:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Eq, PartialEq, Copy, Clone)]
+enum Role { Admin, Guest, }
+struct RoleGuard { role: Role, }
+impl RoleGuard { fn new(role: Role) -> Self { Self { role } } }
+impl Guard for RoleGuard { async fn check(&self, ctx: &Context<'_>) -> Result<()> { todo!() } }
+#[derive(SimpleObject)]
+struct Query {
+    /// Only allow Admin
+    #[graphql(guard = "RoleGuard::new(Role::Admin)")]
+    value1: i32,
+    /// Allow Admin or Guest
+    #[graphql(guard = "RoleGuard::new(Role::Admin).or(RoleGuard::new(Role::Guest))")]
+    value2: i32,
+}
+}
+

Use parameter value

+

Sometimes guards need to use field parameters, you need to pass the parameter value when creating the guard like this:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct EqGuard {
+    expect: i32,
+    actual: i32,
+}
+
+impl EqGuard {
+    fn new(expect: i32, actual: i32) -> Self {
+        Self { expect, actual }
+    }
+}
+
+impl Guard for EqGuard {
+    async fn check(&self, _ctx: &Context<'_>) -> Result<()> {
+        if self.expect != self.actual {
+            Err("Forbidden".into())
+        } else {
+            Ok(())
+        }
+    }
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    #[graphql(guard = "EqGuard::new(100, value)")]
+    async fn get(&self, value: i32) -> i32 {
+        value
+    }
+}
+}
+

Input value validators

+

Async-graphql has some common validators built-in, you can use them on the parameters of object fields or on the fields of InputObject.

+
    +
  • maximum=N the number cannot be greater than N.
  • +
  • minimum=N the number cannot be less than N.
  • +
  • multiple_of=N the number must be a multiple of N.
  • +
  • max_items=N the length of the list cannot be greater than N.
  • +
  • min_items=N the length of the list cannot be less than N.
  • +
  • max_length=N the length of the string cannot be greater than N.
  • +
  • min_length=N the length of the string cannot be less than N.
  • +
  • chars_max_length=N the count of the unicode chars cannot be greater than N.
  • +
  • chars_min_length=N the count of the unicode chars cannot be less than N.
  • +
  • email is valid email.
  • +
  • url is valid url.
  • +
  • ip is valid ip address.
  • +
  • regex=RE is match for the regex.
  • +
  • uuid=V the string or ID is a valid UUID with version V. You may omit V to accept any UUID version.
  • +
+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    /// The length of the name must be greater than or equal to 5 and less than or equal to 10.
+    async fn input(&self, #[graphql(validator(min_length = 5, max_length = 10))] name: String) -> Result<i32> {
+        todo!()
+    }
+}
+}
+

Check every member of the list

+

You can enable the list attribute, and the validator will check all members in list:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn input(&self, #[graphql(validator(list, max_length = 10))] names: Vec<String>) -> Result<i32> {
+       todo!()
+    }
+}
+}
+

Custom validator

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct MyValidator {
+    expect: i32,
+}
+
+impl MyValidator {
+    pub fn new(n: i32) -> Self {
+        MyValidator { expect: n }
+    }
+}
+
+impl CustomValidator<i32> for MyValidator {
+    fn check(&self, value: &i32) -> Result<(), InputValueError<i32>> {
+        if *value == self.expect {
+            Ok(())
+        } else {
+            Err(InputValueError::custom(format!("expect 100, actual {}", value)))
+        }
+    }
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    /// n must be equal to 100
+    async fn value(
+        &self,
+        #[graphql(validator(custom = "MyValidator::new(100)"))] n: i32,
+    ) -> i32 {
+        n
+    }
+}
+}
+

Cache control

+

Production environments often rely on caching to improve performance.

+

A GraphQL query will call multiple resolver functions and each resolver can have a different cache definition. Some may cache for a few seconds, some may cache for a few hours, some may be the same for all users, and some may be different for each session.

+

Async-graphql provides a mechanism that allows you to define the cache time and scope for each resolver.

+

You can define cache parameters on the object or on its fields. The following example shows two uses of cache control parameters.

+

You can use max_age parameters to control the age of the cache (in seconds), and you can also use public and private to control the scope of the cache. When you do not specify it, the scope will default to public.

+

when querying multiple resolvers, the results of all cache control parameters will be combined and the max_age minimum value will be taken. If the scope of any object or field is private, the result will be private.

+

We can use QueryResponse to get a merged cache control result from a query result, and call CacheControl::value to get the corresponding HTTP header.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object(cache_control(max_age = 60))]
+impl Query {
+    #[graphql(cache_control(max_age = 30))]
+    async fn value1(&self) -> i32 {
+        1
+    }
+
+    #[graphql(cache_control(private))]
+    async fn value2(&self) -> i32 {
+        2
+    }
+
+    async fn value3(&self) -> i32 {
+        3
+    }
+}
+}
+

The following are different queries corresponding to different cache control results:

+
# max_age=30
+{ value1 }
+
+
# max_age=30, private
+{ value1 value2 }
+
+
# max_age=60
+{ value3 }
+
+

Cursor connections

+

Relay's cursor connection specification is designed to provide a consistent method for query paging. For more details on the specification see the GraphQL Cursor Connections Specification

+

Defining a cursor connection in async-graphql is very simple, you just call the connection::query function and query data in the closure.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::types::connection::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn numbers(&self,
+        after: Option<String>,
+        before: Option<String>,
+        first: Option<i32>,
+        last: Option<i32>,
+    ) -> Result<Connection<usize, i32, EmptyFields, EmptyFields>> {
+        query(after, before, first, last, |after, before, first, last| async move {
+            let mut start = after.map(|after| after + 1).unwrap_or(0);
+            let mut end = before.unwrap_or(10000);
+            if let Some(first) = first {
+                end = (start + first).min(end);
+            }
+            if let Some(last) = last {
+                start = if last > end - start {
+                     end
+                } else {
+                    end - last
+                };
+            }
+            let mut connection = Connection::new(start > 0, end < 10000);
+            connection.edges.extend(
+                (start..end).into_iter().map(|n|
+                    Edge::with_additional_fields(n, n as i32, EmptyFields)
+            ));
+            Ok::<_, async_graphql::Error>(connection)
+        }).await
+    }
+}
+
+}
+

Error extensions

+

To quote the graphql-spec:

+
+

GraphQL services may provide an additional entry to errors with key extensions. +This entry, if set, must have a map as its value. This entry is reserved for implementer to add +additional information to errors however they see fit, and there are no additional restrictions on +its contents.

+
+

Example

+

I would recommend on checking out this async-graphql example as a quickstart.

+

General Concept

+

In async-graphql all user-facing errors are cast to the Error type which by default provides +the error message exposed by std::fmt::Display. However, Error actually provides an additional information that can extend the error.

+

A resolver looks like this:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query {
+async fn parse_with_extensions(&self) -> Result<i32, Error> {
+    Err(Error::new("MyMessage").extend_with(|_, e| e.set("details", "CAN_NOT_FETCH")))
+}
+}
+}
+

may then return a response like this:

+
{
+  "errors": [
+    {
+      "message": "MyMessage",
+      "locations": [ ... ],
+      "path": [ ... ],
+      "extensions": {
+        "details": "CAN_NOT_FETCH",
+      }
+    }
+  ]
+}
+
+

ErrorExtensions

+

Constructing new Errors by hand quickly becomes tedious. That is why async-graphql provides +two convenience traits for casting your errors to the appropriate Error with +extensions.

+

The easiest way to provide extensions to any error is by calling extend_with on the error. +This will on the fly convert any error into a Error with the given extension.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+use std::num::ParseIntError;
+#[Object]
+impl Query {
+async fn parse_with_extensions(&self) -> Result<i32> {
+     Ok("234a"
+         .parse()
+         .map_err(|err: ParseIntError| err.extend_with(|_err, e| e.set("code", 404)))?)
+}
+}
+}
+

Implementing ErrorExtensions for custom errors.

+

If you find yourself attaching extensions to your errors all over the place you might want to consider +implementing the trait on your custom error type directly.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate thiserror;
+use async_graphql::*;
+#[derive(Debug, thiserror::Error)]
+pub enum MyError {
+    #[error("Could not find resource")]
+    NotFound,
+
+    #[error("ServerError")]
+    ServerError(String),
+
+    #[error("No Extensions")]
+    ErrorWithoutExtensions,
+}
+
+impl ErrorExtensions for MyError {
+    // lets define our base extensions
+    fn extend(&self) -> Error {
+        Error::new(format!("{}", self)).extend_with(|err, e| 
+            match self {
+              MyError::NotFound => e.set("code", "NOT_FOUND"),
+              MyError::ServerError(reason) => e.set("reason", reason.clone()),
+              MyError::ErrorWithoutExtensions => {}
+          })
+    }
+}
+}
+

This way you only need to call extend on your error to deliver the error message alongside the provided extensions. +Or further extend your error through extend_with.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate thiserror;
+use async_graphql::*;
+#[derive(Debug, thiserror::Error)]
+pub enum MyError {
+    #[error("Could not find resource")]
+    NotFound,
+
+    #[error("ServerError")]
+    ServerError(String),
+
+    #[error("No Extensions")]
+    ErrorWithoutExtensions,
+}
+struct Query;
+#[Object]
+impl Query {
+async fn parse_with_extensions_result(&self) -> Result<i32> {
+    // Err(MyError::NotFound.extend())
+    // OR
+    Err(MyError::NotFound.extend_with(|_, e| e.set("on_the_fly", "some_more_info")))
+}
+}
+}
+
{
+  "errors": [
+    {
+      "message": "NotFound",
+      "locations": [ ... ],
+      "path": [ ... ],
+      "extensions": {
+        "code": "NOT_FOUND",
+        "on_the_fly": "some_more_info"
+      }
+    }
+  ]
+}
+
+

ResultExt

+

This trait enables you to call extend_err directly on results. So the above code becomes less verbose.

+
// @todo figure out why this example does not compile!
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query {
+async fn parse_with_extensions(&self) -> Result<i32> {
+     Ok("234a"
+         .parse()
+         .extend_err(|_, e| e.set("code", 404))?)
+}
+}
+

Chained extensions

+

Since ErrorExtensions and ResultExt are implemented for any type &E where E: std::fmt::Display +we can chain the extension together.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query {
+async fn parse_with_extensions(&self) -> Result<i32> {
+    match "234a".parse() {
+        Ok(n) => Ok(n),
+        Err(e) => Err(e
+            .extend_with(|_, e| e.set("code", 404))
+            .extend_with(|_, e| e.set("details", "some more info.."))
+            // keys may also overwrite previous keys...
+            .extend_with(|_, e| e.set("code", 500))),
+    }
+}
+}
+}
+

Expected response:

+
{
+  "errors": [
+    {
+      "message": "MyMessage",
+      "locations": [ ... ],
+      "path": [ ... ],
+      "extensions": {
+      	"details": "some more info...",
+        "code": 500,
+      }
+    }
+  ]
+}
+
+

Pitfalls

+

Rust does not provide stable trait specialization yet. +That is why ErrorExtensions is actually implemented for &E where E: std::fmt::Display +instead of E: std::fmt::Display. Some specialization is provided through +Autoref-based stable specialization. +The disadvantage is that the below code does NOT compile:

+
async fn parse_with_extensions_result(&self) -> Result<i32> {
+    // the trait `error::ErrorExtensions` is not implemented
+    // for `std::num::ParseIntError`
+    "234a".parse().extend_err(|_, e| e.set("code", 404))
+}
+

however this does:

+
async fn parse_with_extensions_result(&self) -> Result<i32> {
+    // does work because ErrorExtensions is implemented for &ParseIntError
+    "234a"
+      .parse()
+      .map_err(|ref e: ParseIntError| e.extend_with(|_, e| e.set("code", 404)))
+}
+

Apollo Tracing

+

Apollo Tracing provides performance analysis results for each step of query. This is an extension to Schema, and the performance analysis results are stored in QueryResponse.

+

To enable the Apollo Tracing extension, add the extension when the Schema is created.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::ApolloTracing;
+
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+
+let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
+    .extension(ApolloTracing) // Enable ApolloTracing extension
+    .finish();
+}
+

Query complexity and depth

+

⚠️GraphQL provides a powerful way to query your data, but putting great +power in the hands of your API clients also exposes you to a risk of denial +of service attacks. You can mitigate that risk with Async-graphql by limiting the +complexity and depth of the queries you allow.

+

Expensive Queries

+

Consider a schema that allows listing blog posts. Each blog post is also related to other posts.

+
type Query {
+	posts(count: Int = 10): [Post!]!
+}
+
+type Post {
+	title: String!
+	text: String!
+	related(count: Int = 10): [Post!]!
+}
+
+

It's not too hard to craft a query that will cause a very large response:

+
{
+    posts(count: 100) {
+        related(count: 100) {
+            related(count: 100) {
+                related(count: 100) {
+                    title
+                }
+            }
+        }
+    }
+}
+
+

The size of the response increases exponentially with every other level of the related field. Fortunately, Async-graphql provides +a way to prevent such queries.

+

Limiting Query depth

+

The depth is the number of nesting levels of the field, and the following is a query with a depth of 3.

+
{
+    a {
+        b {
+            c
+        }
+    }
+}
+
+

You can limit the depth when creating Schema. If the query exceeds this limit, an error will occur and the +message Query is nested too deep will be returned.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
+    .limit_depth(5) // Limit the maximum depth to 5
+    .finish();
+}
+

Limiting Query complexity

+

The complexity is the number of fields in the query. The default complexity of each field is 1. Below is a +query with a complexity of 6.

+
{
+    a b c {
+        d {
+            e f
+        }
+    }
+}
+
+

You can limit the complexity when creating the Schema. If the query exceeds this limit, an error will occur +and Query is too complex will be returned.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
+    .limit_complexity(5) // Limit the maximum complexity to 5
+    .finish();
+}
+

Custom Complexity Calculation

+

There are two ways to customize the complexity for non-list type and list type fields.

+

In the following code, the complexity of the value field is 5. The complexity of the values field is count * child_complexity, +child_complexity is a special variable that represents the complexity of the subquery, and count is the parameter of the field, +used to calculate the complexity of the values field, and the type of the return value must be usize.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+
+#[Object]
+impl Query {
+    #[graphql(complexity = 5)]
+    async fn value(&self) -> i32 {
+        todo!()
+    }
+
+    #[graphql(complexity = "count * child_complexity")]
+    async fn values(&self, count: usize) -> i32 {
+        todo!()
+    }
+}
+}
+

Note: The complexity calculation is done in the validation phase and not the execution phase, +so you don't have to worry about partial execution of over-limit queries.

+

Hide content in introspection

+

By default, all types and fields are visible in introspection. But maybe you want to hide some content according to different users to avoid unnecessary misunderstandings. You can add the visible attribute to the type or field to do it.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+#[derive(SimpleObject)]
+struct MyObj {
+    // This field will be visible in introspection.
+    a: i32,
+
+    // This field is always hidden in introspection.
+    #[graphql(visible = false)]
+    b: i32,
+
+    // This field calls the `is_admin` function, which 
+    // is visible if the return value is `true`.
+    #[graphql(visible = "is_admin")]
+    c: i32,
+}
+
+#[derive(Enum, Copy, Clone, Eq, PartialEq)]
+enum MyEnum {
+    // This item will be visible in introspection.
+    A,
+
+    // This item is always hidden in introspection.
+    #[graphql(visible = false)]
+    B,
+
+    // This item calls the `is_admin` function, which 
+    // is visible if the return value is `true`.
+    #[graphql(visible = "is_admin")]
+    C,
+}
+
+struct IsAdmin(bool);
+
+fn is_admin(ctx: &Context<'_>) -> bool {
+    ctx.data_unchecked::<IsAdmin>().0
+}
+
+}
+

Extensions

+

async-graphql has the capability to be extended with extensions without having to modify the original source code. A lot of features can be added this way, and a lot of extensions already exist.

+

How extensions are defined

+

An async-graphql extension is defined by implementing the trait Extension associated. The Extension trait allows you to insert custom code to some several steps used to respond to GraphQL's queries through async-graphql. With Extensions, your application can hook into the GraphQL's requests lifecycle to add behaviors about incoming requests or outgoing response.

+

Extensions are a lot like middleware from other frameworks, be careful when using those: when you use an extension it'll be run for every GraphQL request.

+

Across every step, you'll have the ExtensionContext supplied with data about your current request execution. Feel free to check how it's constructed in the code, documentation about it will soon come.

+

A word about middleware

+

For those who don't know, let's dig deeper into what is a middleware:

+
async fn middleware(&self, ctx: &ExtensionContext<'_>, next: NextMiddleware<'_>) -> MiddlewareResult {
+  // Logic to your middleware.
+
+  /*
+   * Final step to your middleware, we call the next function which will trigger
+   * the execution of the next middleware. It's like a `callback` in JavaScript.
+   */
+  next.run(ctx).await
+}
+

As you have seen, a Middleware is only a function calling the next function at the end, but we could also do a middleware with the next.run function at the start. This is where it's becoming tricky: depending on where you put your logic and where is the next.run call, your logic won't have the same execution order.

+

Depending on your logic code, you'll want to process it before or after the next.run call. If you need more information about middlewares, there are a lot of things on the web.

+

Processing of a query

+

There are several steps to go to process a query to completion, you'll be able to create extension based on these hooks.

+

request

+

First, when we receive a request, if it's not a subscription, the first function to be called will be request, it's the first step, it's the function called at the incoming request, and it's also the function which will output the response to the user.

+

Default implementation for request:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
+    next.run(ctx).await
+}
+}
+}
+

Depending on where you put your logic code, it'll be executed at the beginning or at the end of the query being processed.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
+    // The code here will be run before the prepare_request is executed.
+    let result = next.run(ctx).await;
+    // The code after the completion of this future will be after the processing, just before sending the result to the user.
+    result
+}
+}
+}
+

prepare_request

+

Just after the request, we will have the prepare_request lifecycle, which will be hooked.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+async fn prepare_request(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    request: Request,
+    next: NextPrepareRequest<'_>,
+) -> ServerResult<Request> {
+    // The code here will be run before the prepare_request is executed, just after the request lifecycle hook.
+    let result = next.run(ctx, request).await;
+    // The code here will be run just after the prepare_request
+    result
+}
+}
+}
+

parse_query

+

The parse_query will create a GraphQL ExecutableDocument on your query, it'll check if the query is valid for the GraphQL Spec. Usually the implemented spec in async-graphql tends to be the last stable one (October2021).

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+use async_graphql::parser::types::ExecutableDocument;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+/// Called at parse query.
+async fn parse_query(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    // The raw query
+    query: &str,
+    // The variables
+    variables: &Variables,
+    next: NextParseQuery<'_>,
+) -> ServerResult<ExecutableDocument> {
+    next.run(ctx, query, variables).await
+}
+}
+}
+

validation

+

The validation step will check (depending on your validation_mode) rules the query should abide to and give the client data about why the query is not valid.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+/// Called at validation query.
+async fn validation(
+  &self,
+  ctx: &ExtensionContext<'_>,
+  next: NextValidation<'_>,
+) -> Result<ValidationResult, Vec<ServerError>> {
+  next.run(ctx).await
+}
+}
+}
+

execute

+

The execution step is a huge one, it'll start the execution of the query by calling each resolver concurrently for a Query and serially for a Mutation.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+/// Called at execute query.
+async fn execute(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    operation_name: Option<&str>,
+    next: NextExecute<'_>,
+) -> Response {
+    // Before starting resolving the whole query
+    let result = next.run(ctx, operation_name).await;
+    // After resolving the whole query
+    result
+}
+}
+}
+

resolve

+

The resolve step is launched for each field.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware { 
+/// Called at resolve field.
+async fn resolve(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    info: ResolveInfo<'_>,
+    next: NextResolve<'_>,
+) -> ServerResult<Option<Value>> {
+    // Logic before resolving the field
+    let result = next.run(ctx, info).await;
+    // Logic after resolving the field
+    result
+}
+}
+}
+

subscribe

+

The subscribe lifecycle has the same behavior as the request but for a Subscription.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+use futures_util::stream::BoxStream;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+/// Called at subscribe request.
+fn subscribe<'s>(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    stream: BoxStream<'s, Response>,
+    next: NextSubscribe<'_>,
+) -> BoxStream<'s, Response> {
+    next.run(ctx, stream)
+}
+}
+}
+

Extensions available

+

There are a lot of available extensions in the async-graphql to empower your GraphQL Server, some of these documentations are documented here.

+

Analyzer

+

Available in the repository

+

The analyzer extension will output a field containing complexity and depth in the response extension field of each query.

+

Apollo Persisted Queries

+

Available in the repository

+

To improve network performance for large queries, you can enable this Persisted Queries extension. With this extension enabled, each unique query is associated to a unique identifier, so clients can send this identifier instead of the corresponding query string to reduce requests sizes.

+

This extension doesn't force you to use some cache strategy, you can choose the caching strategy you want, you'll just have to implement the CacheStorage trait:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[async_trait::async_trait]
+pub trait CacheStorage: Send + Sync + Clone + 'static {
+    /// Load the query by `key`.
+    async fn get(&self, key: String) -> Option<String>;
+    /// Save the query by `key`.
+    async fn set(&self, key: String, query: String);
+}
+}
+

References: Apollo doc - Persisted Queries

+

Apollo Tracing

+

Available in the repository

+

Apollo Tracing is an extension which includes analytics data for your queries. This extension works to follow the old and now deprecated Apollo Tracing Spec. If you want to check the newer Apollo Reporting Protocol, it's implemented by async-graphql Apollo studio extension for Apollo Studio.

+

Apollo Studio

+

Available at async-graphql/async_graphql_apollo_studio_extension

+

Apollo Studio is a cloud platform that helps you build, validate, and secure your organization's graph (description from the official documentation). It's a service allowing you to monitor & work with your team around your GraphQL Schema. async-graphql provides an extension implementing the official Apollo Specification available at async-graphql-extension-apollo-tracing and Crates.io.

+

Logger

+

Available in the repository

+

Logger is a simple extension allowing you to add some logging feature to async-graphql. It's also a good example to learn how to create your own extension.

+

OpenTelemetry

+

Available in the repository

+

OpenTelemetry is an extension providing an integration with the opentelemetry crate to allow your application to capture distributed traces and metrics from async-graphql.

+

Tracing

+

Available in the repository

+

Tracing is a simple extension allowing you to add some tracing feature to async-graphql. A little like the Logger extension.

+

Integrations

+

Async-graphql supports several common Rust web servers.

+ +

Even if the server you are currently using is not in the above list, it is quite simple to implement similar functionality yourself.

+

Poem

+

Request example

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_poem;
+extern crate async_graphql;
+extern crate poem;
+use async_graphql::*;
+#[derive(Default, SimpleObject)]
+struct Query { a: i32 }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+use poem::Route;
+use async_graphql_poem::GraphQL;
+
+let app = Route::new()
+    .at("/ws", GraphQL::new(schema));
+}
+

Subscription example

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_poem;
+extern crate async_graphql;
+extern crate poem;
+use async_graphql::*;
+#[derive(Default, SimpleObject)]
+struct Query { a: i32 }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+use poem::{get, Route};
+use async_graphql_poem::GraphQLSubscription;
+
+let app = Route::new()
+    .at("/ws", get(GraphQLSubscription::new(schema)));
+}
+

More examples

+

https://github.com/async-graphql/examples/tree/master/poem

+

Warp

+

For Async-graphql-warp, two Filter integrations are provided: graphql and graphql_subscription.

+

The graphql filter is used for execution Query and Mutation requests. It extracts GraphQL request and outputs async_graphql::Schema and async_graphql::Request. +You can combine other filters later, or directly call Schema::execute to execute the query.

+

graphql_subscription is used to implement WebSocket subscriptions. It outputs warp::Reply.

+

Request example

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_warp;
+extern crate async_graphql;
+extern crate warp;
+use async_graphql::*;
+use std::convert::Infallible;
+use warp::Filter;
+struct QueryRoot;
+#[Object]
+impl QueryRoot { async fn version(&self) -> &str { "1.0" } }
+async fn other() {
+type MySchema = Schema<QueryRoot, EmptyMutation, EmptySubscription>;
+
+let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription);
+let filter = async_graphql_warp::graphql(schema).and_then(|(schema, request): (MySchema, async_graphql::Request)| async move {
+    // Execute query
+    let resp = schema.execute(request).await;
+
+    // Return result
+    Ok::<_, Infallible>(async_graphql_warp::GraphQLResponse::from(resp))
+});
+warp::serve(filter).run(([0, 0, 0, 0], 8000)).await;
+}
+}
+

Subscription example

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_warp;
+extern crate async_graphql;
+extern crate warp;
+use async_graphql::*;
+use futures_util::stream::{Stream, StreamExt};
+use std::convert::Infallible;
+use warp::Filter;
+struct SubscriptionRoot;
+#[Subscription]
+impl SubscriptionRoot {
+  async fn tick(&self) -> impl Stream<Item = i32> {
+    futures_util::stream::iter(0..10)
+  }
+}
+struct QueryRoot;
+#[Object]
+impl QueryRoot { async fn version(&self) -> &str { "1.0" } }
+async fn other() {
+let schema = Schema::new(QueryRoot, EmptyMutation, SubscriptionRoot);
+let filter = async_graphql_warp::graphql_subscription(schema);
+warp::serve(filter).run(([0, 0, 0, 0], 8000)).await;
+}
+}
+

More examples

+

https://github.com/async-graphql/examples/tree/master/warp

+

Actix-web

+

Request example

+

When you define your actix_web::App you need to pass in the Schema as data.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_actix_web;
+extern crate async_graphql;
+extern crate actix_web;
+use async_graphql::*;
+#[derive(Default,SimpleObject)]
+struct Query { a: i32 }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+use actix_web::{web, HttpRequest, HttpResponse};
+use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse};
+async fn index(
+    // Schema now accessible here
+    schema: web::Data<Schema<Query, EmptyMutation, EmptySubscription>>,
+    request: GraphQLRequest,
+) -> web::Json<GraphQLResponse> {
+    web::Json(schema.execute(request.into_inner()).await.into())
+}
+}
+

Subscription example

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_actix_web;
+extern crate async_graphql;
+extern crate actix_web;
+use async_graphql::*;
+#[derive(Default,SimpleObject)]
+struct Query { a: i32 }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+use actix_web::{web, HttpRequest, HttpResponse};
+use async_graphql_actix_web::GraphQLSubscription;
+async fn index_ws(
+    schema: web::Data<Schema<Query, EmptyMutation, EmptySubscription>>,
+    req: HttpRequest,
+    payload: web::Payload,
+) -> actix_web::Result<HttpResponse> {
+    GraphQLSubscription::new(Schema::clone(&*schema)).start(&req, payload)
+}
+}
+

More examples

+

https://github.com/async-graphql/examples/tree/master/actix-web

+

Advanced topics

+

Custom scalars

+

In Async-graphql most common scalar types are built in, but you can also create your own scalar types.

+

Using async-graphql::Scalar, you can add support for a scalar when you implement it. You only need to implement parsing and output functions.

+

The following example defines a 64-bit integer scalar where its input and output are strings.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct StringNumber(i64);
+
+#[Scalar]
+impl ScalarType for StringNumber {
+    fn parse(value: Value) -> InputValueResult<Self> {
+        if let Value::String(value) = &value {
+            // Parse the integer value
+            Ok(value.parse().map(StringNumber)?)
+        } else {
+            // If the type does not match
+            Err(InputValueError::expected_type(value))
+        }
+    }
+
+    fn to_value(&self) -> Value {
+        Value::String(self.0.to_string())
+    }
+}
+}
+

Use scalar! macro to define scalar

+

If your type implemented serde::Serialize and serde::Deserialize, then you can use this macro to define a scalar more simply.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate serde;
+use async_graphql::*;
+use serde::{Serialize, Deserialize};
+use std::collections::HashMap;
+#[derive(Serialize, Deserialize)]
+struct MyValue {
+    a: i32,
+    b: HashMap<String, i32>,     
+}
+
+scalar!(MyValue);
+
+// Rename to `MV`.
+// scalar!(MyValue, "MV");
+
+// Rename to `MV` and add description.
+// scalar!(MyValue, "MV", "This is my value");
+}
+

Optimizing N+1 queries

+

Have you noticed some GraphQL queries end can make hundreds of database queries, often with mostly repeated data? Lets take a look why and how to fix it.

+

Query Resolution

+

Imagine if you have a simple query like this:

+
query {
+  todos {
+    users {
+      name
+    }
+  }
+}
+
+

and User resolver is like this:

+
struct User {
+    id: u64,
+}
+
+#[Object]
+impl User {
+    async fn name(&self, ctx: &Context<'_>) -> Result<String> {
+        let pool = ctx.data_unchecked::<Pool<Postgres>>();
+        let (name,): (String,) = sqlx::query_as("SELECT name FROM user WHERE id = $1")
+            .bind(self.id)
+            .fetch_one(pool)
+            .await?;
+        Ok(name)
+    }
+}
+

The query executor will call the Todos resolver which does a select * from todo and return N todos. Then for each +of the todos, concurrently, call the User resolver, SELECT from USER where id = todo.user_id.

+

eg:

+
SELECT id, todo, user_id FROM todo
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+
+

After executing SELECT name FROM user WHERE id = $1 many times, and most Todo objects belong to the same user, we +need to optimize these codes!

+

Dataloader

+

We need to group queries and exclude duplicate queries. Dataloader can do this. +facebook gives a request-scope batch and caching solution.

+

The following is a simplified example of using DataLoader to optimize queries, there is also a full code example available in GitHub.

+
use async_graphql::*;
+use async_graphql::dataloader::*;
+use std::sync::Arc;
+
+struct UserNameLoader {
+    pool: sqlx::PgPool,
+}
+
+impl Loader<u64> for UserNameLoader {
+    type Value = String;
+    type Error = Arc<sqlx::Error>;
+
+    async fn load(&self, keys: &[u64]) -> Result<HashMap<u64, Self::Value>, Self::Error> {
+        Ok(sqlx::query_as("SELECT name FROM user WHERE id = ANY($1)")
+            .bind(keys)
+            .fetch(&self.pool)
+            .map_ok(|name: String| name)
+            .map_err(Arc::new)
+            .try_collect().await?)
+    }
+}
+
+#[derive(SimpleObject)]
+#[graphql(complex)]
+struct User {
+    id: u64,
+}
+
+#[ComplexObject]
+impl User {
+    async fn name(&self, ctx: &Context<'_>) -> Result<String> {
+        let loader = ctx.data_unchecked::<DataLoader<UserNameLoader>>();
+        let name: Option<String> = loader.load_one(self.id).await?;
+        name.ok_or_else(|| "Not found".into())
+    }
+}
+

To expose UserNameLoader in the ctx, you have to register it with the schema, along with a task spawner, e.g. async_std::task::spawn:

+
let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
+    .data(DataLoader::new(
+        UserNameLoader,
+        async_std::task::spawn, // or `tokio::spawn`
+    ))
+    .finish();
+

In the end, only two SQLs are needed to query the results we want!

+
SELECT id, todo, user_id FROM todo
+SELECT name FROM user WHERE id IN (1, 2, 3, 4)
+
+

Implement multiple data types

+

You can implement multiple data types for the same Loader, like this:

+
extern crate async_graphql;
+use async_graphql::*;
+struct PostgresLoader {
+    pool: sqlx::PgPool,
+}
+
+impl Loader<UserId> for PostgresLoader {
+    type Value = User;
+    type Error = Arc<sqlx::Error>;
+
+    async fn load(&self, keys: &[UserId]) -> Result<HashMap<UserId, Self::Value>, Self::Error> {
+        // Load users from database
+    }
+}
+
+impl Loader<TodoId> for PostgresLoader {
+    type Value = Todo;
+    type Error = sqlx::Error;
+
+    async fn load(&self, keys: &[TodoId]) -> Result<HashMap<TodoId, Self::Value>, Self::Error> {
+        // Load todos from database
+    }
+}
+

Custom directive

+

There are two types of directives in GraphQL: executable and type system. Executable directives are used by the client within an operation to modify the behavior (like the built-in @include and @skip directives). Type system directives provide additional information about the types, potentially modifying how the server behaves (like @deprecated and @oneOf). async-graphql allows you to declare both types of custom directives, with different limitations on each.

+

Executable directives

+

To create a custom executable directive, you need to implement the CustomDirective trait, and then use the Directive macro to +generate a factory function that receives the parameters of the directive and returns an instance of the directive.

+

Currently async-graphql only supports custom executable directives located at FIELD.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct ConcatDirective {
+    value: String,
+}
+
+#[async_trait::async_trait]
+impl CustomDirective for ConcatDirective {
+    async fn resolve_field(&self, _ctx: &Context<'_>, resolve: ResolveFut<'_>) -> ServerResult<Option<Value>> {
+        resolve.await.map(|value| {
+            value.map(|value| match value {
+                Value::String(str) => Value::String(str + &self.value),
+                _ => value,
+            })
+        })
+    }
+}
+
+#[Directive(location = "Field")]
+fn concat(value: String) -> impl CustomDirective {
+    ConcatDirective { value }
+}
+}
+

Register the directive when building the schema:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+struct ConcatDirective { value: String, }
+#[async_trait::async_trait]
+impl CustomDirective for ConcatDirective {
+  async fn resolve_field(&self, _ctx: &Context<'_>, resolve: ResolveFut<'_>) -> ServerResult<Option<Value>> { todo!() }
+}
+#[Directive(location = "Field")]
+fn concat(value: String) -> impl CustomDirective { ConcatDirective { value } }
+let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
+    .directive(concat)
+    .finish();
+}
+

Type system directives

+

To create a custom type system directive, you can use the #[TypeDirective] macro on a function:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[TypeDirective(
+    location = "FieldDefinition",
+    location = "Object",
+)]
+fn testDirective(scope: String, input: u32, opt: Option<u64>) {}
+}
+

Current only the FieldDefinition and Object locations are supported, you can select one or both. After declaring the directive, you can apply it to a relevant location (after importing the function) like this:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[TypeDirective(
+location = "FieldDefinition",
+location = "Object",
+)]
+fn testDirective(scope: String, input: u32, opt: Option<u64>) {}
+#[derive(SimpleObject)]
+#[graphql(
+    directive = testDirective::apply("simple object type".to_string(), 1, Some(3))
+)]
+struct SimpleValue {
+    #[graphql(
+        directive = testDirective::apply("field and param with \" symbol".to_string(), 2, Some(3))
+    )]
+    some_data: String,
+}
+}
+

This example produces a schema like this:

+
type SimpleValue @testDirective(scope: "simple object type", input: 1, opt: 3) {
+	someData: String! @testDirective(scope: "field and param with \" symbol", input: 2, opt: 3)
+}
+
+directive @testDirective(scope: String!, input: Int!, opt: Int) on FIELD_DEFINITION | OBJECT
+
+

Note: To use a type-system directive with Apollo Federation's @composeDirective, see the federation docs

+

Apollo Federation

+

Apollo Federation is a GraphQL architecture for combining multiple GraphQL services, or subgraphs, into a single supergraph. You can read more in the official documentation.

+
+

To see a complete example of federation, check out the federation example.

+
+

Enabling federation support

+

async-graphql supports all the functionality of Apollo Federation v2. Support will be enabled automatically if any #[graphql(entity)] resolvers are found in the schema. To enable it manually, use the enable_federation method on the SchemaBuilder.

+
extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query {
+   async fn hello(&self) -> String { "Hello".to_string() }
+}
+fn main() {
+  let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
+    .enable_federation()
+    .finish();
+  // ... Start your server of choice
+}
+

This will define the @link directive on your schema to enable Federation v2.

+

Entities and @key

+

Entities are a core feature of federation, they allow multiple subgraphs to contribute fields to the same type. An entity is a GraphQL type with at least one @key directive. To create a @key for a type, create a reference resolver using the #[graphql(entity)] attribute. This resolver should be defined on the Query struct, but will not appear as a field in the schema.

+
+

Even though a reference resolver looks up an individual entity, it is crucial that you use a dataloader in the implementation. The federation router will look up entities in batches, which can quickly lead the N+1 performance issues.

+
+

Example

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct User { id: ID }
+struct Query;
+
+#[Object]
+impl Query {
+    #[graphql(entity)]
+    async fn find_user_by_id(&self, id: ID) -> User {
+        User { id }
+    }
+
+    #[graphql(entity)]
+    async fn find_user_by_id_with_username(&self, #[graphql(key)] id: ID, username: String) -> User {
+        User { id }
+    }
+
+    #[graphql(entity)]
+    async fn find_user_by_id_and_username(&self, id: ID, username: String) -> User {
+        User { id }
+    }
+}
+}
+

Notice the difference between these three lookup functions, which are all looking for the User object.

+
    +
  • +

    find_user_by_id: Use id to find a User object, the key for User is id.

    +
  • +
  • +

    find_user_by_id_with_username: Use id to find an User object, the key for User is id, and the username field value of the User object is requested (e.g., via @external and @requires).

    +
  • +
  • +

    find_user_by_id_and_username: Use id and username to find an User object, the keys for User are id and username.

    +
  • +
+

The resulting schema will look like this:

+
type Query {
+  # These fields will not be exposed to users, they are only used by the router to resolve entities
+  _entities(representations: [_Any!]!): [_Entity]!
+  _service: _Service!
+}
+
+type User @key(fields: "id") @key(fields: "id username") {
+  id: ID!
+}
+
+

Defining a compound primary key

+

A single primary key can consist of multiple fields, and even nested fields, you can use InputObject to implements a nested primary key.

+

In the following example, the primary key of the User object is key { a b }.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct User { key: Key }
+#[derive(SimpleObject)]
+struct Key { a: i32, b: i32 }
+#[derive(InputObject)]
+struct NestedKey {
+  a: i32,
+  b: i32,
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+  #[graphql(entity)]
+  async fn find_user_by_key(&self, key: NestedKey) -> User {
+    let NestedKey { a, b } = key;
+    User { key: Key{a, b} }
+  }
+}
+}
+

The resulting schema will look like this:

+
type Query {
+  # These fields will not be exposed to users, they are only used by the router to resolve entities
+  _entities(representations: [_Any!]!): [_Entity]!
+  _service: _Service!
+}
+
+type User @key(fields: "key { a b }") {
+  key: Key!
+}
+
+type Key {
+  a: Int!
+  b: Int!
+}
+
+

Creating unresolvable entities

+

There are certain times when you need to reference an entity, but not add any fields to it. This is particularly useful when you want to link data from separate subgraphs together, but neither subgraph has all the data.

+

If you wanted to implement the products and reviews subgraphs example from the Apollo Docs, you would create the following types for the reviews subgraph:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct Review {
+    product: Product,
+    score: u64,
+}
+
+#[derive(SimpleObject)]
+#[graphql(unresolvable)]
+struct Product {
+    id: u64,
+}
+}
+

This will add the @key(fields: "id", resolvable: false) directive to the Product type in the reviews subgraph.

+

For more complex entity keys, such as ones with nested fields in compound keys, you can override the fields in the directive as so:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+#[graphql(unresolvable = "id organization { id }")]
+struct User {
+    id: u64,
+    organization: Organization,
+}
+
+#[derive(SimpleObject)]
+struct Organization {
+    id: u64,
+}
+}
+

However, it is important to note that no validation will be done to check that these fields exist.

+

@shareable

+

Apply the @shareable directive to a type or field to indicate that multiple subgraphs can resolve it.

+

@shareable fields

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+#[graphql(complex)]
+struct Position {
+  #[graphql(shareable)]
+  x: u64,
+}
+
+#[ComplexObject]
+impl Position {
+  #[graphql(shareable)]
+  async fn y(&self) -> u64 {
+    0
+  }
+}
+}
+

The resulting schema will look like this:

+
type Position {
+  x: Int! @shareable
+  y: Int! @shareable
+}
+
+

@shareable type

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+#[graphql(shareable)]
+struct Position {
+  x: u64,
+  y: u64,
+}
+}
+

The resulting schema will look like this:

+
type Position @shareable {
+  x: Int!
+  y: Int!
+}
+
+

@inaccessible

+

The @inaccessible directive is used to omit something from the supergraph schema (e.g., if it's not yet added to all subgraphs which share a @shareable type).

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+#[graphql(shareable)]
+struct Position {
+  x: u32,
+  y: u32,
+  #[graphql(inaccessible)]
+  z: u32,
+} 
+}
+

Results in:

+
type Position @shareable {
+  x: Int!
+  y: Int!
+  z: Int! @inaccessible
+}
+
+

@override

+

The @override directive is used to take ownership of a field from another subgraph. This is useful for migrating a field from one subgraph to another.

+

For example, if you add a new "Inventory" subgraph which should take over responsibility for the inStock field currently provided by the "Products" subgraph, you might have something like this:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct Product {
+  id: ID,
+  #[graphql(override_from = "Products")]
+  in_stock: bool,
+}
+}
+

Which results in:

+
type Product @key(fields: "id") {
+  id: ID!
+  inStock: Boolean! @override(from: "Products")
+}
+
+

@external

+

The @external directive is used to indicate that a field is usually provided by another subgraph, but is sometimes required by this subgraph (when combined with @requires) or provided by this subgraph (when combined with @provides).

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct Product {
+  id: ID,
+  #[graphql(external)]
+  name: String,
+  in_stock: bool,
+}
+}
+

Results in:

+
type Product {
+  id: ID!
+  name: String! @external
+  inStock: Boolean!
+}
+
+

@provides

+

The @provides directive is used to indicate that a field is provided by this subgraph, but only sometimes.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct Product {
+    id: ID,
+    #[graphql(external)]
+    human_name: String,
+    in_stock: bool,
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    /// This operation will provide the `humanName` field on `Product
+    #[graphql(provides = "humanName")]
+    async fn out_of_stock_products(&self) -> Vec<Product> {
+      vec![Product {
+        id: "1".into(),
+        human_name: "My Product".to_string(),
+        in_stock: false,
+      }]
+    }
+    async fn discontinued_products(&self) -> Vec<Product> {
+        vec![Product {
+            id: "2".into(),
+            human_name: String::new(),  // This is ignored by the router
+            in_stock: false,
+        }]
+    }
+    #[graphql(entity)]
+    async fn find_product_by_id(&self, id: ID) -> Product {
+        Product {
+            id,
+            human_name: String::new(),  // This is ignored by the router
+            in_stock: true,
+        }
+    }
+}
+}
+

Note that the #[graphql(provides)] attribute takes the field name as it appears in the schema, not the Rust field name.

+

The resulting schema will look like this:

+
type Product @key(fields: "id") {
+    id: ID!
+    humanName: String! @external
+    inStock: Boolean!
+}
+
+type Query {
+    outOfStockProducts: [Product!]! @provides(fields: "humanName")
+    discontinuedProducts: [Product!]!
+}
+
+

@requires

+

The @requires directive is used to indicate that an @external field is required for this subgraph to resolve some other field(s). If our shippingEstimate field requires the size and weightInPounts fields, then we might want a subgraph entity which looks like this:

+
type Product @key(fields: "id") {
+  id: ID!
+  size: Int! @external
+  weightInPounds: Int! @external
+  shippingEstimate: String! @requires(fields: "size weightInPounds")
+}
+
+

In order to implement this in Rust, we can use the #[graphql(requires)] attribute:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+#[graphql(complex)]
+struct Product {
+  id: ID,
+  #[graphql(external)]
+  size: u32,
+  #[graphql(external)]
+  weight_in_pounds: u32,
+}
+
+#[ComplexObject]
+impl Product {
+  #[graphql(requires = "size weightInPounds")]
+  async fn shipping_estimate(&self) -> String {
+    let price = self.size * self.weight_in_pounds;
+    format!("${}", price)
+  }
+}
+}
+

Note that we use the GraphQL field name weightInPounds, not the Rust field name weight_in_pounds in requires. To populate those external fields, we add them as arguments in the entity resolver:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct Product {
+    id: ID,
+    #[graphql(external)]
+    size: u32,
+    #[graphql(external)]
+    weight_in_pounds: u32,
+}
+struct Query;
+#[Object]
+impl Query {
+  #[graphql(entity)]
+  async fn find_product_by_id(
+    &self, 
+    #[graphql(key)] id: ID, 
+    size: Option<u32>, 
+    weight_in_pounds: Option<u32>
+  ) -> Product {
+    Product {
+      id,
+      size: size.unwrap_or_default(),
+      weight_in_pounds: weight_in_pounds.unwrap_or_default(),
+    }
+  }
+}
+}
+

The inputs are Option<> even though the fields are required. This is because the external fields are only passed to the subgraph when the field(s) that require them are being selected. If the shippingEstimate field is not selected, then the size and weightInPounds fields will not be passed to the subgraph. Always use optional types for external fields.

+

We have to put something in place for size and weight_in_pounds as they are still required fields on the type, so we use unwrap_or_default() to provide a default value. This looks a little funny, as we're populating the fields with nonsense values, but we have confidence that they will not be needed if they were not provided. Make sure to use @requires if you are consuming @external fields, or your code will be wrong.

+

Nested @requires

+

A case where the @requires directive can be confusing is when there are nested entities. For example, if we had an Order type which contained a Product, then we would need an entity resolver like this:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+pub struct Order { id: ID }
+struct Query;
+#[Object]
+impl Query {
+  #[graphql(entity)]
+  async fn find_order_by_id(&self, id: ID) -> Option<Order> {
+      Some(Order { id })
+  }
+}
+}
+

There are no inputs on this entity resolver, so how do we populate the size and weight_in_pounds fields on Product if a user has a query like order { product { shippingEstimate } }? The supergraph implementation will solve this for us by calling the find_product_by_id separately for any fields which have a @requires directive, so the subgraph code does not need to worry about how entities relate.

+

@tag

+

The @tag directive is used to add metadata to a schema location for features like contracts. To add a tag like this:

+
type User @tag(name: "team-accounts") {
+  id: String!
+  name: String!
+}
+
+

You can write code like this:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+#[graphql(tag = "team-accounts")]
+struct User {
+  id: ID,
+  name: String,
+}
+}
+

@composeDirective

+

The @composeDirective directive is used to add a custom type system directive to the supergraph schema. Without @composeDirective, and custom type system directives are omitted from the composed supergraph schema. To include a custom type system directive as a composed directive, just add the composable attribute to the #[TypeDirective] macro:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[TypeDirective(
+    location = "Object",
+    composable = "https://custom.spec.dev/extension/v1.0",
+)]
+fn custom() {}
+}
+

In addition to the normal type system directive behavior, this will add the following bits to the output schema:

+
extend schema @link(
+	url: "https://custom.spec.dev/extension/v1.0"
+	import: ["@custom"]
+)
+	@composeDirective(name: "@custom")
+
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/query_and_mutation.html b/en/query_and_mutation.html new file mode 100644 index 000000000..5765a63e2 --- /dev/null +++ b/en/query_and_mutation.html @@ -0,0 +1,264 @@ + + + + + + Query and Mutation - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Query and Mutation

+

Query root object

+

The query root object is a GraphQL object with a definition similar to other objects. Resolver functions for all fields of the query object are executed concurrently.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct User { a: i32 }
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn user(&self, username: String) -> Result<Option<User>> {
+        // Look up users from the database
+       todo!()
+    }
+}
+
+}
+

Mutation root object

+

The mutation root object is also a GraphQL object, but it executes sequentially. One mutation following from another will only be executed only after the first mutation is completed.

+

The following mutation root object provides an example of user registration and login:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Mutation;
+
+#[Object]
+impl Mutation {
+    async fn signup(&self, username: String, password: String) -> Result<bool> {
+        // User signup
+       todo!()
+    }
+
+    async fn login(&self, username: String, password: String) -> Result<String> {
+        // User login (generate token)
+       todo!()
+    }
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/quickstart.html b/en/quickstart.html new file mode 100644 index 000000000..78426e85a --- /dev/null +++ b/en/quickstart.html @@ -0,0 +1,272 @@ + + + + + + Quickstart - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Quickstart

+

Add dependency libraries

+
[dependencies]
+async-graphql = "4.0"
+async-graphql-actix-web = "4.0" # If you need to integrate into actix-web
+async-graphql-warp = "4.0" # If you need to integrate into warp
+async-graphql-tide = "4.0" # If you need to integrate into tide
+
+

Write a Schema

+

The Schema of a GraphQL contains a required Query, an optional Mutation, and an optional Subscription. These object types are described using the structure of the Rust language. The field of the structure corresponds to the field of the GraphQL object.

+

Async-graphql implements the mapping of common data types to GraphQL types, such as i32, f64, Option<T>, Vec<T>, etc. Also, you can extend these base types, which are called scalars in the GraphQL.

+

Here is a simple example where we provide just one query that returns the sum of a and b.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    /// Returns the sum of a and b
+    async fn add(&self, a: i32, b: i32) -> i32 {
+        a + b
+    }
+}
+}
+

Execute the query

+

In our example, there is only a Query without a Mutation or Subscription, so we create the Schema with EmptyMutation and EmptySubscription, and then call Schema::execute to execute the Query.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+#[Object]
+impl Query {
+  async fn version(&self) -> &str { "1.0" }    
+}
+async fn other() {
+let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
+let res = schema.execute("{ add(a: 10, b: 20) }").await;
+}
+}
+

Output the query results as JSON

+
let json = serde_json::to_string(&res);
+

Web server integration

+

All examples are in the sub-repository, located in the examples directory.

+
git submodule update # update the examples repo
+cd examples && cargo run --bin [name]
+
+

For more information, see the sub-repository README.md.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/sdl_export.html b/en/sdl_export.html new file mode 100644 index 000000000..eca4adf4e --- /dev/null +++ b/en/sdl_export.html @@ -0,0 +1,241 @@ + + + + + + SDL Export - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

SDL Export

+

You can export your schema in Schema Definition Language (SDL) by using the Schema::sdl() method.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn add(&self, u: i32, v: i32) -> i32 {
+        u + v
+    }
+}
+
+let schema = Schema::build(Query, EmptyMutation, EmptySubscription).finish();
+    
+// Print the schema in SDL format
+println!("{}", &schema.sdl());
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/searcher.js b/en/searcher.js new file mode 100644 index 000000000..5f7a7be95 --- /dev/null +++ b/en/searcher.js @@ -0,0 +1,529 @@ +'use strict'; + +/* global Mark, elasticlunr, path_to_root */ + +window.search = window.search || {}; +(function search() { + // Search functionality + // + // You can use !hasFocus() to prevent keyhandling in your key + // event handlers while the user is typing their search. + + if (!Mark || !elasticlunr) { + return; + } + + // eslint-disable-next-line max-len + // IE 11 Compatibility from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith + if (!String.prototype.startsWith) { + String.prototype.startsWith = function(search, pos) { + return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search; + }; + } + + const search_wrap = document.getElementById('search-wrapper'), + searchbar = document.getElementById('searchbar'), + searchresults = document.getElementById('searchresults'), + searchresults_outer = document.getElementById('searchresults-outer'), + searchresults_header = document.getElementById('searchresults-header'), + searchicon = document.getElementById('search-toggle'), + content = document.getElementById('content'), + + // SVG text elements don't render if inside a tag. + mark_exclude = ['text'], + marker = new Mark(content), + URL_SEARCH_PARAM = 'search', + URL_MARK_PARAM = 'highlight'; + + let current_searchterm = '', + doc_urls = [], + search_options = { + bool: 'AND', + expand: true, + fields: { + title: {boost: 1}, + body: {boost: 1}, + breadcrumbs: {boost: 0}, + }, + }, + searchindex = null, + results_options = { + teaser_word_count: 30, + limit_results: 30, + }, + teaser_count = 0; + + function hasFocus() { + return searchbar === document.activeElement; + } + + function removeChildren(elem) { + while (elem.firstChild) { + elem.removeChild(elem.firstChild); + } + } + + // Helper to parse a url into its building blocks. + function parseURL(url) { + const a = document.createElement('a'); + a.href = url; + return { + source: url, + protocol: a.protocol.replace(':', ''), + host: a.hostname, + port: a.port, + params: (function() { + const ret = {}; + const seg = a.search.replace(/^\?/, '').split('&'); + for (const part of seg) { + if (!part) { + continue; + } + const s = part.split('='); + ret[s[0]] = s[1]; + } + return ret; + })(), + file: (a.pathname.match(/\/([^/?#]+)$/i) || ['', ''])[1], + hash: a.hash.replace('#', ''), + path: a.pathname.replace(/^([^/])/, '/$1'), + }; + } + + // Helper to recreate a url string from its building blocks. + function renderURL(urlobject) { + let url = urlobject.protocol + '://' + urlobject.host; + if (urlobject.port !== '') { + url += ':' + urlobject.port; + } + url += urlobject.path; + let joiner = '?'; + for (const prop in urlobject.params) { + if (Object.prototype.hasOwnProperty.call(urlobject.params, prop)) { + url += joiner + prop + '=' + urlobject.params[prop]; + joiner = '&'; + } + } + if (urlobject.hash !== '') { + url += '#' + urlobject.hash; + } + return url; + } + + // Helper to escape html special chars for displaying the teasers + const escapeHTML = (function() { + const MAP = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '\'': ''', + }; + const repl = function(c) { + return MAP[c]; + }; + return function(s) { + return s.replace(/[&<>'"]/g, repl); + }; + })(); + + function formatSearchMetric(count, searchterm) { + if (count === 1) { + return count + ' search result for \'' + searchterm + '\':'; + } else if (count === 0) { + return 'No search results for \'' + searchterm + '\'.'; + } else { + return count + ' search results for \'' + searchterm + '\':'; + } + } + + function formatSearchResult(result, searchterms) { + const teaser = makeTeaser(escapeHTML(result.doc.body), searchterms); + teaser_count++; + + // The ?URL_MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor + const url = doc_urls[result.ref].split('#'); + if (url.length === 1) { // no anchor found + url.push(''); + } + + // encodeURIComponent escapes all chars that could allow an XSS except + // for '. Due to that we also manually replace ' with its url-encoded + // representation (%27). + const encoded_search = encodeURIComponent(searchterms.join(' ')).replace(/'/g, '%27'); + + return '' + + result.doc.breadcrumbs + '' + '' + teaser + ''; + } + + function makeTeaser(body, searchterms) { + // The strategy is as follows: + // First, assign a value to each word in the document: + // Words that correspond to search terms (stemmer aware): 40 + // Normal words: 2 + // First word in a sentence: 8 + // Then use a sliding window with a constant number of words and count the + // sum of the values of the words within the window. Then use the window that got the + // maximum sum. If there are multiple maximas, then get the last one. + // Enclose the terms in . + const stemmed_searchterms = searchterms.map(function(w) { + return elasticlunr.stemmer(w.toLowerCase()); + }); + const searchterm_weight = 40; + const weighted = []; // contains elements of ["word", weight, index_in_document] + // split in sentences, then words + const sentences = body.toLowerCase().split('. '); + let index = 0; + let value = 0; + let searchterm_found = false; + for (const sentenceindex in sentences) { + const words = sentences[sentenceindex].split(' '); + value = 8; + for (const wordindex in words) { + const word = words[wordindex]; + if (word.length > 0) { + for (const searchtermindex in stemmed_searchterms) { + if (elasticlunr.stemmer(word).startsWith( + stemmed_searchterms[searchtermindex]) + ) { + value = searchterm_weight; + searchterm_found = true; + } + } + weighted.push([word, value, index]); + value = 2; + } + index += word.length; + index += 1; // ' ' or '.' if last word in sentence + } + index += 1; // because we split at a two-char boundary '. ' + } + + if (weighted.length === 0) { + return body; + } + + const window_weight = []; + const window_size = Math.min(weighted.length, results_options.teaser_word_count); + + let cur_sum = 0; + for (let wordindex = 0; wordindex < window_size; wordindex++) { + cur_sum += weighted[wordindex][1]; + } + window_weight.push(cur_sum); + for (let wordindex = 0; wordindex < weighted.length - window_size; wordindex++) { + cur_sum -= weighted[wordindex][1]; + cur_sum += weighted[wordindex + window_size][1]; + window_weight.push(cur_sum); + } + + let max_sum_window_index = 0; + if (searchterm_found) { + let max_sum = 0; + // backwards + for (let i = window_weight.length - 1; i >= 0; i--) { + if (window_weight[i] > max_sum) { + max_sum = window_weight[i]; + max_sum_window_index = i; + } + } + } else { + max_sum_window_index = 0; + } + + // add around searchterms + const teaser_split = []; + index = weighted[max_sum_window_index][2]; + for (let i = max_sum_window_index; i < max_sum_window_index + window_size; i++) { + const word = weighted[i]; + if (index < word[2]) { + // missing text from index to start of `word` + teaser_split.push(body.substring(index, word[2])); + index = word[2]; + } + if (word[1] === searchterm_weight) { + teaser_split.push(''); + } + index = word[2] + word[0].length; + teaser_split.push(body.substring(word[2], index)); + if (word[1] === searchterm_weight) { + teaser_split.push(''); + } + } + + return teaser_split.join(''); + } + + function init(config) { + results_options = config.results_options; + search_options = config.search_options; + doc_urls = config.doc_urls; + searchindex = elasticlunr.Index.load(config.index); + + // Set up events + searchicon.addEventListener('click', () => { + searchIconClickHandler(); + }, false); + searchbar.addEventListener('keyup', () => { + searchbarKeyUpHandler(); + }, false); + document.addEventListener('keydown', e => { + globalKeyHandler(e); + }, false); + // If the user uses the browser buttons, do the same as if a reload happened + window.onpopstate = () => { + doSearchOrMarkFromUrl(); + }; + // Suppress "submit" events so the page doesn't reload when the user presses Enter + document.addEventListener('submit', e => { + e.preventDefault(); + }, false); + + // If reloaded, do the search or mark again, depending on the current url parameters + doSearchOrMarkFromUrl(); + + // Exported functions + config.hasFocus = hasFocus; + } + + function unfocusSearchbar() { + // hacky, but just focusing a div only works once + const tmp = document.createElement('input'); + tmp.setAttribute('style', 'position: absolute; opacity: 0;'); + searchicon.appendChild(tmp); + tmp.focus(); + tmp.remove(); + } + + // On reload or browser history backwards/forwards events, parse the url and do search or mark + function doSearchOrMarkFromUrl() { + // Check current URL for search request + const url = parseURL(window.location.href); + if (Object.prototype.hasOwnProperty.call(url.params, URL_SEARCH_PARAM) + && url.params[URL_SEARCH_PARAM] !== '') { + showSearch(true); + searchbar.value = decodeURIComponent( + (url.params[URL_SEARCH_PARAM] + '').replace(/\+/g, '%20')); + searchbarKeyUpHandler(); // -> doSearch() + } else { + showSearch(false); + } + + if (Object.prototype.hasOwnProperty.call(url.params, URL_MARK_PARAM)) { + const words = decodeURIComponent(url.params[URL_MARK_PARAM]).split(' '); + marker.mark(words, { + exclude: mark_exclude, + }); + + const markers = document.querySelectorAll('mark'); + const hide = () => { + for (let i = 0; i < markers.length; i++) { + markers[i].classList.add('fade-out'); + window.setTimeout(() => { + marker.unmark(); + }, 300); + } + }; + + for (let i = 0; i < markers.length; i++) { + markers[i].addEventListener('click', hide); + } + } + } + + // Eventhandler for keyevents on `document` + function globalKeyHandler(e) { + if (e.altKey || + e.ctrlKey || + e.metaKey || + e.shiftKey || + e.target.type === 'textarea' || + e.target.type === 'text' || + !hasFocus() && /^(?:input|select|textarea)$/i.test(e.target.nodeName) + ) { + return; + } + + if (e.key === 'Escape') { + e.preventDefault(); + searchbar.classList.remove('active'); + setSearchUrlParameters('', + searchbar.value.trim() !== '' ? 'push' : 'replace'); + if (hasFocus()) { + unfocusSearchbar(); + } + showSearch(false); + marker.unmark(); + } else if (!hasFocus() && (e.key === 'S' || e.key === '/')) { + e.preventDefault(); + showSearch(true); + window.scrollTo(0, 0); + searchbar.select(); + } else if (hasFocus() && (e.key === 'ArrowDown' + || e.key === 'Enter')) { + e.preventDefault(); + const first = searchresults.firstElementChild; + if (first !== null) { + unfocusSearchbar(); + first.classList.add('focus'); + if (e.key === 'Enter') { + window.location.assign(first.querySelector('a')); + } + } + } else if (!hasFocus() && (e.key === 'ArrowDown' + || e.key === 'ArrowUp' + || e.key === 'Enter')) { + // not `:focus` because browser does annoying scrolling + const focused = searchresults.querySelector('li.focus'); + if (!focused) { + return; + } + e.preventDefault(); + if (e.key === 'ArrowDown') { + const next = focused.nextElementSibling; + if (next) { + focused.classList.remove('focus'); + next.classList.add('focus'); + } + } else if (e.key === 'ArrowUp') { + focused.classList.remove('focus'); + const prev = focused.previousElementSibling; + if (prev) { + prev.classList.add('focus'); + } else { + searchbar.select(); + } + } else { // Enter + window.location.assign(focused.querySelector('a')); + } + } + } + + function showSearch(yes) { + if (yes) { + search_wrap.classList.remove('hidden'); + searchicon.setAttribute('aria-expanded', 'true'); + } else { + search_wrap.classList.add('hidden'); + searchicon.setAttribute('aria-expanded', 'false'); + const results = searchresults.children; + for (let i = 0; i < results.length; i++) { + results[i].classList.remove('focus'); + } + } + } + + function showResults(yes) { + if (yes) { + searchresults_outer.classList.remove('hidden'); + } else { + searchresults_outer.classList.add('hidden'); + } + } + + // Eventhandler for search icon + function searchIconClickHandler() { + if (search_wrap.classList.contains('hidden')) { + showSearch(true); + window.scrollTo(0, 0); + searchbar.select(); + } else { + showSearch(false); + } + } + + // Eventhandler for keyevents while the searchbar is focused + function searchbarKeyUpHandler() { + const searchterm = searchbar.value.trim(); + if (searchterm !== '') { + searchbar.classList.add('active'); + doSearch(searchterm); + } else { + searchbar.classList.remove('active'); + showResults(false); + removeChildren(searchresults); + } + + setSearchUrlParameters(searchterm, 'push_if_new_search_else_replace'); + + // Remove marks + marker.unmark(); + } + + // Update current url with ?URL_SEARCH_PARAM= parameter, remove ?URL_MARK_PARAM and + // `#heading-anchor`. `action` can be one of "push", "replace", + // "push_if_new_search_else_replace" and replaces or pushes a new browser history item. + // "push_if_new_search_else_replace" pushes if there is no `?URL_SEARCH_PARAM=abc` yet. + function setSearchUrlParameters(searchterm, action) { + const url = parseURL(window.location.href); + const first_search = !Object.prototype.hasOwnProperty.call(url.params, URL_SEARCH_PARAM); + + if (searchterm !== '' || action === 'push_if_new_search_else_replace') { + url.params[URL_SEARCH_PARAM] = searchterm; + delete url.params[URL_MARK_PARAM]; + url.hash = ''; + } else { + delete url.params[URL_MARK_PARAM]; + delete url.params[URL_SEARCH_PARAM]; + } + // A new search will also add a new history item, so the user can go back + // to the page prior to searching. A updated search term will only replace + // the url. + if (action === 'push' || action === 'push_if_new_search_else_replace' && first_search ) { + history.pushState({}, document.title, renderURL(url)); + } else if (action === 'replace' || + action === 'push_if_new_search_else_replace' && + !first_search + ) { + history.replaceState({}, document.title, renderURL(url)); + } + } + + function doSearch(searchterm) { + // Don't search the same twice + if (current_searchterm === searchterm) { + return; + } else { + current_searchterm = searchterm; + } + + if (searchindex === null) { + return; + } + + // Do the actual search + const results = searchindex.search(searchterm, search_options); + const resultcount = Math.min(results.length, results_options.limit_results); + + // Display search metrics + searchresults_header.innerText = formatSearchMetric(resultcount, searchterm); + + // Clear and insert results + const searchterms = searchterm.split(' '); + removeChildren(searchresults); + for (let i = 0; i < resultcount ; i++) { + const resultElem = document.createElement('li'); + resultElem.innerHTML = formatSearchResult(results[i], searchterms); + searchresults.appendChild(resultElem); + } + + // Display results + showResults(true); + } + + function loadScript(url, id) { + const script = document.createElement('script'); + script.src = url; + script.id = id; + script.onload = () => init(window.search); + script.onerror = error => { + console.error(`Failed to load \`${url}\`: ${error}`); + }; + document.head.append(script); + } + + loadScript(path_to_root + 'searchindex.js', 'search-index'); + +})(window.search); diff --git a/en/searchindex.js b/en/searchindex.js new file mode 100644 index 000000000..bce8d9c72 --- /dev/null +++ b/en/searchindex.js @@ -0,0 +1 @@ +window.search = JSON.parse('{"doc_urls":["introduction.html#introduction","introduction.html#why-do-this","introduction.html#benchmarks","quickstart.html#quickstart","quickstart.html#add-dependency-libraries","quickstart.html#write-a-schema","quickstart.html#execute-the-query","quickstart.html#output-the-query-results-as-json","quickstart.html#web-server-integration","typesystem.html#type-system","define_simple_object.html#simpleobject","define_simple_object.html#user-defined-resolvers","define_simple_object.html#used-for-both-input-and-output","define_simple_object.html#flatten-fields","define_complex_object.html#object","context.html#context","context.html#store-data","context.html#schema-data","context.html#request-data","context.html#headers","context.html#selection--lookahead","error_handling.html#error-handling","merging_objects.html#merging-objects","merging_objects.html#merging-subscriptions","derived_fields.html#derived-fields","derived_fields.html#wrapper-types","derived_fields.html#example","define_enum.html#enum","define_enum.html#wrapping-a-remote-enum","define_interface.html#interface","define_interface.html#register-the-interface-manually","define_union.html#union","define_union.html#flattening-nested-unions","define_input_object.html#inputobject","define_input_object.html#generic-inputobjects","define_input_object.html#redacting-sensitive-data","define_input_object.html#flattening-fields","define_one_of_object.html#oneofobject","default_value.html#default-value","default_value.html#object-field","default_value.html#interface-field","default_value.html#input-object-field","generic.html#generics","generic.html#concrete-instantiation","generic.html#typename-trait","define_schema.html#schema","query_and_mutation.html#query-and-mutation","query_and_mutation.html#query-root-object","query_and_mutation.html#mutation-root-object","subscription.html#subscription","sdl_export.html#sdl-export","utilities.html#utilities","field_guard.html#field-guard","field_guard.html#use-parameter-value","input_value_validators.html#input-value-validators","input_value_validators.html#check-every-member-of-the-list","input_value_validators.html#custom-validator","cache_control.html#cache-control","cursor_connections.html#cursor-connections","error_extensions.html#error-extensions","error_extensions.html#example","error_extensions.html#general-concept","error_extensions.html#errorextensions","error_extensions.html#implementing-errorextensions-for-custom-errors","error_extensions.html#resultext","error_extensions.html#chained-extensions","error_extensions.html#pitfalls","apollo_tracing.html#apollo-tracing","depth_and_complexity.html#query-complexity-and-depth","depth_and_complexity.html#expensive-queries","depth_and_complexity.html#limiting-query-depth","depth_and_complexity.html#limiting-query-complexity","depth_and_complexity.html#custom-complexity-calculation","visibility.html#hide-content-in-introspection","extensions.html#extensions","extensions_inner_working.html#how-extensions-are-defined","extensions_inner_working.html#a-word-about-middleware","extensions_inner_working.html#processing-of-a-query","extensions_inner_working.html#request","extensions_inner_working.html#prepare_request","extensions_inner_working.html#parse_query","extensions_inner_working.html#validation","extensions_inner_working.html#execute","extensions_inner_working.html#resolve","extensions_inner_working.html#subscribe","extensions_available.html#extensions-available","extensions_available.html#analyzer","extensions_available.html#apollo-persisted-queries","extensions_available.html#apollo-tracing","extensions_available.html#apollo-studio","extensions_available.html#logger","extensions_available.html#opentelemetry","extensions_available.html#tracing","integrations.html#integrations","integrations_to_poem.html#poem","integrations_to_poem.html#request-example","integrations_to_poem.html#subscription-example","integrations_to_poem.html#more-examples","integrations_to_warp.html#warp","integrations_to_warp.html#request-example","integrations_to_warp.html#subscription-example","integrations_to_warp.html#more-examples","integrations_to_actix_web.html#actix-web","integrations_to_actix_web.html#request-example","integrations_to_actix_web.html#subscription-example","integrations_to_actix_web.html#more-examples","advanced_topics.html#advanced-topics","custom_scalars.html#custom-scalars","custom_scalars.html#use-scalar-macro-to-define-scalar","dataloader.html#optimizing-n1-queries","dataloader.html#query-resolution","dataloader.html#dataloader","dataloader.html#implement-multiple-data-types","custom_directive.html#custom-directive","custom_directive.html#executable-directives","custom_directive.html#type-system-directives","apollo_federation.html#apollo-federation","apollo_federation.html#enabling-federation-support","apollo_federation.html#entities-and-key","apollo_federation.html#example","apollo_federation.html#defining-a-compound-primary-key","apollo_federation.html#creating-unresolvable-entities","apollo_federation.html#shareable","apollo_federation.html#shareable-fields","apollo_federation.html#shareable-type","apollo_federation.html#inaccessible","apollo_federation.html#override","apollo_federation.html#external","apollo_federation.html#provides","apollo_federation.html#requires","apollo_federation.html#nested-requires","apollo_federation.html#tag","apollo_federation.html#composedirective"],"index":{"documentStore":{"docInfo":{"0":{"body":43,"breadcrumbs":2,"title":1},"1":{"body":23,"breadcrumbs":1,"title":0},"10":{"body":44,"breadcrumbs":4,"title":1},"100":{"body":53,"breadcrumbs":4,"title":2},"101":{"body":2,"breadcrumbs":4,"title":2},"102":{"body":0,"breadcrumbs":5,"title":2},"103":{"body":47,"breadcrumbs":5,"title":2},"104":{"body":39,"breadcrumbs":5,"title":2},"105":{"body":3,"breadcrumbs":5,"title":2},"106":{"body":0,"breadcrumbs":4,"title":2},"107":{"body":59,"breadcrumbs":6,"title":2},"108":{"body":43,"breadcrumbs":9,"title":5},"109":{"body":15,"breadcrumbs":8,"title":3},"11":{"body":80,"breadcrumbs":6,"title":3},"110":{"body":157,"breadcrumbs":7,"title":2},"111":{"body":126,"breadcrumbs":6,"title":1},"112":{"body":55,"breadcrumbs":9,"title":4},"113":{"body":43,"breadcrumbs":6,"title":2},"114":{"body":119,"breadcrumbs":6,"title":2},"115":{"body":120,"breadcrumbs":7,"title":3},"116":{"body":23,"breadcrumbs":6,"title":2},"117":{"body":53,"breadcrumbs":7,"title":3},"118":{"body":56,"breadcrumbs":6,"title":2},"119":{"body":121,"breadcrumbs":5,"title":1},"12":{"body":27,"breadcrumbs":7,"title":4},"120":{"body":93,"breadcrumbs":8,"title":4},"121":{"body":99,"breadcrumbs":7,"title":3},"122":{"body":9,"breadcrumbs":5,"title":1},"123":{"body":32,"breadcrumbs":6,"title":2},"124":{"body":23,"breadcrumbs":6,"title":2},"125":{"body":41,"breadcrumbs":5,"title":1},"126":{"body":54,"breadcrumbs":5,"title":1},"127":{"body":43,"breadcrumbs":5,"title":1},"128":{"body":107,"breadcrumbs":5,"title":1},"129":{"body":196,"breadcrumbs":5,"title":1},"13":{"body":36,"breadcrumbs":5,"title":2},"130":{"body":67,"breadcrumbs":6,"title":2},"131":{"body":37,"breadcrumbs":5,"title":1},"132":{"body":64,"breadcrumbs":5,"title":1},"14":{"body":129,"breadcrumbs":4,"title":1},"15":{"body":14,"breadcrumbs":5,"title":1},"16":{"body":60,"breadcrumbs":6,"title":2},"17":{"body":46,"breadcrumbs":6,"title":2},"18":{"body":81,"breadcrumbs":6,"title":2},"19":{"body":64,"breadcrumbs":5,"title":1},"2":{"body":14,"breadcrumbs":2,"title":1},"20":{"body":72,"breadcrumbs":6,"title":2},"21":{"body":102,"breadcrumbs":7,"title":2},"22":{"body":126,"breadcrumbs":8,"title":2},"23":{"body":66,"breadcrumbs":8,"title":2},"24":{"body":102,"breadcrumbs":7,"title":2},"25":{"body":55,"breadcrumbs":7,"title":2},"26":{"body":50,"breadcrumbs":6,"title":1},"27":{"body":47,"breadcrumbs":4,"title":1},"28":{"body":131,"breadcrumbs":6,"title":3},"29":{"body":164,"breadcrumbs":4,"title":1},"3":{"body":0,"breadcrumbs":2,"title":1},"30":{"body":102,"breadcrumbs":6,"title":3},"31":{"body":83,"breadcrumbs":4,"title":1},"32":{"body":97,"breadcrumbs":6,"title":3},"33":{"body":57,"breadcrumbs":4,"title":1},"34":{"body":153,"breadcrumbs":5,"title":2},"35":{"body":24,"breadcrumbs":6,"title":3},"36":{"body":41,"breadcrumbs":5,"title":2},"37":{"body":84,"breadcrumbs":4,"title":1},"38":{"body":8,"breadcrumbs":6,"title":2},"39":{"body":63,"breadcrumbs":6,"title":2},"4":{"body":27,"breadcrumbs":4,"title":3},"40":{"body":69,"breadcrumbs":6,"title":2},"41":{"body":23,"breadcrumbs":7,"title":3},"42":{"body":25,"breadcrumbs":4,"title":1},"43":{"body":109,"breadcrumbs":5,"title":2},"44":{"body":52,"breadcrumbs":5,"title":2},"45":{"body":40,"breadcrumbs":2,"title":1},"46":{"body":0,"breadcrumbs":5,"title":2},"47":{"body":40,"breadcrumbs":6,"title":3},"48":{"body":58,"breadcrumbs":6,"title":3},"49":{"body":79,"breadcrumbs":3,"title":1},"5":{"body":73,"breadcrumbs":3,"title":2},"50":{"body":39,"breadcrumbs":5,"title":2},"51":{"body":0,"breadcrumbs":2,"title":1},"52":{"body":107,"breadcrumbs":5,"title":2},"53":{"body":62,"breadcrumbs":6,"title":3},"54":{"body":109,"breadcrumbs":7,"title":3},"55":{"body":27,"breadcrumbs":7,"title":3},"56":{"body":53,"breadcrumbs":6,"title":2},"57":{"body":145,"breadcrumbs":5,"title":2},"58":{"body":102,"breadcrumbs":5,"title":2},"59":{"body":27,"breadcrumbs":5,"title":2},"6":{"body":41,"breadcrumbs":3,"title":2},"60":{"body":7,"breadcrumbs":4,"title":1},"61":{"body":52,"breadcrumbs":5,"title":2},"62":{"body":56,"breadcrumbs":4,"title":1},"63":{"body":121,"breadcrumbs":7,"title":4},"64":{"body":36,"breadcrumbs":4,"title":1},"65":{"body":60,"breadcrumbs":5,"title":2},"66":{"body":54,"breadcrumbs":4,"title":1},"67":{"body":50,"breadcrumbs":5,"title":2},"68":{"body":26,"breadcrumbs":7,"title":3},"69":{"body":59,"breadcrumbs":6,"title":2},"7":{"body":2,"breadcrumbs":5,"title":4},"70":{"body":50,"breadcrumbs":7,"title":3},"71":{"body":55,"breadcrumbs":7,"title":3},"72":{"body":81,"breadcrumbs":7,"title":3},"73":{"body":87,"breadcrumbs":7,"title":3},"74":{"body":19,"breadcrumbs":2,"title":1},"75":{"body":65,"breadcrumbs":5,"title":2},"76":{"body":71,"breadcrumbs":5,"title":2},"77":{"body":11,"breadcrumbs":5,"title":2},"78":{"body":96,"breadcrumbs":4,"title":1},"79":{"body":47,"breadcrumbs":4,"title":1},"8":{"body":24,"breadcrumbs":4,"title":3},"80":{"body":58,"breadcrumbs":4,"title":1},"81":{"body":40,"breadcrumbs":4,"title":1},"82":{"body":54,"breadcrumbs":4,"title":1},"83":{"body":43,"breadcrumbs":4,"title":1},"84":{"body":38,"breadcrumbs":4,"title":1},"85":{"body":11,"breadcrumbs":5,"title":2},"86":{"body":14,"breadcrumbs":4,"title":1},"87":{"body":80,"breadcrumbs":6,"title":3},"88":{"body":33,"breadcrumbs":5,"title":2},"89":{"body":40,"breadcrumbs":5,"title":2},"9":{"body":11,"breadcrumbs":4,"title":2},"90":{"body":17,"breadcrumbs":4,"title":1},"91":{"body":16,"breadcrumbs":4,"title":1},"92":{"body":14,"breadcrumbs":4,"title":1},"93":{"body":42,"breadcrumbs":2,"title":1},"94":{"body":0,"breadcrumbs":3,"title":1},"95":{"body":28,"breadcrumbs":4,"title":2},"96":{"body":29,"breadcrumbs":4,"title":2},"97":{"body":2,"breadcrumbs":4,"title":2},"98":{"body":37,"breadcrumbs":3,"title":1},"99":{"body":56,"breadcrumbs":4,"title":2}},"docs":{"0":{"body":"Async-graphql is a GraphQL server-side library implemented in Rust. It is fully compatible with the GraphQL specification and most of its extensions, and offers type safety and high performance. You can define a Schema in Rust and procedural macros will automatically generate code for a GraphQL query. This library does not extend Rust\'s syntax, which means that Rustfmt can be used normally. I value this highly and it is one of the reasons why I developed Async-graphql.","breadcrumbs":"Introduction » Introduction","id":"0","title":"Introduction"},"1":{"body":"I like GraphQL and Rust. I\'ve been using Juniper, which solves the problem of implementing a GraphQL server with Rust. But Juniper had several problems, the most important of which is that it didn\'t support async/await at the time. So I decided to make this library for myself.","breadcrumbs":"Introduction » Why do this?","id":"1","title":"Why do this?"},"10":{"body":"SimpleObject directly maps all the fields of a struct to GraphQL object. If you don\'t require automatic mapping of fields, see Object . The example below defines an object MyObject which includes the fields a and b. c will be not mapped to GraphQL as it is labelled as #[graphql(skip)] # extern crate async_graphql;\\nuse async_graphql::*; #[derive(SimpleObject)]\\nstruct MyObject { /// Value a a: i32, /// Value b b: i32, #[graphql(skip)] c: i32,\\n}","breadcrumbs":"Type System » SimpleObject » SimpleObject","id":"10","title":"SimpleObject"},"100":{"body":"# extern crate async_graphql_warp;\\n# extern crate async_graphql;\\n# extern crate warp;\\n# use async_graphql::*;\\n# use futures_util::stream::{Stream, StreamExt};\\n# use std::convert::Infallible;\\n# use warp::Filter;\\n# struct SubscriptionRoot;\\n# #[Subscription]\\n# impl SubscriptionRoot {\\n# async fn tick(&self) -> impl Stream {\\n# futures_util::stream::iter(0..10)\\n# }\\n# }\\n# struct QueryRoot;\\n# #[Object]\\n# impl QueryRoot { async fn version(&self) -> &str { \\"1.0\\" } }\\n# async fn other() {\\nlet schema = Schema::new(QueryRoot, EmptyMutation, SubscriptionRoot);\\nlet filter = async_graphql_warp::graphql_subscription(schema);\\nwarp::serve(filter).run(([0, 0, 0, 0], 8000)).await;\\n# }","breadcrumbs":"Integrations » Warp » Subscription example","id":"100","title":"Subscription example"},"101":{"body":"https://github.com/async-graphql/examples/tree/master/warp","breadcrumbs":"Integrations » Warp » More examples","id":"101","title":"More examples"},"102":{"body":"","breadcrumbs":"Integrations » Actix-web » Actix-web","id":"102","title":"Actix-web"},"103":{"body":"When you define your actix_web::App you need to pass in the Schema as data. # extern crate async_graphql_actix_web;\\n# extern crate async_graphql;\\n# extern crate actix_web;\\n# use async_graphql::*;\\n# #[derive(Default,SimpleObject)]\\n# struct Query { a: i32 }\\n# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();\\nuse actix_web::{web, HttpRequest, HttpResponse};\\nuse async_graphql_actix_web::{GraphQLRequest, GraphQLResponse};\\nasync fn index( // Schema now accessible here schema: web::Data>, request: GraphQLRequest,\\n) -> web::Json { web::Json(schema.execute(request.into_inner()).await.into())\\n}","breadcrumbs":"Integrations » Actix-web » Request example","id":"103","title":"Request example"},"104":{"body":"# extern crate async_graphql_actix_web;\\n# extern crate async_graphql;\\n# extern crate actix_web;\\n# use async_graphql::*;\\n# #[derive(Default,SimpleObject)]\\n# struct Query { a: i32 }\\n# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();\\nuse actix_web::{web, HttpRequest, HttpResponse};\\nuse async_graphql_actix_web::GraphQLSubscription;\\nasync fn index_ws( schema: web::Data>, req: HttpRequest, payload: web::Payload,\\n) -> actix_web::Result { GraphQLSubscription::new(Schema::clone(&*schema)).start(&req, payload)\\n}","breadcrumbs":"Integrations » Actix-web » Subscription example","id":"104","title":"Subscription example"},"105":{"body":"https://github.com/async-graphql/examples/tree/master/actix-web","breadcrumbs":"Integrations » Actix-web » More examples","id":"105","title":"More examples"},"106":{"body":"","breadcrumbs":"Advanced topics » Advanced topics","id":"106","title":"Advanced topics"},"107":{"body":"In Async-graphql most common scalar types are built in, but you can also create your own scalar types. Using async-graphql::Scalar, you can add support for a scalar when you implement it. You only need to implement parsing and output functions. The following example defines a 64-bit integer scalar where its input and output are strings. # extern crate async_graphql;\\nuse async_graphql::*; struct StringNumber(i64); #[Scalar]\\nimpl ScalarType for StringNumber { fn parse(value: Value) -> InputValueResult { if let Value::String(value) = &value { // Parse the integer value Ok(value.parse().map(StringNumber)?) } else { // If the type does not match Err(InputValueError::expected_type(value)) } } fn to_value(&self) -> Value { Value::String(self.0.to_string()) }\\n}","breadcrumbs":"Advanced topics » Custom scalars » Custom scalars","id":"107","title":"Custom scalars"},"108":{"body":"If your type implemented serde::Serialize and serde::Deserialize, then you can use this macro to define a scalar more simply. # extern crate async_graphql;\\n# extern crate serde;\\n# use async_graphql::*;\\n# use serde::{Serialize, Deserialize};\\n# use std::collections::HashMap;\\n#[derive(Serialize, Deserialize)]\\nstruct MyValue { a: i32, b: HashMap, } scalar!(MyValue); // Rename to `MV`.\\n// scalar!(MyValue, \\"MV\\"); // Rename to `MV` and add description.\\n// scalar!(MyValue, \\"MV\\", \\"This is my value\\");","breadcrumbs":"Advanced topics » Custom scalars » Use scalar! macro to define scalar","id":"108","title":"Use scalar! macro to define scalar"},"109":{"body":"Have you noticed some GraphQL queries end can make hundreds of database queries, often with mostly repeated data? Lets take a look why and how to fix it.","breadcrumbs":"Advanced topics » Optimizing N+1 queries » Optimizing N+1 queries","id":"109","title":"Optimizing N+1 queries"},"11":{"body":"Sometimes most of the fields of a GraphQL object simply return the value of the structure member, but a few fields are calculated. In this case, the Object macro cannot be used unless you hand-write all the resolvers. The ComplexObject macro works in conjunction with the SimpleObject macro. The SimpleObject derive macro defines the non-calculated fields, where as the ComplexObject macro let\'s you write user-defined resolvers for the calculated fields. Resolvers added to ComplexObject adhere to the same rules as resolvers of Object . # extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(SimpleObject)]\\n#[graphql(complex)] // NOTE: If you want the `ComplexObject` macro to take effect, this `complex` attribute is required.\\nstruct MyObj { a: i32, b: i32,\\n} #[ComplexObject]\\nimpl MyObj { async fn c(&self) -> i32 { self.a + self.b }\\n}","breadcrumbs":"Type System » SimpleObject » User-defined resolvers","id":"11","title":"User-defined resolvers"},"110":{"body":"Imagine if you have a simple query like this: query { todos { users { name } }\\n} and User resolver is like this: struct User { id: u64,\\n} #[Object]\\nimpl User { async fn name(&self, ctx: &Context<\'_>) -> Result { let pool = ctx.data_unchecked::>(); let (name,): (String,) = sqlx::query_as(\\"SELECT name FROM user WHERE id = $1\\") .bind(self.id) .fetch_one(pool) .await?; Ok(name) }\\n} The query executor will call the Todos resolver which does a select * from todo and return N todos. Then for each of the todos, concurrently, call the User resolver, SELECT from USER where id = todo.user_id. eg: SELECT id, todo, user_id FROM todo\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1 After executing SELECT name FROM user WHERE id = $1 many times, and most Todo objects belong to the same user, we need to optimize these codes!","breadcrumbs":"Advanced topics » Optimizing N+1 queries » Query Resolution","id":"110","title":"Query Resolution"},"111":{"body":"We need to group queries and exclude duplicate queries. Dataloader can do this. facebook gives a request-scope batch and caching solution. The following is a simplified example of using DataLoader to optimize queries, there is also a full code example available in GitHub . use async_graphql::*;\\nuse async_graphql::dataloader::*;\\nuse std::sync::Arc; struct UserNameLoader { pool: sqlx::PgPool,\\n} impl Loader for UserNameLoader { type Value = String; type Error = Arc; async fn load(&self, keys: &[u64]) -> Result, Self::Error> { Ok(sqlx::query_as(\\"SELECT name FROM user WHERE id = ANY($1)\\") .bind(keys) .fetch(&self.pool) .map_ok(|name: String| name) .map_err(Arc::new) .try_collect().await?) }\\n} #[derive(SimpleObject)]\\n#[graphql(complex)]\\nstruct User { id: u64,\\n} #[ComplexObject]\\nimpl User { async fn name(&self, ctx: &Context<\'_>) -> Result { let loader = ctx.data_unchecked::>(); let name: Option = loader.load_one(self.id).await?; name.ok_or_else(|| \\"Not found\\".into()) }\\n} To expose UserNameLoader in the ctx, you have to register it with the schema, along with a task spawner, e.g. async_std::task::spawn: let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription) .data(DataLoader::new( UserNameLoader, async_std::task::spawn, // or `tokio::spawn` )) .finish(); In the end, only two SQLs are needed to query the results we want! SELECT id, todo, user_id FROM todo\\nSELECT name FROM user WHERE id IN (1, 2, 3, 4)","breadcrumbs":"Advanced topics » Optimizing N+1 queries » Dataloader","id":"111","title":"Dataloader"},"112":{"body":"You can implement multiple data types for the same Loader, like this: # extern crate async_graphql;\\n# use async_graphql::*;\\nstruct PostgresLoader { pool: sqlx::PgPool,\\n} impl Loader for PostgresLoader { type Value = User; type Error = Arc; async fn load(&self, keys: &[UserId]) -> Result, Self::Error> { // Load users from database }\\n} impl Loader for PostgresLoader { type Value = Todo; type Error = sqlx::Error; async fn load(&self, keys: &[TodoId]) -> Result, Self::Error> { // Load todos from database }\\n}","breadcrumbs":"Advanced topics » Optimizing N+1 queries » Implement multiple data types","id":"112","title":"Implement multiple data types"},"113":{"body":"There are two types of directives in GraphQL: executable and type system. Executable directives are used by the client within an operation to modify the behavior (like the built-in @include and @skip directives). Type system directives provide additional information about the types, potentially modifying how the server behaves (like @deprecated and @oneOf). async-graphql allows you to declare both types of custom directives, with different limitations on each.","breadcrumbs":"Advanced topics » Custom directive » Custom directive","id":"113","title":"Custom directive"},"114":{"body":"To create a custom executable directive, you need to implement the CustomDirective trait, and then use the Directive macro to generate a factory function that receives the parameters of the directive and returns an instance of the directive. Currently async-graphql only supports custom executable directives located at FIELD. # extern crate async_graphql;\\n# use async_graphql::*;\\nstruct ConcatDirective { value: String,\\n} #[async_trait::async_trait]\\nimpl CustomDirective for ConcatDirective { async fn resolve_field(&self, _ctx: &Context<\'_>, resolve: ResolveFut<\'_>) -> ServerResult> { resolve.await.map(|value| { value.map(|value| match value { Value::String(str) => Value::String(str + &self.value), _ => value, }) }) }\\n} #[Directive(location = \\"Field\\")]\\nfn concat(value: String) -> impl CustomDirective { ConcatDirective { value }\\n} Register the directive when building the schema: # extern crate async_graphql;\\n# use async_graphql::*;\\n# struct Query;\\n# #[Object]\\n# impl Query { async fn version(&self) -> &str { \\"1.0\\" } }\\n# struct ConcatDirective { value: String, }\\n# #[async_trait::async_trait]\\n# impl CustomDirective for ConcatDirective {\\n# async fn resolve_field(&self, _ctx: &Context<\'_>, resolve: ResolveFut<\'_>) -> ServerResult> { todo!() }\\n# }\\n# #[Directive(location = \\"Field\\")]\\n# fn concat(value: String) -> impl CustomDirective { ConcatDirective { value } }\\nlet schema = Schema::build(Query, EmptyMutation, EmptySubscription) .directive(concat) .finish();","breadcrumbs":"Advanced topics » Custom directive » Executable directives","id":"114","title":"Executable directives"},"115":{"body":"To create a custom type system directive, you can use the #[TypeDirective] macro on a function: # extern crate async_graphql;\\n# use async_graphql::*;\\n#[TypeDirective( location = \\"FieldDefinition\\", location = \\"Object\\",\\n)]\\nfn testDirective(scope: String, input: u32, opt: Option) {} Current only the FieldDefinition and Object locations are supported, you can select one or both. After declaring the directive, you can apply it to a relevant location (after importing the function) like this: # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[TypeDirective(\\n# location = \\"FieldDefinition\\",\\n# location = \\"Object\\",\\n# )]\\n# fn testDirective(scope: String, input: u32, opt: Option) {}\\n#[derive(SimpleObject)]\\n#[graphql( directive = testDirective::apply(\\"simple object type\\".to_string(), 1, Some(3))\\n)]\\nstruct SimpleValue { #[graphql( directive = testDirective::apply(\\"field and param with \\\\\\" symbol\\".to_string(), 2, Some(3)) )] some_data: String,\\n} This example produces a schema like this: type SimpleValue @testDirective(scope: \\"simple object type\\", input: 1, opt: 3) { someData: String! @testDirective(scope: \\"field and param with \\\\\\" symbol\\", input: 2, opt: 3)\\n} directive @testDirective(scope: String!, input: Int!, opt: Int) on FIELD_DEFINITION | OBJECT Note: To use a type-system directive with Apollo Federation\'s @composeDirective, see the federation docs","breadcrumbs":"Advanced topics » Custom directive » Type system directives","id":"115","title":"Type system directives"},"116":{"body":"Apollo Federation is a GraphQL architecture for combining multiple GraphQL services, or subgraphs, into a single supergraph. You can read more in the official documentation . To see a complete example of federation, check out the federation example .","breadcrumbs":"Advanced topics » Apollo Federation » Apollo Federation","id":"116","title":"Apollo Federation"},"117":{"body":"async-graphql supports all the functionality of Apollo Federation v2. Support will be enabled automatically if any #[graphql(entity)] resolvers are found in the schema. To enable it manually, use the enable_federation method on the SchemaBuilder. # extern crate async_graphql;\\n# use async_graphql::*;\\n# struct Query;\\n# #[Object]\\n# impl Query {\\n# async fn hello(&self) -> String { \\"Hello\\".to_string() }\\n# }\\nfn main() { let schema = Schema::build(Query, EmptyMutation, EmptySubscription) .enable_federation() .finish(); // ... Start your server of choice\\n} This will define the @link directive on your schema to enable Federation v2.","breadcrumbs":"Advanced topics » Apollo Federation » Enabling federation support","id":"117","title":"Enabling federation support"},"118":{"body":"Entities are a core feature of federation, they allow multiple subgraphs to contribute fields to the same type. An entity is a GraphQL type with at least one @key directive . To create a @key for a type, create a reference resolver using the #[graphql(entity)] attribute. This resolver should be defined on the Query struct, but will not appear as a field in the schema. Even though a reference resolver looks up an individual entity, it is crucial that you use a dataloader in the implementation. The federation router will look up entities in batches, which can quickly lead the N+1 performance issues.","breadcrumbs":"Advanced topics » Apollo Federation » Entities and @key","id":"118","title":"Entities and @key"},"119":{"body":"# extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(SimpleObject)]\\n# struct User { id: ID }\\nstruct Query; #[Object]\\nimpl Query { #[graphql(entity)] async fn find_user_by_id(&self, id: ID) -> User { User { id } } #[graphql(entity)] async fn find_user_by_id_with_username(&self, #[graphql(key)] id: ID, username: String) -> User { User { id } } #[graphql(entity)] async fn find_user_by_id_and_username(&self, id: ID, username: String) -> User { User { id } }\\n} Notice the difference between these three lookup functions, which are all looking for the User object. find_user_by_id: Use id to find a User object, the key for User is id. find_user_by_id_with_username: Use id to find an User object, the key for User is id, and the username field value of the User object is requested (e.g., via @external and @requires). find_user_by_id_and_username: Use id and username to find an User object, the keys for User are id and username. The resulting schema will look like this: type Query { # These fields will not be exposed to users, they are only used by the router to resolve entities _entities(representations: [_Any!]!): [_Entity]! _service: _Service!\\n} type User @key(fields: \\"id\\") @key(fields: \\"id username\\") { id: ID!\\n}","breadcrumbs":"Advanced topics » Apollo Federation » Example","id":"119","title":"Example"},"12":{"body":"# extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(SimpleObject, InputObject)]\\n#[graphql(input_name = \\"MyObjInput\\")] // Note: You must use the input_name attribute to define a new name for the input type, otherwise a runtime error will occur.\\nstruct MyObj { a: i32, b: i32,\\n}","breadcrumbs":"Type System » SimpleObject » Used for both input and output","id":"12","title":"Used for both input and output"},"120":{"body":"A single primary key can consist of multiple fields, and even nested fields, you can use InputObject to implements a nested primary key. In the following example, the primary key of the User object is key { a b }. # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(SimpleObject)]\\n# struct User { key: Key }\\n# #[derive(SimpleObject)]\\n# struct Key { a: i32, b: i32 }\\n#[derive(InputObject)]\\nstruct NestedKey { a: i32, b: i32,\\n} struct Query; #[Object]\\nimpl Query { #[graphql(entity)] async fn find_user_by_key(&self, key: NestedKey) -> User { let NestedKey { a, b } = key; User { key: Key{a, b} } }\\n} The resulting schema will look like this: type Query { # These fields will not be exposed to users, they are only used by the router to resolve entities _entities(representations: [_Any!]!): [_Entity]! _service: _Service!\\n} type User @key(fields: \\"key { a b }\\") { key: Key!\\n} type Key { a: Int! b: Int!\\n}","breadcrumbs":"Advanced topics » Apollo Federation » Defining a compound primary key","id":"120","title":"Defining a compound primary key"},"121":{"body":"There are certain times when you need to reference an entity, but not add any fields to it. This is particularly useful when you want to link data from separate subgraphs together, but neither subgraph has all the data. If you wanted to implement the products and reviews subgraphs example from the Apollo Docs, you would create the following types for the reviews subgraph: # extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(SimpleObject)]\\nstruct Review { product: Product, score: u64,\\n} #[derive(SimpleObject)]\\n#[graphql(unresolvable)]\\nstruct Product { id: u64,\\n} This will add the @key(fields: \\"id\\", resolvable: false) directive to the Product type in the reviews subgraph. For more complex entity keys, such as ones with nested fields in compound keys, you can override the fields in the directive as so: # extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(SimpleObject)]\\n#[graphql(unresolvable = \\"id organization { id }\\")]\\nstruct User { id: u64, organization: Organization,\\n} #[derive(SimpleObject)]\\nstruct Organization { id: u64,\\n} However, it is important to note that no validation will be done to check that these fields exist.","breadcrumbs":"Advanced topics » Apollo Federation » Creating unresolvable entities","id":"121","title":"Creating unresolvable entities"},"122":{"body":"Apply the @shareable directive to a type or field to indicate that multiple subgraphs can resolve it.","breadcrumbs":"Advanced topics » Apollo Federation » @shareable","id":"122","title":"@shareable"},"123":{"body":"# extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(SimpleObject)]\\n#[graphql(complex)]\\nstruct Position { #[graphql(shareable)] x: u64,\\n} #[ComplexObject]\\nimpl Position { #[graphql(shareable)] async fn y(&self) -> u64 { 0 }\\n} The resulting schema will look like this: type Position { x: Int! @shareable y: Int! @shareable\\n}","breadcrumbs":"Advanced topics » Apollo Federation » @shareable fields","id":"123","title":"@shareable fields"},"124":{"body":"# extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(SimpleObject)]\\n#[graphql(shareable)]\\nstruct Position { x: u64, y: u64,\\n} The resulting schema will look like this: type Position @shareable { x: Int! y: Int!\\n}","breadcrumbs":"Advanced topics » Apollo Federation » @shareable type","id":"124","title":"@shareable type"},"125":{"body":"The @inaccessible directive is used to omit something from the supergraph schema (e.g., if it\'s not yet added to all subgraphs which share a @shareable type). # extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(SimpleObject)]\\n#[graphql(shareable)]\\nstruct Position { x: u32, y: u32, #[graphql(inaccessible)] z: u32,\\n} Results in: type Position @shareable { x: Int! y: Int! z: Int! @inaccessible\\n}","breadcrumbs":"Advanced topics » Apollo Federation » @inaccessible","id":"125","title":"@inaccessible"},"126":{"body":"The @override directive is used to take ownership of a field from another subgraph. This is useful for migrating a field from one subgraph to another. For example, if you add a new \\"Inventory\\" subgraph which should take over responsibility for the inStock field currently provided by the \\"Products\\" subgraph, you might have something like this: # extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(SimpleObject)]\\nstruct Product { id: ID, #[graphql(override_from = \\"Products\\")] in_stock: bool,\\n} Which results in: type Product @key(fields: \\"id\\") { id: ID! inStock: Boolean! @override(from: \\"Products\\")\\n}","breadcrumbs":"Advanced topics » Apollo Federation » @override","id":"126","title":"@override"},"127":{"body":"The @external directive is used to indicate that a field is usually provided by another subgraph, but is sometimes required by this subgraph (when combined with @requires) or provided by this subgraph (when combined with @provides). # extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(SimpleObject)]\\nstruct Product { id: ID, #[graphql(external)] name: String, in_stock: bool,\\n} Results in: type Product { id: ID! name: String! @external inStock: Boolean!\\n}","breadcrumbs":"Advanced topics » Apollo Federation » @external","id":"127","title":"@external"},"128":{"body":"The @provides directive is used to indicate that a field is provided by this subgraph, but only sometimes. # extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(SimpleObject)]\\nstruct Product { id: ID, #[graphql(external)] human_name: String, in_stock: bool,\\n} struct Query; #[Object]\\nimpl Query { /// This operation will provide the `humanName` field on `Product #[graphql(provides = \\"humanName\\")] async fn out_of_stock_products(&self) -> Vec { vec![Product { id: \\"1\\".into(), human_name: \\"My Product\\".to_string(), in_stock: false, }] } async fn discontinued_products(&self) -> Vec { vec![Product { id: \\"2\\".into(), human_name: String::new(), // This is ignored by the router in_stock: false, }] } #[graphql(entity)] async fn find_product_by_id(&self, id: ID) -> Product { Product { id, human_name: String::new(), // This is ignored by the router in_stock: true, } }\\n} Note that the #[graphql(provides)] attribute takes the field name as it appears in the schema, not the Rust field name. The resulting schema will look like this: type Product @key(fields: \\"id\\") { id: ID! humanName: String! @external inStock: Boolean!\\n} type Query { outOfStockProducts: [Product!]! @provides(fields: \\"humanName\\") discontinuedProducts: [Product!]!\\n}","breadcrumbs":"Advanced topics » Apollo Federation » @provides","id":"128","title":"@provides"},"129":{"body":"The @requires directive is used to indicate that an @external field is required for this subgraph to resolve some other field(s). If our shippingEstimate field requires the size and weightInPounts fields, then we might want a subgraph entity which looks like this: type Product @key(fields: \\"id\\") { id: ID! size: Int! @external weightInPounds: Int! @external shippingEstimate: String! @requires(fields: \\"size weightInPounds\\")\\n} In order to implement this in Rust, we can use the #[graphql(requires)] attribute: # extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(SimpleObject)]\\n#[graphql(complex)]\\nstruct Product { id: ID, #[graphql(external)] size: u32, #[graphql(external)] weight_in_pounds: u32,\\n} #[ComplexObject]\\nimpl Product { #[graphql(requires = \\"size weightInPounds\\")] async fn shipping_estimate(&self) -> String { let price = self.size * self.weight_in_pounds; format!(\\"${}\\", price) }\\n} Note that we use the GraphQL field name weightInPounds, not the Rust field name weight_in_pounds in requires. To populate those external fields, we add them as arguments in the entity resolver: # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(SimpleObject)]\\n# struct Product {\\n# id: ID,\\n# #[graphql(external)]\\n# size: u32,\\n# #[graphql(external)]\\n# weight_in_pounds: u32,\\n# }\\n# struct Query;\\n#[Object]\\nimpl Query { #[graphql(entity)] async fn find_product_by_id( &self, #[graphql(key)] id: ID, size: Option, weight_in_pounds: Option ) -> Product { Product { id, size: size.unwrap_or_default(), weight_in_pounds: weight_in_pounds.unwrap_or_default(), } }\\n} The inputs are Option<> even though the fields are required. This is because the external fields are only passed to the subgraph when the field(s) that require them are being selected. If the shippingEstimate field is not selected, then the size and weightInPounds fields will not be passed to the subgraph. Always use optional types for external fields. We have to put something in place for size and weight_in_pounds as they are still required fields on the type, so we use unwrap_or_default() to provide a default value. This looks a little funny, as we\'re populating the fields with nonsense values, but we have confidence that they will not be needed if they were not provided. Make sure to use @requires if you are consuming @external fields, or your code will be wrong.","breadcrumbs":"Advanced topics » Apollo Federation » @requires","id":"129","title":"@requires"},"13":{"body":"You can flatten fields by adding #[graphql(flatten)], i.e.: # extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(SimpleObject)]\\npub struct ChildObject { b: String, c: String,\\n} #[derive(SimpleObject)]\\npub struct ParentObject { a: String, #[graphql(flatten)] child: ChildObject,\\n} // Is the same as #[derive(SimpleObject)]\\npub struct Object { a: String, b: String, c: String,\\n}","breadcrumbs":"Type System » SimpleObject » Flatten fields","id":"13","title":"Flatten fields"},"130":{"body":"A case where the @requires directive can be confusing is when there are nested entities. For example, if we had an Order type which contained a Product, then we would need an entity resolver like this: # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(SimpleObject)]\\n# pub struct Order { id: ID }\\n# struct Query;\\n#[Object]\\nimpl Query { #[graphql(entity)] async fn find_order_by_id(&self, id: ID) -> Option { Some(Order { id }) }\\n} There are no inputs on this entity resolver, so how do we populate the size and weight_in_pounds fields on Product if a user has a query like order { product { shippingEstimate } }? The supergraph implementation will solve this for us by calling the find_product_by_id separately for any fields which have a @requires directive, so the subgraph code does not need to worry about how entities relate.","breadcrumbs":"Advanced topics » Apollo Federation » Nested @requires","id":"130","title":"Nested @requires"},"131":{"body":"The @tag directive is used to add metadata to a schema location for features like contracts . To add a tag like this: type User @tag(name: \\"team-accounts\\") { id: String! name: String!\\n} You can write code like this: # extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(SimpleObject)]\\n#[graphql(tag = \\"team-accounts\\")]\\nstruct User { id: ID, name: String,\\n}","breadcrumbs":"Advanced topics » Apollo Federation » @tag","id":"131","title":"@tag"},"132":{"body":"The @composeDirective directive is used to add a custom type system directive to the supergraph schema. Without @composeDirective, and custom type system directives are omitted from the composed supergraph schema. To include a custom type system directive as a composed directive, just add the composable attribute to the #[TypeDirective] macro: # extern crate async_graphql;\\n# use async_graphql::*;\\n#[TypeDirective( location = \\"Object\\", composable = \\"https://custom.spec.dev/extension/v1.0\\",\\n)]\\nfn custom() {} In addition to the normal type system directive behavior , this will add the following bits to the output schema: extend schema @link( url: \\"https://custom.spec.dev/extension/v1.0\\" import: [\\"@custom\\"]\\n) @composeDirective(name: \\"@custom\\")","breadcrumbs":"Advanced topics » Apollo Federation » @composeDirective","id":"132","title":"@composeDirective"},"14":{"body":"Different from SimpleObject, Object must have a resolver defined for each field in its impl. A resolver function has to be asynchronous. The first argument has to be &self, the second is an optional Context and it is followed by field arguments. The resolver is used to get the value of the field. For example, you can query a database and return the result. The return type of the function is the type of the field. You can also return a async_graphql::Result to return an error if it occurs. The error message will then be sent as query result. You may need access to global data in your query, for example a database connection pool. When creating your Schema, you can use SchemaBuilder::data to configure the global data, and Context::data to configure Context data. The following value_from_db function shows how to retrieve a database connection from Context. # extern crate async_graphql;\\n# struct Data { pub name: String }\\n# struct DbConn {}\\n# impl DbConn {\\n# fn query_something(&self, id: i64) -> std::result::Result { Ok(Data {name:\\"\\".into()})}\\n# }\\n# struct DbPool {}\\n# impl DbPool {\\n# fn take(&self) -> DbConn { DbConn {} } # }\\nuse async_graphql::*; struct MyObject { value: i32,\\n} #[Object]\\nimpl MyObject { async fn value(&self) -> String { self.value.to_string() } async fn value_from_db( &self, ctx: &Context<\'_>, #[graphql(desc = \\"Id of object\\")] id: i64 ) -> Result { let conn = ctx.data::()?.take(); Ok(conn.query_something(id)?.name) }\\n}","breadcrumbs":"Type System » Object » Object","id":"14","title":"Object"},"15":{"body":"The main goal of Context is to acquire global data attached to Schema and also data related to the actual query being processed.","breadcrumbs":"Type System » Object » Context » Context","id":"15","title":"Context"},"16":{"body":"Inside the Context you can put global data, like environment variables, db connection pool, whatever you may need in every query. The data must implement Send and Sync. You can request the data inside a query by just calling ctx.data::(). Note that if the return value of resolver function is borrowed from Context, you will need to explicitly state the lifetime of the argument. The following example shows how to borrow data in Context. # extern crate async_graphql;\\nuse async_graphql::*; struct Query; #[Object]\\nimpl Query { async fn borrow_from_context_data<\'ctx>( &self, ctx: &Context<\'ctx> ) -> Result<&\'ctx String> { ctx.data::() }\\n}","breadcrumbs":"Type System » Object » Context » Store Data","id":"16","title":"Store Data"},"17":{"body":"You can put data inside the context at the creation of the schema, it\'s useful for data that do not change, like a connection pool. An instance of how it would be written inside an application: # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(Default,SimpleObject)]\\n# struct Query { version: i32}\\n# struct EnvStruct;\\n# let env_struct = EnvStruct;\\n# struct S3Object;\\n# let s3_storage = S3Object;\\n# struct DBConnection;\\n# let db_core = DBConnection;\\nlet schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription) .data(env_struct) .data(s3_storage) .data(db_core) .finish();","breadcrumbs":"Type System » Object » Context » Schema data","id":"17","title":"Schema data"},"18":{"body":"You can put data inside the context at the execution of the request, it\'s useful for authentication data for instance. A little example with a warp route: # extern crate async_graphql;\\n# extern crate async_graphql_warp;\\n# extern crate warp;\\n# use async_graphql::*;\\n# use warp::{Filter, Reply};\\n# use std::convert::Infallible;\\n# #[derive(Default, SimpleObject)]\\n# struct Query { name: String }\\n# struct AuthInfo { pub token: Option }\\n# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();\\n# let schema_filter = async_graphql_warp::graphql(schema);\\nlet graphql_post = warp::post() .and(warp::path(\\"graphql\\")) .and(warp::header::optional(\\"Authorization\\")) .and(schema_filter) .and_then( |auth: Option, (schema, mut request): (Schema, async_graphql::Request)| async move { // Do something to get auth data from the header let your_auth_data = AuthInfo { token: auth }; let response = schema .execute( request .data(your_auth_data) ).await; Ok::<_, Infallible>(async_graphql_warp::GraphQLResponse::from(response)) });","breadcrumbs":"Type System » Object » Context » Request data","id":"18","title":"Request data"},"19":{"body":"With the Context you can also insert and appends headers. # extern crate async_graphql;\\n# extern crate http;\\n# use ::http::header::ACCESS_CONTROL_ALLOW_ORIGIN;\\n# use async_graphql::*;\\n# struct Query;\\n#[Object]\\nimpl Query { async fn greet(&self, ctx: &Context<\'_>) -> String { // Headers can be inserted using the `http` constants let was_in_headers = ctx.insert_http_header(ACCESS_CONTROL_ALLOW_ORIGIN, \\"*\\"); // They can also be inserted using &str let was_in_headers = ctx.insert_http_header(\\"Custom-Header\\", \\"1234\\"); // If multiple headers with the same key are `inserted` then the most recent // one overwrites the previous. If you want multiple headers for the same key, use // `append_http_header` for subsequent headers let was_in_headers = ctx.append_http_header(\\"Custom-Header\\", \\"Hello World\\"); String::from(\\"Hello world\\") }\\n}","breadcrumbs":"Type System » Object » Context » Headers","id":"19","title":"Headers"},"2":{"body":"Ensure that there is no CPU-heavy process in background! cd benchmark\\ncargo bench Now a HTML report is available at benchmark/target/criterion/report.","breadcrumbs":"Introduction » Benchmarks","id":"2","title":"Benchmarks"},"20":{"body":"Sometimes you want to know what fields are requested in the subquery to optimize the processing of data. You can read fields across the query with ctx.field() which will give you a SelectionField which will allow you to navigate across the fields and subfields. If you want to perform a search across the query or the subqueries, you do not have to do this by hand with the SelectionField, you can use the ctx.look_ahead() to perform a selection # extern crate async_graphql;\\nuse async_graphql::*; #[derive(SimpleObject)]\\nstruct Detail { c: i32, d: i32,\\n} #[derive(SimpleObject)]\\nstruct MyObj { a: i32, b: i32, detail: Detail,\\n} struct Query; #[Object]\\nimpl Query { async fn obj(&self, ctx: &Context<\'_>) -> MyObj { if ctx.look_ahead().field(\\"a\\").exists() { // This is a query like `obj { a }` } else if ctx.look_ahead().field(\\"detail\\").field(\\"c\\").exists() { // This is a query like `obj { detail { c } }` } else { // This query doesn\'t have `a` } unimplemented!() }\\n}","breadcrumbs":"Type System » Object » Context » Selection / LookAhead","id":"20","title":"Selection / LookAhead"},"21":{"body":"Resolve can return a Result, which has the following definition: type Result = std::result::Result; Any Error that implements std::fmt::Display can be converted to Error and you can extend the error message. The following example shows how to parse an input string to an integer. When parsing fails, it will return an error and attach an error message. See the Error Extensions section of this book for more details. # extern crate async_graphql;\\n# use std::num::ParseIntError;\\nuse async_graphql::*; struct Query; #[Object]\\nimpl Query { async fn parse_with_extensions(&self, input: String) -> Result { Ok(\\"234a\\" .parse() .map_err(|err: ParseIntError| err.extend_with(|_, e| e.set(\\"code\\", 400)))?) }\\n} Errors in subscriptions Errors can be returned from subscription resolvers as well, using a return type of the form: async fn my_subscription_resolver(&self) -> impl Stream> { ... } Note however that the MyError struct must have Clone implemented, due to the restrictions placed by the Subscription macro. One way to accomplish this is by creating a custom error type, with #[derive(Clone)], as seen here .","breadcrumbs":"Type System » Object » Error handling » Error handling","id":"21","title":"Error handling"},"22":{"body":"Usually we can create multiple implementations for the same type in Rust, but due to the limitation of procedural macros, we can not create multiple Object implementations for the same type. For example, the following code will fail to compile. #[Object]\\nimpl Query { async fn users(&self) -> Vec { todo!() }\\n} #[Object]\\nimpl Query { async fn movies(&self) -> Vec { todo!() }\\n} Instead, the #[derive(MergedObject)] macro allows you to split an object\'s resolvers across multiple modules or files by merging 2 or more #[Object] implementations into one. Tip: Every #[Object] needs a unique name, even in a MergedObject, so make sure to give each object you\'re merging its own name. Note: This works for queries and mutations. For subscriptions, see \\"Merging Subscriptions\\" below. # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(SimpleObject)]\\n# struct User { a: i32 }\\n# #[derive(SimpleObject)]\\n# struct Movie { a: i32 }\\n#[derive(Default)]\\nstruct UserQuery; #[Object]\\nimpl UserQuery { async fn users(&self) -> Vec { todo!() }\\n} #[derive(Default)]\\nstruct MovieQuery; #[Object]\\nimpl MovieQuery { async fn movies(&self) -> Vec { todo!() }\\n} #[derive(MergedObject, Default)]\\nstruct Query(UserQuery, MovieQuery); let schema = Schema::new( Query::default(), EmptyMutation, EmptySubscription\\n); ⚠️ MergedObject cannot be used in Interface.","breadcrumbs":"Type System » Object » Merging Objects / Subscriptions » Merging Objects","id":"22","title":"Merging Objects"},"23":{"body":"Along with MergedObject, you can derive MergedSubscription or use #[MergedSubscription] to merge separate #[Subscription] blocks. Like merging Objects, each subscription block requires a unique name. Example: # extern crate async_graphql;\\n# use async_graphql::*;\\n# use futures_util::stream::{Stream};\\n# #[derive(Default,SimpleObject)]\\n# struct Query { a: i32 }\\n#[derive(Default)]\\nstruct Subscription1; #[Subscription]\\nimpl Subscription1 { async fn events1(&self) -> impl Stream { futures_util::stream::iter(0..10) }\\n} #[derive(Default)]\\nstruct Subscription2; #[Subscription]\\nimpl Subscription2 { async fn events2(&self) -> impl Stream { futures_util::stream::iter(10..20) }\\n} #[derive(MergedSubscription, Default)]\\nstruct Subscription(Subscription1, Subscription2); let schema = Schema::new( Query::default(), EmptyMutation, Subscription::default()\\n);","breadcrumbs":"Type System » Object » Merging Objects / Subscriptions » Merging Subscriptions","id":"23","title":"Merging Subscriptions"},"24":{"body":"Sometimes two fields have the same query logic, but the output type is different. In async-graphql, you can create a derived field for it. In the following example, you already have a date_rfc2822 field outputting the time format in RFC2822 format, and then reuse it to derive a new date_rfc3339 field. # extern crate chrono;\\n# use chrono::Utc;\\n# extern crate async_graphql;\\n# use async_graphql::*;\\nstruct DateRFC3339(chrono::DateTime);\\nstruct DateRFC2822(chrono::DateTime); #[Scalar]\\nimpl ScalarType for DateRFC3339 { fn parse(value: Value) -> InputValueResult { todo!() } fn to_value(&self) -> Value { Value::String(self.0.to_rfc3339()) }\\n} #[Scalar]\\nimpl ScalarType for DateRFC2822 { fn parse(value: Value) -> InputValueResult { todo!() } fn to_value(&self) -> Value { Value::String(self.0.to_rfc2822()) }\\n} impl From for DateRFC3339 { fn from(value: DateRFC2822) -> Self { DateRFC3339(value.0) }\\n} struct Query; #[Object]\\nimpl Query { #[graphql(derived(name = \\"date_rfc3339\\", into = \\"DateRFC3339\\"))] async fn date_rfc2822(&self, arg: String) -> DateRFC2822 { todo!() }\\n} It will render a GraphQL like: type Query { date_rfc2822(arg: String): DateRFC2822! date_rfc3339(arg: String): DateRFC3339!\\n}","breadcrumbs":"Type System » Object » Derived fields » Derived fields","id":"24","title":"Derived fields"},"25":{"body":"A derived field won\'t be able to manage everything easily: Rust\'s orphan rule requires that either the trait or the type for which you are implementing the trait must be defined in the same crate as the impl, so the following code cannot be compiled: impl From> for Vec { ...\\n} So you wouldn\'t be able to generate derived fields for existing wrapper type structures like Vec or Option. But when you implement a From for T you should be able to derived a From> for Vec and a From> for Option. We included a with parameter to help you define a function to call instead of using the Into trait implementation between wrapper structures.","breadcrumbs":"Type System » Object » Derived fields » Wrapper types","id":"25","title":"Wrapper types"},"26":{"body":"# extern crate serde;\\n# use serde::{Serialize, Deserialize};\\n# extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(Serialize, Deserialize, Clone)]\\nstruct ValueDerived(String); #[derive(Serialize, Deserialize, Clone)]\\nstruct ValueDerived2(String); scalar!(ValueDerived);\\nscalar!(ValueDerived2); impl From for ValueDerived2 { fn from(value: ValueDerived) -> Self { ValueDerived2(value.0) }\\n} fn option_to_option>(value: Option) -> Option { value.map(|x| x.into())\\n} #[derive(SimpleObject)]\\nstruct TestObj { #[graphql(derived(owned, name = \\"value2\\", into = \\"Option\\", with = \\"option_to_option\\"))] pub value1: Option,\\n}","breadcrumbs":"Type System » Object » Derived fields » Example","id":"26","title":"Example"},"27":{"body":"It\'s easy to define an Enum, here we have an example: Async-graphql will automatically change the name of each item to GraphQL\'s CONSTANT_CASE convention. You can use name to rename. # extern crate async_graphql;\\nuse async_graphql::*; /// One of the films in the Star Wars Trilogy\\n#[derive(Enum, Copy, Clone, Eq, PartialEq)]\\npub enum Episode { /// Released in 1977. NewHope, /// Released in 1980. Empire, /// Released in 1983. #[graphql(name=\\"AAA\\")] Jedi,\\n}","breadcrumbs":"Type System » Enum » Enum","id":"27","title":"Enum"},"28":{"body":"Rust\'s orphan rule requires that either the trait or the type for which you are implementing the trait must be defined in the same crate as the impl, so you cannot expose remote enumeration types to GraphQL. In order to provide an Enum type, a common workaround is to create a new enum that has parity with the existing, remote enum type. # extern crate async_graphql;\\n# mod remote_crate { pub enum RemoteEnum { A, B, C } }\\nuse async_graphql::*; /// Provides parity with a remote enum type\\n#[derive(Enum, Copy, Clone, Eq, PartialEq)]\\npub enum LocalEnum { A, B, C,\\n} /// Conversion interface from remote type to our local GraphQL enum type\\nimpl From for LocalEnum { fn from(e: remote_crate::RemoteEnum) -> Self { match e { remote_crate::RemoteEnum::A => Self::A, remote_crate::RemoteEnum::B => Self::B, remote_crate::RemoteEnum::C => Self::C, } }\\n} The process is tedious and requires multiple steps to keep the local and remote enums in sync. Async_graphql provides a handy feature to generate the From for LocalEnum as well as an opposite direction of From for remote_crate::RemoteEnum via an additional attribute after deriving Enum: # extern crate async_graphql;\\n# use async_graphql::*;\\n# mod remote_crate { pub enum RemoteEnum { A, B, C } }\\n#[derive(Enum, Copy, Clone, Eq, PartialEq)]\\n#[graphql(remote = \\"remote_crate::RemoteEnum\\")]\\nenum LocalEnum { A, B, C,\\n}","breadcrumbs":"Type System » Enum » Wrapping a remote enum","id":"28","title":"Wrapping a remote enum"},"29":{"body":"Interface is used to abstract Objects with common fields. Async-graphql implements it as a wrapper. The wrapper will forward field resolution to the Object that implements this Interface. Therefore, the Object\'s fields\' type and arguments must match with the Interface\'s. Async-graphql implements auto conversion from Object to Interface, you only need to call Into::into. Interface field names are transformed to camelCase for the schema definition. If you need e.g. a snake_cased GraphQL field name, you can use both the name and method attributes. When name and method exist together, name is the GraphQL field name and the method is the resolver function name. When only name exists, name.to_camel_case() is the GraphQL field name and the name is the resolver function name. # extern crate async_graphql;\\nuse async_graphql::*; struct Circle { radius: f32,\\n} #[Object]\\nimpl Circle { async fn area(&self) -> f32 { std::f32::consts::PI * self.radius * self.radius } async fn scale(&self, s: f32) -> Shape { Circle { radius: self.radius * s }.into() } #[graphql(name = \\"short_description\\")] async fn short_description(&self) -> String { \\"Circle\\".to_string() }\\n} struct Square { width: f32,\\n} #[Object]\\nimpl Square { async fn area(&self) -> f32 { self.width * self.width } async fn scale(&self, s: f32) -> Shape { Square { width: self.width * s }.into() } #[graphql(name = \\"short_description\\")] async fn short_description(&self) -> String { \\"Square\\".to_string() }\\n} #[derive(Interface)]\\n#[graphql( field(name = \\"area\\", ty = \\"f32\\"), field(name = \\"scale\\", ty = \\"Shape\\", arg(name = \\"s\\", ty = \\"f32\\")), field(name = \\"short_description\\", method = \\"short_description\\", ty = \\"String\\")\\n)]\\nenum Shape { Circle(Circle), Square(Square),\\n}","breadcrumbs":"Type System » Interface » Interface","id":"29","title":"Interface"},"3":{"body":"","breadcrumbs":"Quickstart » Quickstart","id":"3","title":"Quickstart"},"30":{"body":"Async-graphql traverses and registers all directly or indirectly referenced types from Schema in the initialization phase. If an interface is not referenced, it will not exist in the registry, as in the following example , even if MyObject implements MyInterface, because MyInterface is not referenced in Schema, the MyInterface type will not exist in the registry. # extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(Interface)]\\n#[graphql( field(name = \\"name\\", ty = \\"String\\"),\\n)]\\nenum MyInterface { MyObject(MyObject),\\n} #[derive(SimpleObject)]\\nstruct MyObject { name: String,\\n} struct Query; #[Object]\\nimpl Query { async fn obj(&self) -> MyObject { todo!() }\\n} type MySchema = Schema; You need to manually register the MyInterface type when constructing the Schema: # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(Interface)]\\n# #[graphql(field(name = \\"name\\", ty = \\"String\\"))]\\n# enum MyInterface { MyObject(MyObject) }\\n# #[derive(SimpleObject)]\\n# struct MyObject { name: String, }\\n# struct Query;\\n# #[Object]\\n# impl Query { async fn version(&self) -> &str { \\"1.0\\" } } Schema::build(Query, EmptyMutation, EmptySubscription) .register_output_type::() .finish();","breadcrumbs":"Type System » Interface » Register the interface manually","id":"30","title":"Register the interface manually"},"31":{"body":"The definition of a Union is similar to an Interface, but with no fields allowed. . The implementation is quite similar for Async-graphql; from Async-graphql\'s perspective, Union is a subset of Interface. The following example modified the definition of Interface a little bit and removed fields. # extern crate async_graphql;\\nuse async_graphql::*; struct Circle { radius: f32,\\n} #[Object]\\nimpl Circle { async fn area(&self) -> f32 { std::f32::consts::PI * self.radius * self.radius } async fn scale(&self, s: f32) -> Shape { Circle { radius: self.radius * s }.into() }\\n} struct Square { width: f32,\\n} #[Object]\\nimpl Square { async fn area(&self) -> f32 { self.width * self.width } async fn scale(&self, s: f32) -> Shape { Square { width: self.width * s }.into() }\\n} #[derive(Union)]\\nenum Shape { Circle(Circle), Square(Square),\\n}","breadcrumbs":"Type System » Union » Union","id":"31","title":"Union"},"32":{"body":"A restriction in GraphQL is the inability to create a union type out of other union types. All members must be Object. To support nested unions, we can \\"flatten\\" members that are unions, bringing their members up into the parent union. This is done by applying #[graphql(flatten)] on each member we want to flatten. # extern crate async_graphql;\\n#[derive(async_graphql::Union)]\\npub enum TopLevelUnion { A(A), // Will fail to compile unless we flatten the union member #[graphql(flatten)] B(B),\\n} #[derive(async_graphql::SimpleObject)]\\npub struct A { a: i32, // ...\\n} #[derive(async_graphql::Union)]\\npub enum B { C(C), D(D),\\n} #[derive(async_graphql::SimpleObject)]\\npub struct C { c: i32, // ...\\n} #[derive(async_graphql::SimpleObject)]\\npub struct D { d: i32, // ...\\n} The above example transforms the top-level union into this equivalent: # extern crate async_graphql;\\n# #[derive(async_graphql::SimpleObject)]\\n# struct A { a: i32 }\\n# #[derive(async_graphql::SimpleObject)]\\n# struct C { c: i32 }\\n# #[derive(async_graphql::SimpleObject)]\\n# struct D { d: i32 }\\n#[derive(async_graphql::Union)]\\npub enum TopLevelUnion { A(A), C(C), D(D),\\n}","breadcrumbs":"Type System » Union » Flattening nested unions","id":"32","title":"Flattening nested unions"},"33":{"body":"You can use an Object as an argument, and GraphQL calls it an InputObject. The definition of InputObject is similar to SimpleObject , but SimpleObject can only be used as output and InputObject can only be used as input. You can add optional #[graphql] attributes to add descriptions or rename the field. # extern crate async_graphql;\\n# #[derive(SimpleObject)]\\n# struct User { a: i32 }\\nuse async_graphql::*; #[derive(InputObject)]\\nstruct Coordinate { latitude: f64, longitude: f64\\n} struct Mutation; #[Object]\\nimpl Mutation { async fn users_at_location(&self, coordinate: Coordinate, radius: f64) -> Vec { // Writes coordination to database. // ...\\n# todo!() }\\n}","breadcrumbs":"Type System » InputObject » InputObject","id":"33","title":"InputObject"},"34":{"body":"If you want to reuse an InputObject for other types, you can define a generic InputObject and specify how its concrete types should be implemented. In the following example, two InputObject types are created: # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(InputObject)]\\n# struct SomeType { a: i32 }\\n# #[derive(InputObject)]\\n# struct SomeOtherType { a: i32 }\\n#[derive(InputObject)]\\n#[graphql(concrete(name = \\"SomeName\\", params(SomeType)))]\\n#[graphql(concrete(name = \\"SomeOtherName\\", params(SomeOtherType)))]\\npub struct SomeGenericInput { field1: Option, field2: String\\n} Note: Each generic parameter must implement InputType, as shown above. The schema generated is: input SomeName { field1: SomeType field2: String!\\n} input SomeOtherName { field1: SomeOtherType field2: String!\\n} In your resolver method or field of another input object, use as a normal generic type: # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(InputObject)]\\n# struct SomeType { a: i32 }\\n# #[derive(InputObject)]\\n# struct SomeOtherType { a: i32 }\\n# #[derive(InputObject)]\\n# #[graphql(concrete(name = \\"SomeName\\", params(SomeType)))]\\n# #[graphql(concrete(name = \\"SomeOtherName\\", params(SomeOtherType)))]\\n# pub struct SomeGenericInput {\\n# field1: Option,\\n# field2: String\\n# }\\n#[derive(InputObject)]\\npub struct YetAnotherInput { a: SomeGenericInput, b: SomeGenericInput,\\n} You can pass multiple generic types to params(), separated by a comma. If you also want to implement OutputType, then you will need to explicitly declare the input and output type names of the concrete types like so: # extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(SimpleObject, InputObject)]\\n#[graphql(concrete( name = \\"SomeGenericTypeOut\\", input_name = \\"SomeGenericTypeIn\\", params(i32),\\n))]\\npub struct SomeGenericType { field1: Option, field2: String\\n}","breadcrumbs":"Type System » InputObject » Generic InputObjects","id":"34","title":"Generic InputObjects"},"35":{"body":"If any part of your input is considered sensitive and you wish to redact it, you can mark it with secret directive. For example: # extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(InputObject)]\\npub struct CredentialsInput { username: String, #[graphql(secret)] password: String,\\n}","breadcrumbs":"Type System » InputObject » Redacting sensitive data","id":"35","title":"Redacting sensitive data"},"36":{"body":"You can add #[graphql(flatten)] to a field to inline keys from the field type into it\'s parent. For example: # extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(InputObject)]\\npub struct ChildInput { b: String, c: String,\\n} #[derive(InputObject)]\\npub struct ParentInput { a: String, #[graphql(flatten)] child: ChildInput,\\n} // Is the same as #[derive(InputObject)]\\npub struct Input { a: String, b: String, c: String,\\n}","breadcrumbs":"Type System » InputObject » Flattening fields","id":"36","title":"Flattening fields"},"37":{"body":"A OneofObject is a special type of InputObject, in which only one of its fields must be set and is not-null. It is especially useful when you want a user to be able to choose between several potential input types. This feature is still an RFC and therefore not yet officially part of the GraphQL spec, but Async-graphql already supports it! # extern crate async_graphql;\\n# #[derive(SimpleObject)]\\n# struct User { a: i32 }\\nuse async_graphql::*; #[derive(OneofObject)]\\nenum UserBy { Email(String), RegistrationNumber(i64), Address(Address)\\n} #[derive(InputObject)]\\nstruct Address { street: String, house_number: String, city: String, zip: String,\\n} struct Query {} #[Object]\\nimpl Query { async fn search_users(&self, by: Vec) -> Vec { // ... Searches and returns a list of users ...\\n# todo!() }\\n} As you can see, a OneofObject is represented by an enum in which each variant contains another InputType. This means that you can use InputObject as variant too.","breadcrumbs":"Type System » OneofObject » OneofObject","id":"37","title":"OneofObject"},"38":{"body":"You can define default values for input value types. Below are some examples.","breadcrumbs":"Type System » Default value » Default value","id":"38","title":"Default value"},"39":{"body":"# extern crate async_graphql;\\nuse async_graphql::*; struct Query; fn my_default() -> i32 { 30\\n} #[Object]\\nimpl Query { // The default value of the value parameter is 0, it will call i32::default() async fn test1(&self, #[graphql(default)] value: i32) -> i32 { todo!() } // The default value of the value parameter is 10 async fn test2(&self, #[graphql(default = 10)] value: i32) -> i32 { todo!() } // The default value of the value parameter uses the return result of the my_default function, the value is 30. async fn test3(&self, #[graphql(default_with = \\"my_default()\\")] value: i32) -> i32 { todo!() }\\n}","breadcrumbs":"Type System » Default value » Object field","id":"39","title":"Object field"},"4":{"body":"[dependencies]\\nasync-graphql = \\"4.0\\"\\nasync-graphql-actix-web = \\"4.0\\" # If you need to integrate into actix-web\\nasync-graphql-warp = \\"4.0\\" # If you need to integrate into warp\\nasync-graphql-tide = \\"4.0\\" # If you need to integrate into tide","breadcrumbs":"Quickstart » Add dependency libraries","id":"4","title":"Add dependency libraries"},"40":{"body":"# extern crate async_graphql;\\n# fn my_default() -> i32 { 5 }\\n# struct MyObj;\\n# #[Object]\\n# impl MyObj {\\n# async fn test1(&self, value: i32) -> i32 { todo!() }\\n# async fn test2(&self, value: i32) -> i32 { todo!() }\\n# async fn test3(&self, value: i32) -> i32 { todo!() }\\n# }\\nuse async_graphql::*; #[derive(Interface)]\\n#[graphql( field(name = \\"test1\\", ty = \\"i32\\", arg(name = \\"value\\", ty = \\"i32\\", default)), field(name = \\"test2\\", ty = \\"i32\\", arg(name = \\"value\\", ty = \\"i32\\", default = 10)), field(name = \\"test3\\", ty = \\"i32\\", arg(name = \\"value\\", ty = \\"i32\\", default_with = \\"my_default()\\")),\\n)]\\nenum MyInterface { MyObj(MyObj),\\n}","breadcrumbs":"Type System » Default value » Interface field","id":"40","title":"Interface field"},"41":{"body":"# extern crate async_graphql;\\n# fn my_default() -> i32 { 5 }\\nuse async_graphql::*; #[derive(InputObject)]\\nstruct MyInputObject { #[graphql(default)] value1: i32, #[graphql(default = 10)] value2: i32, #[graphql(default_with = \\"my_default()\\")] value3: i32,\\n}","breadcrumbs":"Type System » Default value » Input object field","id":"41","title":"Input object field"},"42":{"body":"It is possible to define reusable objects using generics; however each concrete instantiation of a generic object must be given a unique GraphQL type name. There are two ways of specifying these concrete names: concrete instantiation and the TypeName trait.","breadcrumbs":"Type System » Generics » Generics","id":"42","title":"Generics"},"43":{"body":"In the following example, two SimpleObject types are created: # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(SimpleObject)]\\n# struct SomeType { a: i32 }\\n# #[derive(SimpleObject)]\\n# struct SomeOtherType { a: i32 }\\n#[derive(SimpleObject)]\\n#[graphql(concrete(name = \\"SomeName\\", params(SomeType)))]\\n#[graphql(concrete(name = \\"SomeOtherName\\", params(SomeOtherType)))]\\npub struct SomeGenericObject { field1: Option, field2: String\\n} Note: Each generic parameter must implement OutputType, as shown above. The schema generated is: # SomeGenericObject\\ntype SomeName { field1: SomeType field2: String!\\n} # SomeGenericObject\\ntype SomeOtherName { field1: SomeOtherType field2: String!\\n} In your resolver method or field of another object, use as a normal generic type: # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(SimpleObject)]\\n# struct SomeType { a: i32 }\\n# #[derive(SimpleObject)]\\n# struct SomeOtherType { a: i32 }\\n# #[derive(SimpleObject)]\\n# #[graphql(concrete(name = \\"SomeName\\", params(SomeType)))]\\n# #[graphql(concrete(name = \\"SomeOtherName\\", params(SomeOtherType)))]\\n# pub struct SomeGenericObject {\\n# field1: Option,\\n# field2: String,\\n# }\\n#[derive(SimpleObject)]\\npub struct YetAnotherObject { a: SomeGenericObject, b: SomeGenericObject,\\n} You can pass multiple generic types to params(), separated by a comma.","breadcrumbs":"Type System » Generics » Concrete Instantiation","id":"43","title":"Concrete Instantiation"},"44":{"body":"Some type names can be derived. # extern crate async_graphql;\\n# use async_graphql::*;\\n# use std::borrow::Cow;\\n#[derive(SimpleObject)]\\n#[graphql(name_type)] // Use `TypeName` trait\\nstruct Bag { content: Vec, len: usize,\\n} impl TypeName for Bag { fn type_name() -> Cow<\'static, str> { format!(\\"{}Bag\\", ::type_name()).into() }\\n} Using bool and String the generated schema is: # Bag\\ntype BooleanBag { content: [Boolean!]! len: Int!\\n} # Bag\\ntype StringBag { content: [String!]! len: Int!\\n}","breadcrumbs":"Type System » Generics » TypeName trait","id":"44","title":"TypeName trait"},"45":{"body":"After defining the basic types, you need to define a schema to combine them. The schema consists of three types: a query object, a mutation object, and a subscription object, where the mutation object and subscription object are optional. When the schema is created, Async-graphql will traverse all object graphs and register all types. This means that if a GraphQL object is defined but never referenced, this object will not be exposed in the schema.","breadcrumbs":"Schema » Schema","id":"45","title":"Schema"},"46":{"body":"","breadcrumbs":"Schema » Query and Mutation » Query and Mutation","id":"46","title":"Query and Mutation"},"47":{"body":"The query root object is a GraphQL object with a definition similar to other objects. Resolver functions for all fields of the query object are executed concurrently. # extern crate async_graphql;\\nuse async_graphql::*;\\n# #[derive(SimpleObject)]\\n# struct User { a: i32 } struct Query; #[Object]\\nimpl Query { async fn user(&self, username: String) -> Result> { // Look up users from the database\\n# todo!() }\\n}","breadcrumbs":"Schema » Query and Mutation » Query root object","id":"47","title":"Query root object"},"48":{"body":"The mutation root object is also a GraphQL object, but it executes sequentially. One mutation following from another will only be executed only after the first mutation is completed. The following mutation root object provides an example of user registration and login: # extern crate async_graphql;\\nuse async_graphql::*; struct Mutation; #[Object]\\nimpl Mutation { async fn signup(&self, username: String, password: String) -> Result { // User signup\\n# todo!() } async fn login(&self, username: String, password: String) -> Result { // User login (generate token)\\n# todo!() }\\n}","breadcrumbs":"Schema » Query and Mutation » Mutation root object","id":"48","title":"Mutation root object"},"49":{"body":"The definition of the subscription root object is slightly different from other root objects. Its resolver function always returns a Stream or Result, and the field parameters are usually used as data filtering conditions. The following example subscribes to an integer stream, which generates one integer per second. The parameter step specifies the integer step size with a default of 1. # extern crate async_graphql;\\n# use std::time::Duration;\\n# use async_graphql::futures_util::stream::Stream;\\n# use async_graphql::futures_util::StreamExt;\\n# extern crate tokio_stream;\\n# extern crate tokio;\\nuse async_graphql::*; struct Subscription; #[Subscription]\\nimpl Subscription { async fn integers(&self, #[graphql(default = 1)] step: i32) -> impl Stream { let mut value = 0; tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(Duration::from_secs(1))) .map(move |_| { value += step; value }) }\\n}","breadcrumbs":"Schema » Subscription » Subscription","id":"49","title":"Subscription"},"5":{"body":"The Schema of a GraphQL contains a required Query, an optional Mutation, and an optional Subscription. These object types are described using the structure of the Rust language. The field of the structure corresponds to the field of the GraphQL object. Async-graphql implements the mapping of common data types to GraphQL types, such as i32, f64, Option, Vec, etc. Also, you can extend these base types , which are called scalars in the GraphQL. Here is a simple example where we provide just one query that returns the sum of a and b. # extern crate async_graphql;\\nuse async_graphql::*; struct Query; #[Object]\\nimpl Query { /// Returns the sum of a and b async fn add(&self, a: i32, b: i32) -> i32 { a + b }\\n}","breadcrumbs":"Quickstart » Write a Schema","id":"5","title":"Write a Schema"},"50":{"body":"You can export your schema in Schema Definition Language (SDL) by using the Schema::sdl() method. # extern crate async_graphql;\\nuse async_graphql::*; struct Query; #[Object]\\nimpl Query { async fn add(&self, u: i32, v: i32) -> i32 { u + v }\\n} let schema = Schema::build(Query, EmptyMutation, EmptySubscription).finish(); // Print the schema in SDL format\\nprintln!(\\"{}\\", &schema.sdl());","breadcrumbs":"Schema » SDL Export » SDL Export","id":"50","title":"SDL Export"},"51":{"body":"","breadcrumbs":"Utilities » Utilities","id":"51","title":"Utilities"},"52":{"body":"You can define a guard for the fields of Object, SimpleObject, ComplexObject and Subscription, it will be executed before calling the resolver function, and an error will be returned if it fails. # extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(Eq, PartialEq, Copy, Clone)]\\nenum Role { Admin, Guest,\\n} struct RoleGuard { role: Role,\\n} impl RoleGuard { fn new(role: Role) -> Self { Self { role } }\\n} impl Guard for RoleGuard { async fn check(&self, ctx: &Context<\'_>) -> Result<()> { if ctx.data_opt::() == Some(&self.role) { Ok(()) } else { Err(\\"Forbidden\\".into()) } }\\n} Use it with the guard attribute: # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(Eq, PartialEq, Copy, Clone)]\\n# enum Role { Admin, Guest, }\\n# struct RoleGuard { role: Role, }\\n# impl RoleGuard { fn new(role: Role) -> Self { Self { role } } }\\n# impl Guard for RoleGuard { async fn check(&self, ctx: &Context<\'_>) -> Result<()> { todo!() } }\\n#[derive(SimpleObject)]\\nstruct Query { /// Only allow Admin #[graphql(guard = \\"RoleGuard::new(Role::Admin)\\")] value1: i32, /// Allow Admin or Guest #[graphql(guard = \\"RoleGuard::new(Role::Admin).or(RoleGuard::new(Role::Guest))\\")] value2: i32,\\n}","breadcrumbs":"Utilities » Field guard » Field Guard","id":"52","title":"Field Guard"},"53":{"body":"Sometimes guards need to use field parameters, you need to pass the parameter value when creating the guard like this: # extern crate async_graphql;\\n# use async_graphql::*;\\nstruct EqGuard { expect: i32, actual: i32,\\n} impl EqGuard { fn new(expect: i32, actual: i32) -> Self { Self { expect, actual } }\\n} impl Guard for EqGuard { async fn check(&self, _ctx: &Context<\'_>) -> Result<()> { if self.expect != self.actual { Err(\\"Forbidden\\".into()) } else { Ok(()) } }\\n} struct Query; #[Object]\\nimpl Query { #[graphql(guard = \\"EqGuard::new(100, value)\\")] async fn get(&self, value: i32) -> i32 { value }\\n}","breadcrumbs":"Utilities » Field guard » Use parameter value","id":"53","title":"Use parameter value"},"54":{"body":"Async-graphql has some common validators built-in, you can use them on the parameters of object fields or on the fields of InputObject. maximum=N the number cannot be greater than N. minimum=N the number cannot be less than N. multiple_of=N the number must be a multiple of N. max_items=N the length of the list cannot be greater than N. min_items=N the length of the list cannot be less than N. max_length=N the length of the string cannot be greater than N. min_length=N the length of the string cannot be less than N. chars_max_length=N the count of the unicode chars cannot be greater than N. chars_min_length=N the count of the unicode chars cannot be less than N. email is valid email. url is valid url. ip is valid ip address. regex=RE is match for the regex. uuid=V the string or ID is a valid UUID with version V. You may omit V to accept any UUID version. # extern crate async_graphql;\\nuse async_graphql::*; struct Query; #[Object]\\nimpl Query { /// The length of the name must be greater than or equal to 5 and less than or equal to 10. async fn input(&self, #[graphql(validator(min_length = 5, max_length = 10))] name: String) -> Result {\\n# todo!() }\\n}","breadcrumbs":"Utilities » Input value validators » Input value validators","id":"54","title":"Input value validators"},"55":{"body":"You can enable the list attribute, and the validator will check all members in list: # extern crate async_graphql;\\nuse async_graphql::*; struct Query; #[Object]\\nimpl Query { async fn input(&self, #[graphql(validator(list, max_length = 10))] names: Vec) -> Result {\\n# todo!() }\\n}","breadcrumbs":"Utilities » Input value validators » Check every member of the list","id":"55","title":"Check every member of the list"},"56":{"body":"# extern crate async_graphql;\\n# use async_graphql::*;\\nstruct MyValidator { expect: i32,\\n} impl MyValidator { pub fn new(n: i32) -> Self { MyValidator { expect: n } }\\n} impl CustomValidator for MyValidator { fn check(&self, value: &i32) -> Result<(), InputValueError> { if *value == self.expect { Ok(()) } else { Err(InputValueError::custom(format!(\\"expect 100, actual {}\\", value))) } }\\n} struct Query; #[Object]\\nimpl Query { /// n must be equal to 100 async fn value( &self, #[graphql(validator(custom = \\"MyValidator::new(100)\\"))] n: i32, ) -> i32 { n }\\n}","breadcrumbs":"Utilities » Input value validators » Custom validator","id":"56","title":"Custom validator"},"57":{"body":"Production environments often rely on caching to improve performance. A GraphQL query will call multiple resolver functions and each resolver can have a different cache definition. Some may cache for a few seconds, some may cache for a few hours, some may be the same for all users, and some may be different for each session. Async-graphql provides a mechanism that allows you to define the cache time and scope for each resolver. You can define cache parameters on the object or on its fields. The following example shows two uses of cache control parameters. You can use max_age parameters to control the age of the cache (in seconds), and you can also use public and private to control the scope of the cache. When you do not specify it, the scope will default to public. when querying multiple resolvers, the results of all cache control parameters will be combined and the max_age minimum value will be taken. If the scope of any object or field is private, the result will be private. We can use QueryResponse to get a merged cache control result from a query result, and call CacheControl::value to get the corresponding HTTP header. # extern crate async_graphql;\\n# use async_graphql::*;\\n# struct Query;\\n#[Object(cache_control(max_age = 60))]\\nimpl Query { #[graphql(cache_control(max_age = 30))] async fn value1(&self) -> i32 { 1 } #[graphql(cache_control(private))] async fn value2(&self) -> i32 { 2 } async fn value3(&self) -> i32 { 3 }\\n} The following are different queries corresponding to different cache control results: # max_age=30\\n{ value1 } # max_age=30, private\\n{ value1 value2 } # max_age=60\\n{ value3 }","breadcrumbs":"Utilities » Cache control » Cache control","id":"57","title":"Cache control"},"58":{"body":"Relay\'s cursor connection specification is designed to provide a consistent method for query paging. For more details on the specification see the GraphQL Cursor Connections Specification 。 Defining a cursor connection in async-graphql is very simple, you just call the connection::query function and query data in the closure. # extern crate async_graphql;\\nuse async_graphql::*;\\nuse async_graphql::types::connection::*; struct Query; #[Object]\\nimpl Query { async fn numbers(&self, after: Option, before: Option, first: Option, last: Option, ) -> Result> { query(after, before, first, last, |after, before, first, last| async move { let mut start = after.map(|after| after + 1).unwrap_or(0); let mut end = before.unwrap_or(10000); if let Some(first) = first { end = (start + first).min(end); } if let Some(last) = last { start = if last > end - start { end } else { end - last }; } let mut connection = Connection::new(start > 0, end < 10000); connection.edges.extend( (start..end).into_iter().map(|n| Edge::with_additional_fields(n, n as i32, EmptyFields) )); Ok::<_, async_graphql::Error>(connection) }).await }\\n}","breadcrumbs":"Utilities » Cursor connections » Cursor connections","id":"58","title":"Cursor connections"},"59":{"body":"To quote the graphql-spec : GraphQL services may provide an additional entry to errors with key extensions. This entry, if set, must have a map as its value. This entry is reserved for implementer to add additional information to errors however they see fit, and there are no additional restrictions on its contents.","breadcrumbs":"Utilities » Error extensions » Error extensions","id":"59","title":"Error extensions"},"6":{"body":"In our example, there is only a Query without a Mutation or Subscription, so we create the Schema with EmptyMutation and EmptySubscription, and then call Schema::execute to execute the Query. # extern crate async_graphql;\\n# use async_graphql::*;\\n#\\n# struct Query;\\n# #[Object]\\n# impl Query {\\n# async fn version(&self) -> &str { \\"1.0\\" } # }\\n# async fn other() {\\nlet schema = Schema::new(Query, EmptyMutation, EmptySubscription);\\nlet res = schema.execute(\\"{ add(a: 10, b: 20) }\\").await;\\n# }","breadcrumbs":"Quickstart » Execute the query","id":"6","title":"Execute the query"},"60":{"body":"I would recommend on checking out this async-graphql example as a quickstart.","breadcrumbs":"Utilities » Error extensions » Example","id":"60","title":"Example"},"61":{"body":"In async-graphql all user-facing errors are cast to the Error type which by default provides the error message exposed by std::fmt::Display. However, Error actually provides an additional information that can extend the error. A resolver looks like this: # extern crate async_graphql;\\n# use async_graphql::*;\\n# struct Query;\\n# #[Object]\\n# impl Query {\\nasync fn parse_with_extensions(&self) -> Result { Err(Error::new(\\"MyMessage\\").extend_with(|_, e| e.set(\\"details\\", \\"CAN_NOT_FETCH\\")))\\n}\\n# } may then return a response like this: { \\"errors\\": [ { \\"message\\": \\"MyMessage\\", \\"locations\\": [ ... ], \\"path\\": [ ... ], \\"extensions\\": { \\"details\\": \\"CAN_NOT_FETCH\\", } } ]\\n}","breadcrumbs":"Utilities » Error extensions » General Concept","id":"61","title":"General Concept"},"62":{"body":"Constructing new Errors by hand quickly becomes tedious. That is why async-graphql provides two convenience traits for casting your errors to the appropriate Error with extensions. The easiest way to provide extensions to any error is by calling extend_with on the error. This will on the fly convert any error into a Error with the given extension. # extern crate async_graphql;\\n# use async_graphql::*;\\n# struct Query;\\nuse std::num::ParseIntError;\\n# #[Object]\\n# impl Query {\\nasync fn parse_with_extensions(&self) -> Result { Ok(\\"234a\\" .parse() .map_err(|err: ParseIntError| err.extend_with(|_err, e| e.set(\\"code\\", 404)))?)\\n}\\n# }","breadcrumbs":"Utilities » Error extensions » ErrorExtensions","id":"62","title":"ErrorExtensions"},"63":{"body":"If you find yourself attaching extensions to your errors all over the place you might want to consider implementing the trait on your custom error type directly. # extern crate async_graphql;\\n# extern crate thiserror;\\n# use async_graphql::*;\\n#[derive(Debug, thiserror::Error)]\\npub enum MyError { #[error(\\"Could not find resource\\")] NotFound, #[error(\\"ServerError\\")] ServerError(String), #[error(\\"No Extensions\\")] ErrorWithoutExtensions,\\n} impl ErrorExtensions for MyError { // lets define our base extensions fn extend(&self) -> Error { Error::new(format!(\\"{}\\", self)).extend_with(|err, e| match self { MyError::NotFound => e.set(\\"code\\", \\"NOT_FOUND\\"), MyError::ServerError(reason) => e.set(\\"reason\\", reason.clone()), MyError::ErrorWithoutExtensions => {} }) }\\n} This way you only need to call extend on your error to deliver the error message alongside the provided extensions. Or further extend your error through extend_with. # extern crate async_graphql;\\n# extern crate thiserror;\\n# use async_graphql::*;\\n# #[derive(Debug, thiserror::Error)]\\n# pub enum MyError {\\n# #[error(\\"Could not find resource\\")]\\n# NotFound,\\n# # #[error(\\"ServerError\\")]\\n# ServerError(String),\\n# # #[error(\\"No Extensions\\")]\\n# ErrorWithoutExtensions,\\n# }\\n# struct Query;\\n# #[Object]\\n# impl Query {\\nasync fn parse_with_extensions_result(&self) -> Result { // Err(MyError::NotFound.extend()) // OR Err(MyError::NotFound.extend_with(|_, e| e.set(\\"on_the_fly\\", \\"some_more_info\\")))\\n}\\n# } { \\"errors\\": [ { \\"message\\": \\"NotFound\\", \\"locations\\": [ ... ], \\"path\\": [ ... ], \\"extensions\\": { \\"code\\": \\"NOT_FOUND\\", \\"on_the_fly\\": \\"some_more_info\\" } } ]\\n}","breadcrumbs":"Utilities » Error extensions » Implementing ErrorExtensions for custom errors.","id":"63","title":"Implementing ErrorExtensions for custom errors."},"64":{"body":"This trait enables you to call extend_err directly on results. So the above code becomes less verbose. # // @todo figure out why this example does not compile!\\n# extern crate async_graphql;\\nuse async_graphql::*;\\n# struct Query;\\n# #[Object]\\n# impl Query {\\nasync fn parse_with_extensions(&self) -> Result { Ok(\\"234a\\" .parse() .extend_err(|_, e| e.set(\\"code\\", 404))?)\\n}\\n# }","breadcrumbs":"Utilities » Error extensions » ResultExt","id":"64","title":"ResultExt"},"65":{"body":"Since ErrorExtensions and ResultExt are implemented for any type &E where E: std::fmt::Display we can chain the extension together. # extern crate async_graphql;\\nuse async_graphql::*;\\n# struct Query;\\n# #[Object]\\n# impl Query {\\nasync fn parse_with_extensions(&self) -> Result { match \\"234a\\".parse() { Ok(n) => Ok(n), Err(e) => Err(e .extend_with(|_, e| e.set(\\"code\\", 404)) .extend_with(|_, e| e.set(\\"details\\", \\"some more info..\\")) // keys may also overwrite previous keys... .extend_with(|_, e| e.set(\\"code\\", 500))), }\\n}\\n# } Expected response: { \\"errors\\": [ { \\"message\\": \\"MyMessage\\", \\"locations\\": [ ... ], \\"path\\": [ ... ], \\"extensions\\": { \\"details\\": \\"some more info...\\", \\"code\\": 500, } } ]\\n}","breadcrumbs":"Utilities » Error extensions » Chained extensions","id":"65","title":"Chained extensions"},"66":{"body":"Rust does not provide stable trait specialization yet. That is why ErrorExtensions is actually implemented for &E where E: std::fmt::Display instead of E: std::fmt::Display. Some specialization is provided through Autoref-based stable specialization . The disadvantage is that the below code does NOT compile: async fn parse_with_extensions_result(&self) -> Result { // the trait `error::ErrorExtensions` is not implemented // for `std::num::ParseIntError` \\"234a\\".parse().extend_err(|_, e| e.set(\\"code\\", 404))\\n} however this does: async fn parse_with_extensions_result(&self) -> Result { // does work because ErrorExtensions is implemented for &ParseIntError \\"234a\\" .parse() .map_err(|ref e: ParseIntError| e.extend_with(|_, e| e.set(\\"code\\", 404)))\\n}","breadcrumbs":"Utilities » Error extensions » Pitfalls","id":"66","title":"Pitfalls"},"67":{"body":"Apollo Tracing provides performance analysis results for each step of query. This is an extension to Schema, and the performance analysis results are stored in QueryResponse. To enable the Apollo Tracing extension, add the extension when the Schema is created. # extern crate async_graphql;\\nuse async_graphql::*;\\nuse async_graphql::extensions::ApolloTracing; # struct Query;\\n# #[Object]\\n# impl Query { async fn version(&self) -> &str { \\"1.0\\" } } let schema = Schema::build(Query, EmptyMutation, EmptySubscription) .extension(ApolloTracing) // Enable ApolloTracing extension .finish();","breadcrumbs":"Utilities » Apollo Tracing » Apollo Tracing","id":"67","title":"Apollo Tracing"},"68":{"body":"⚠️GraphQL provides a powerful way to query your data, but putting great power in the hands of your API clients also exposes you to a risk of denial of service attacks. You can mitigate that risk with Async-graphql by limiting the complexity and depth of the queries you allow.","breadcrumbs":"Utilities » Query complexity and depth » Query complexity and depth","id":"68","title":"Query complexity and depth"},"69":{"body":"Consider a schema that allows listing blog posts. Each blog post is also related to other posts. type Query { posts(count: Int = 10): [Post!]!\\n} type Post { title: String! text: String! related(count: Int = 10): [Post!]!\\n} It\'s not too hard to craft a query that will cause a very large response: { posts(count: 100) { related(count: 100) { related(count: 100) { related(count: 100) { title } } } }\\n} The size of the response increases exponentially with every other level of the related field. Fortunately, Async-graphql provides a way to prevent such queries.","breadcrumbs":"Utilities » Query complexity and depth » Expensive Queries","id":"69","title":"Expensive Queries"},"7":{"body":"let json = serde_json::to_string(&res);","breadcrumbs":"Quickstart » Output the query results as JSON","id":"7","title":"Output the query results as JSON"},"70":{"body":"The depth is the number of nesting levels of the field, and the following is a query with a depth of 3. { a { b { c } }\\n} You can limit the depth when creating Schema. If the query exceeds this limit, an error will occur and the message Query is nested too deep will be returned. # extern crate async_graphql;\\n# use async_graphql::*;\\n# struct Query;\\n# #[Object]\\n# impl Query { async fn version(&self) -> &str { \\"1.0\\" } }\\nlet schema = Schema::build(Query, EmptyMutation, EmptySubscription) .limit_depth(5) // Limit the maximum depth to 5 .finish();","breadcrumbs":"Utilities » Query complexity and depth » Limiting Query depth","id":"70","title":"Limiting Query depth"},"71":{"body":"The complexity is the number of fields in the query. The default complexity of each field is 1. Below is a query with a complexity of 6. { a b c { d { e f } }\\n} You can limit the complexity when creating the Schema. If the query exceeds this limit, an error will occur and Query is too complex will be returned. # extern crate async_graphql;\\n# use async_graphql::*;\\n# struct Query;\\n# #[Object]\\n# impl Query { async fn version(&self) -> &str { \\"1.0\\" } }\\nlet schema = Schema::build(Query, EmptyMutation, EmptySubscription) .limit_complexity(5) // Limit the maximum complexity to 5 .finish();","breadcrumbs":"Utilities » Query complexity and depth » Limiting Query complexity","id":"71","title":"Limiting Query complexity"},"72":{"body":"There are two ways to customize the complexity for non-list type and list type fields. In the following code, the complexity of the value field is 5. The complexity of the values field is count * child_complexity, child_complexity is a special variable that represents the complexity of the subquery, and count is the parameter of the field, used to calculate the complexity of the values field, and the type of the return value must be usize. # extern crate async_graphql;\\n# use async_graphql::*;\\nstruct Query; #[Object]\\nimpl Query { #[graphql(complexity = 5)] async fn value(&self) -> i32 { todo!() } #[graphql(complexity = \\"count * child_complexity\\")] async fn values(&self, count: usize) -> i32 { todo!() }\\n} Note: The complexity calculation is done in the validation phase and not the execution phase, so you don\'t have to worry about partial execution of over-limit queries.","breadcrumbs":"Utilities » Query complexity and depth » Custom Complexity Calculation","id":"72","title":"Custom Complexity Calculation"},"73":{"body":"By default, all types and fields are visible in introspection. But maybe you want to hide some content according to different users to avoid unnecessary misunderstandings. You can add the visible attribute to the type or field to do it. # extern crate async_graphql;\\nuse async_graphql::*; #[derive(SimpleObject)]\\nstruct MyObj { // This field will be visible in introspection. a: i32, // This field is always hidden in introspection. #[graphql(visible = false)] b: i32, // This field calls the `is_admin` function, which // is visible if the return value is `true`. #[graphql(visible = \\"is_admin\\")] c: i32,\\n} #[derive(Enum, Copy, Clone, Eq, PartialEq)]\\nenum MyEnum { // This item will be visible in introspection. A, // This item is always hidden in introspection. #[graphql(visible = false)] B, // This item calls the `is_admin` function, which // is visible if the return value is `true`. #[graphql(visible = \\"is_admin\\")] C,\\n} struct IsAdmin(bool); fn is_admin(ctx: &Context<\'_>) -> bool { ctx.data_unchecked::().0\\n}","breadcrumbs":"Utilities » Hide content in introspection » Hide content in introspection","id":"73","title":"Hide content in introspection"},"74":{"body":"async-graphql has the capability to be extended with extensions without having to modify the original source code. A lot of features can be added this way, and a lot of extensions already exist.","breadcrumbs":"Extensions » Extensions","id":"74","title":"Extensions"},"75":{"body":"An async-graphql extension is defined by implementing the trait Extension associated. The Extension trait allows you to insert custom code to some several steps used to respond to GraphQL\'s queries through async-graphql. With Extensions, your application can hook into the GraphQL\'s requests lifecycle to add behaviors about incoming requests or outgoing response. Extensions are a lot like middleware from other frameworks, be careful when using those: when you use an extension it\'ll be run for every GraphQL request . Across every step, you\'ll have the ExtensionContext supplied with data about your current request execution. Feel free to check how it\'s constructed in the code, documentation about it will soon come.","breadcrumbs":"Extensions » How extensions are working » How extensions are defined","id":"75","title":"How extensions are defined"},"76":{"body":"For those who don\'t know, let\'s dig deeper into what is a middleware: async fn middleware(&self, ctx: &ExtensionContext<\'_>, next: NextMiddleware<\'_>) -> MiddlewareResult { // Logic to your middleware. /* * Final step to your middleware, we call the next function which will trigger * the execution of the next middleware. It\'s like a `callback` in JavaScript. */ next.run(ctx).await\\n} As you have seen, a Middleware is only a function calling the next function at the end, but we could also do a middleware with the next.run function at the start. This is where it\'s becoming tricky: depending on where you put your logic and where is the next.run call, your logic won\'t have the same execution order. Depending on your logic code, you\'ll want to process it before or after the next.run call. If you need more information about middlewares, there are a lot of things on the web.","breadcrumbs":"Extensions » How extensions are working » A word about middleware","id":"76","title":"A word about middleware"},"77":{"body":"There are several steps to go to process a query to completion, you\'ll be able to create extension based on these hooks.","breadcrumbs":"Extensions » How extensions are working » Processing of a query","id":"77","title":"Processing of a query"},"78":{"body":"First, when we receive a request, if it\'s not a subscription, the first function to be called will be request, it\'s the first step, it\'s the function called at the incoming request, and it\'s also the function which will output the response to the user. Default implementation for request: # extern crate async_graphql;\\n# use async_graphql::*;\\n# use async_graphql::extensions::*;\\n# struct MyMiddleware;\\n# #[async_trait::async_trait]\\n# impl Extension for MyMiddleware {\\nasync fn request(&self, ctx: &ExtensionContext<\'_>, next: NextRequest<\'_>) -> Response { next.run(ctx).await\\n}\\n# } Depending on where you put your logic code, it\'ll be executed at the beginning or at the end of the query being processed. # extern crate async_graphql;\\n# use async_graphql::*;\\n# use async_graphql::extensions::*;\\n# struct MyMiddleware;\\n# #[async_trait::async_trait]\\n# impl Extension for MyMiddleware {\\nasync fn request(&self, ctx: &ExtensionContext<\'_>, next: NextRequest<\'_>) -> Response { // The code here will be run before the prepare_request is executed. let result = next.run(ctx).await; // The code after the completion of this future will be after the processing, just before sending the result to the user. result\\n}\\n# }","breadcrumbs":"Extensions » How extensions are working » request","id":"78","title":"request"},"79":{"body":"Just after the request, we will have the prepare_request lifecycle, which will be hooked. # extern crate async_graphql;\\n# use async_graphql::*;\\n# use async_graphql::*;\\n# use async_graphql::extensions::*;\\n# struct MyMiddleware;\\n# #[async_trait::async_trait]\\n# impl Extension for MyMiddleware {\\nasync fn prepare_request( &self, ctx: &ExtensionContext<\'_>, request: Request, next: NextPrepareRequest<\'_>,\\n) -> ServerResult { // The code here will be run before the prepare_request is executed, just after the request lifecycle hook. let result = next.run(ctx, request).await; // The code here will be run just after the prepare_request result\\n}\\n# }","breadcrumbs":"Extensions » How extensions are working » prepare_request","id":"79","title":"prepare_request"},"8":{"body":"All examples are in the sub-repository , located in the examples directory. git submodule update # update the examples repo\\ncd examples && cargo run --bin [name] For more information, see the sub-repository README.md.","breadcrumbs":"Quickstart » Web server integration","id":"8","title":"Web server integration"},"80":{"body":"The parse_query will create a GraphQL ExecutableDocument on your query, it\'ll check if the query is valid for the GraphQL Spec. Usually the implemented spec in async-graphql tends to be the last stable one (October2021). # extern crate async_graphql;\\n# use async_graphql::*;\\n# use async_graphql::extensions::*;\\n# use async_graphql::parser::types::ExecutableDocument;\\n# struct MyMiddleware;\\n# #[async_trait::async_trait]\\n# impl Extension for MyMiddleware {\\n/// Called at parse query.\\nasync fn parse_query( &self, ctx: &ExtensionContext<\'_>, // The raw query query: &str, // The variables variables: &Variables, next: NextParseQuery<\'_>,\\n) -> ServerResult { next.run(ctx, query, variables).await\\n}\\n# }","breadcrumbs":"Extensions » How extensions are working » parse_query","id":"80","title":"parse_query"},"81":{"body":"The validation step will check (depending on your validation_mode) rules the query should abide to and give the client data about why the query is not valid. # extern crate async_graphql;\\n# use async_graphql::*;\\n# use async_graphql::extensions::*;\\n# struct MyMiddleware;\\n# #[async_trait::async_trait]\\n# impl Extension for MyMiddleware {\\n/// Called at validation query.\\nasync fn validation( &self, ctx: &ExtensionContext<\'_>, next: NextValidation<\'_>,\\n) -> Result> { next.run(ctx).await\\n}\\n# }","breadcrumbs":"Extensions » How extensions are working » validation","id":"81","title":"validation"},"82":{"body":"The execution step is a huge one, it\'ll start the execution of the query by calling each resolver concurrently for a Query and serially for a Mutation. # extern crate async_graphql;\\n# use async_graphql::*;\\n# use async_graphql::extensions::*;\\n# struct MyMiddleware;\\n# #[async_trait::async_trait]\\n# impl Extension for MyMiddleware {\\n/// Called at execute query.\\nasync fn execute( &self, ctx: &ExtensionContext<\'_>, operation_name: Option<&str>, next: NextExecute<\'_>,\\n) -> Response { // Before starting resolving the whole query let result = next.run(ctx, operation_name).await; // After resolving the whole query result\\n}\\n# }","breadcrumbs":"Extensions » How extensions are working » execute","id":"82","title":"execute"},"83":{"body":"The resolve step is launched for each field. # extern crate async_graphql;\\n# use async_graphql::*;\\n# use async_graphql::extensions::*;\\n# struct MyMiddleware;\\n# #[async_trait::async_trait]\\n# impl Extension for MyMiddleware { /// Called at resolve field.\\nasync fn resolve( &self, ctx: &ExtensionContext<\'_>, info: ResolveInfo<\'_>, next: NextResolve<\'_>,\\n) -> ServerResult> { // Logic before resolving the field let result = next.run(ctx, info).await; // Logic after resolving the field result\\n}\\n# }","breadcrumbs":"Extensions » How extensions are working » resolve","id":"83","title":"resolve"},"84":{"body":"The subscribe lifecycle has the same behavior as the request but for a Subscription. # extern crate async_graphql;\\n# use async_graphql::*;\\n# use async_graphql::extensions::*;\\n# use futures_util::stream::BoxStream;\\n# struct MyMiddleware;\\n# #[async_trait::async_trait]\\n# impl Extension for MyMiddleware {\\n/// Called at subscribe request.\\nfn subscribe<\'s>( &self, ctx: &ExtensionContext<\'_>, stream: BoxStream<\'s, Response>, next: NextSubscribe<\'_>,\\n) -> BoxStream<\'s, Response> { next.run(ctx, stream)\\n}\\n# }","breadcrumbs":"Extensions » How extensions are working » subscribe","id":"84","title":"subscribe"},"85":{"body":"There are a lot of available extensions in the async-graphql to empower your GraphQL Server, some of these documentations are documented here.","breadcrumbs":"Extensions » Available extensions » Extensions available","id":"85","title":"Extensions available"},"86":{"body":"Available in the repository The analyzer extension will output a field containing complexity and depth in the response extension field of each query.","breadcrumbs":"Extensions » Available extensions » Analyzer","id":"86","title":"Analyzer"},"87":{"body":"Available in the repository To improve network performance for large queries, you can enable this Persisted Queries extension. With this extension enabled, each unique query is associated to a unique identifier, so clients can send this identifier instead of the corresponding query string to reduce requests sizes. This extension doesn\'t force you to use some cache strategy, you can choose the caching strategy you want, you\'ll just have to implement the CacheStorage trait: # extern crate async_graphql;\\n# use async_graphql::*;\\n#[async_trait::async_trait]\\npub trait CacheStorage: Send + Sync + Clone + \'static { /// Load the query by `key`. async fn get(&self, key: String) -> Option; /// Save the query by `key`. async fn set(&self, key: String, query: String);\\n} References: Apollo doc - Persisted Queries","breadcrumbs":"Extensions » Available extensions » Apollo Persisted Queries","id":"87","title":"Apollo Persisted Queries"},"88":{"body":"Available in the repository Apollo Tracing is an extension which includes analytics data for your queries. This extension works to follow the old and now deprecated Apollo Tracing Spec . If you want to check the newer Apollo Reporting Protocol, it\'s implemented by async-graphql Apollo studio extension for Apollo Studio.","breadcrumbs":"Extensions » Available extensions » Apollo Tracing","id":"88","title":"Apollo Tracing"},"89":{"body":"Available at async-graphql/async_graphql_apollo_studio_extension Apollo Studio is a cloud platform that helps you build, validate, and secure your organization\'s graph (description from the official documentation). It\'s a service allowing you to monitor & work with your team around your GraphQL Schema. async-graphql provides an extension implementing the official Apollo Specification available at async-graphql-extension-apollo-tracing and Crates.io .","breadcrumbs":"Extensions » Available extensions » Apollo Studio","id":"89","title":"Apollo Studio"},"9":{"body":"Async-graphql implements conversions from GraphQL Objects to Rust structs, and it\'s easy to use.","breadcrumbs":"Type System » Type System","id":"9","title":"Type System"},"90":{"body":"Available in the repository Logger is a simple extension allowing you to add some logging feature to async-graphql. It\'s also a good example to learn how to create your own extension.","breadcrumbs":"Extensions » Available extensions » Logger","id":"90","title":"Logger"},"91":{"body":"Available in the repository OpenTelemetry is an extension providing an integration with the opentelemetry crate to allow your application to capture distributed traces and metrics from async-graphql.","breadcrumbs":"Extensions » Available extensions » OpenTelemetry","id":"91","title":"OpenTelemetry"},"92":{"body":"Available in the repository Tracing is a simple extension allowing you to add some tracing feature to async-graphql. A little like the Logger extension.","breadcrumbs":"Extensions » Available extensions » Tracing","id":"92","title":"Tracing"},"93":{"body":"Async-graphql supports several common Rust web servers. Poem async-graphql-poem Actix-web async-graphql-actix-web Warp async-graphql-warp Axum async-graphql-axum Rocket async-graphql-rocket Even if the server you are currently using is not in the above list, it is quite simple to implement similar functionality yourself.","breadcrumbs":"Integrations » Integrations","id":"93","title":"Integrations"},"94":{"body":"","breadcrumbs":"Integrations » Poem » Poem","id":"94","title":"Poem"},"95":{"body":"# extern crate async_graphql_poem;\\n# extern crate async_graphql;\\n# extern crate poem;\\n# use async_graphql::*;\\n# #[derive(Default, SimpleObject)]\\n# struct Query { a: i32 }\\n# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();\\nuse poem::Route;\\nuse async_graphql_poem::GraphQL; let app = Route::new() .at(\\"/ws\\", GraphQL::new(schema));","breadcrumbs":"Integrations » Poem » Request example","id":"95","title":"Request example"},"96":{"body":"# extern crate async_graphql_poem;\\n# extern crate async_graphql;\\n# extern crate poem;\\n# use async_graphql::*;\\n# #[derive(Default, SimpleObject)]\\n# struct Query { a: i32 }\\n# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();\\nuse poem::{get, Route};\\nuse async_graphql_poem::GraphQLSubscription; let app = Route::new() .at(\\"/ws\\", get(GraphQLSubscription::new(schema)));","breadcrumbs":"Integrations » Poem » Subscription example","id":"96","title":"Subscription example"},"97":{"body":"https://github.com/async-graphql/examples/tree/master/poem","breadcrumbs":"Integrations » Poem » More examples","id":"97","title":"More examples"},"98":{"body":"For Async-graphql-warp, two Filter integrations are provided: graphql and graphql_subscription. The graphql filter is used for execution Query and Mutation requests. It extracts GraphQL request and outputs async_graphql::Schema and async_graphql::Request. You can combine other filters later, or directly call Schema::execute to execute the query. graphql_subscription is used to implement WebSocket subscriptions. It outputs warp::Reply.","breadcrumbs":"Integrations » Warp » Warp","id":"98","title":"Warp"},"99":{"body":"# extern crate async_graphql_warp;\\n# extern crate async_graphql;\\n# extern crate warp;\\n# use async_graphql::*;\\n# use std::convert::Infallible;\\n# use warp::Filter;\\n# struct QueryRoot;\\n# #[Object]\\n# impl QueryRoot { async fn version(&self) -> &str { \\"1.0\\" } }\\n# async fn other() {\\ntype MySchema = Schema; let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription);\\nlet filter = async_graphql_warp::graphql(schema).and_then(|(schema, request): (MySchema, async_graphql::Request)| async move { // Execute query let resp = schema.execute(request).await; // Return result Ok::<_, Infallible>(async_graphql_warp::GraphQLResponse::from(resp))\\n});\\nwarp::serve(filter).run(([0, 0, 0, 0], 8000)).await;\\n# }","breadcrumbs":"Integrations » Warp » Request example","id":"99","title":"Request example"}},"length":133,"save":true},"fields":["title","body","breadcrumbs"],"index":{"body":{"root":{"0":{"df":6,"docs":{"100":{"tf":1.7320508075688772},"123":{"tf":1.0},"39":{"tf":1.0},"49":{"tf":1.0},"58":{"tf":1.0},"99":{"tf":1.7320508075688772}}},"1":{"\\"":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"128":{"tf":1.0}}}}}}},"df":0,"docs":{}},")":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"0":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},".":{"0":{"df":8,"docs":{"100":{"tf":1.0},"114":{"tf":1.0},"30":{"tf":1.0},"6":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"99":{"tf":1.0}}},"df":0,"docs":{}},"0":{"0":{"0":{"0":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}},"df":2,"docs":{"56":{"tf":1.4142135623730951},"69":{"tf":2.0}}},"df":7,"docs":{"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"41":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.0},"6":{"tf":1.0},"69":{"tf":1.4142135623730951}}},"2":{"3":{"4":{"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"7":{"7":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"8":{"0":{"df":1,"docs":{"27":{"tf":1.0}}},"3":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":6,"docs":{"110":{"tf":4.242640687119285},"111":{"tf":1.0},"115":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951},"57":{"tf":1.0},"71":{"tf":1.0}}},"2":{"\\"":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"128":{"tf":1.0}}}}}}},"df":0,"docs":{}},"0":{"df":1,"docs":{"6":{"tf":1.0}}},"3":{"4":{"a":{"\\"":{".":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"65":{"tf":1.0}},"e":{"(":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":1,"docs":{"66":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":4,"docs":{"111":{"tf":1.0},"115":{"tf":1.4142135623730951},"22":{"tf":1.0},"57":{"tf":1.0}}},"3":{"0":{"df":2,"docs":{"39":{"tf":1.4142135623730951},"57":{"tf":1.0}}},"df":4,"docs":{"111":{"tf":1.0},"115":{"tf":1.4142135623730951},"57":{"tf":1.0},"70":{"tf":1.0}}},"4":{".":{"0":{"df":1,"docs":{"4":{"tf":2.0}}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"21":{"tf":1.0}}},"4":{"df":4,"docs":{"62":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"111":{"tf":1.0}}},"5":{"0":{"0":{"df":1,"docs":{"65":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":6,"docs":{"40":{"tf":1.0},"41":{"tf":1.0},"54":{"tf":1.4142135623730951},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.4142135623730951}}},"6":{"0":{"df":1,"docs":{"57":{"tf":1.0}}},"4":{"df":1,"docs":{"107":{"tf":1.0}}},"df":1,"docs":{"71":{"tf":1.0}}},"8":{"0":{"0":{"0":{")":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"100":{"tf":1.0},"99":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":2,"docs":{"119":{"tf":1.0},"120":{"tf":1.0}}}}},"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":2,"docs":{"114":{"tf":1.4142135623730951},"53":{"tf":1.0}}}}},"df":2,"docs":{"114":{"tf":1.0},"49":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"119":{"tf":1.0},"120":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"119":{"tf":1.0},"120":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"119":{"tf":1.4142135623730951},"120":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}}},"a":{"(":{"a":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"81":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"v":{"df":5,"docs":{"32":{"tf":1.0},"34":{"tf":1.0},"43":{"tf":1.0},"64":{"tf":1.0},"93":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"103":{"tf":1.0},"14":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"21":{"tf":1.0}}}}}}}},"r":{"d":{"df":1,"docs":{"73":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"131":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"15":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"b":{":":{":":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":1,"docs":{"103":{"tf":1.0}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"104":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}},"{":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"b":{"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.0}}},"df":0,"docs":{}}}},"df":3,"docs":{"102":{"tf":1.0},"4":{"tf":1.4142135623730951},"93":{"tf":1.4142135623730951}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":5,"docs":{"15":{"tf":1.0},"53":{"tf":1.7320508075688772},"56":{"tf":1.0},"61":{"tf":1.0},"66":{"tf":1.0}}}},"df":0,"docs":{}}}},"d":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"5":{"tf":1.0},"50":{"tf":1.0}}}}}}},"a":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}},"df":16,"docs":{"107":{"tf":1.0},"108":{"tf":1.0},"121":{"tf":1.4142135623730951},"126":{"tf":1.0},"129":{"tf":1.0},"131":{"tf":1.4142135623730951},"132":{"tf":1.7320508075688772},"33":{"tf":1.4142135623730951},"36":{"tf":1.0},"4":{"tf":1.0},"59":{"tf":1.0},"67":{"tf":1.0},"73":{"tf":1.0},"75":{"tf":1.0},"90":{"tf":1.0},"92":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":5,"docs":{"113":{"tf":1.0},"132":{"tf":1.0},"28":{"tf":1.0},"59":{"tf":1.7320508075688772},"61":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"(":{"a":{"d":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"37":{"tf":1.0},"54":{"tf":1.0}}}}}}},"df":4,"docs":{"11":{"tf":1.0},"125":{"tf":1.0},"13":{"tf":1.0},"74":{"tf":1.0}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"11":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"52":{"tf":2.0}}}}},"v":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"106":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"|":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"g":{"df":1,"docs":{"57":{"tf":1.0}}},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":14,"docs":{"113":{"tf":1.0},"118":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.0},"31":{"tf":1.0},"52":{"tf":1.4142135623730951},"57":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"75":{"tf":1.0},"89":{"tf":1.0},"90":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"111":{"tf":1.0},"23":{"tf":1.0}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"i":{"df":3,"docs":{"24":{"tf":1.0},"37":{"tf":1.0},"74":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"w":{"a":{"df":0,"docs":{},"y":{"df":3,"docs":{"129":{"tf":1.0},"49":{"tf":1.0},"73":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":1,"docs":{"67":{"tf":1.4142135623730951}}}},"t":{"df":1,"docs":{"88":{"tf":1.0}}},"z":{"df":1,"docs":{"86":{"tf":1.4142135623730951}}}}}},"d":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{":":{":":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"(":{"\\"":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"\\"":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":6,"docs":{"126":{"tf":1.4142135623730951},"127":{"tf":1.0},"34":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":1.0},"48":{"tf":1.0}}}}},"y":{"(":{"$":{"1":{"df":1,"docs":{"111":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"68":{"tf":1.0}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":8,"docs":{"115":{"tf":1.0},"116":{"tf":1.4142135623730951},"117":{"tf":1.0},"121":{"tf":1.0},"67":{"tf":1.7320508075688772},"87":{"tf":1.4142135623730951},"88":{"tf":2.449489742783178},"89":{"tf":2.0}},"t":{"df":0,"docs":{},"r":{"a":{"c":{"df":1,"docs":{"67":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"p":{"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"118":{"tf":1.0},"128":{"tf":1.0}}}},"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"c":{"df":3,"docs":{"17":{"tf":1.0},"75":{"tf":1.0},"91":{"tf":1.0}}},"df":3,"docs":{"115":{"tf":1.0},"122":{"tf":1.0},"32":{"tf":1.0}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.0}}}}}}}}},"r":{"c":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"x":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"111":{"tf":1.0},"112":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"116":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"a":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"29":{"tf":1.0}}},"df":0,"docs":{}},"g":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"29":{"tf":1.0},"40":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"df":1,"docs":{"24":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"129":{"tf":1.0},"14":{"tf":1.4142135623730951},"16":{"tf":1.0},"29":{"tf":1.0},"33":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"89":{"tf":1.0}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"i":{"df":2,"docs":{"75":{"tf":1.0},"87":{"tf":1.0}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"n":{"c":{"/":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"111":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{">":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":7,"docs":{"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"c":{"df":1,"docs":{"67":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"49":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"49":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"80":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"18":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}}},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"98":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"_":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"b":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"{":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"95":{"tf":1.0}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"96":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{")":{".":{"a":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"|":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"99":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"18":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"100":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":3,"docs":{"100":{"tf":1.0},"18":{"tf":1.0},"99":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":88,"docs":{"10":{"tf":1.4142135623730951},"100":{"tf":1.4142135623730951},"103":{"tf":1.4142135623730951},"104":{"tf":1.4142135623730951},"107":{"tf":1.4142135623730951},"108":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"111":{"tf":1.0},"112":{"tf":1.4142135623730951},"114":{"tf":2.0},"115":{"tf":2.0},"117":{"tf":1.4142135623730951},"119":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"120":{"tf":1.4142135623730951},"121":{"tf":2.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"125":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"127":{"tf":1.4142135623730951},"128":{"tf":1.4142135623730951},"129":{"tf":2.0},"13":{"tf":1.4142135623730951},"130":{"tf":1.4142135623730951},"131":{"tf":1.4142135623730951},"132":{"tf":1.4142135623730951},"14":{"tf":1.4142135623730951},"16":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"21":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"26":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"28":{"tf":2.23606797749979},"29":{"tf":1.4142135623730951},"30":{"tf":2.0},"31":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"34":{"tf":2.449489742783178},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"41":{"tf":1.4142135623730951},"43":{"tf":2.0},"44":{"tf":1.4142135623730951},"47":{"tf":1.4142135623730951},"48":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"50":{"tf":1.4142135623730951},"52":{"tf":2.0},"53":{"tf":1.4142135623730951},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"6":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"63":{"tf":2.0},"64":{"tf":1.4142135623730951},"65":{"tf":1.4142135623730951},"67":{"tf":1.4142135623730951},"70":{"tf":1.4142135623730951},"71":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"78":{"tf":2.0},"79":{"tf":1.7320508075688772},"80":{"tf":1.4142135623730951},"81":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951},"83":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951},"87":{"tf":1.4142135623730951},"95":{"tf":1.4142135623730951},"96":{"tf":1.4142135623730951},"99":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"d":{":":{":":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"111":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{":":{":":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":9,"docs":{"114":{"tf":1.4142135623730951},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"87":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":83,"docs":{"0":{"tf":1.4142135623730951},"100":{"tf":1.7320508075688772},"103":{"tf":1.0},"104":{"tf":1.0},"107":{"tf":1.4142135623730951},"11":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.4142135623730951},"112":{"tf":1.4142135623730951},"113":{"tf":1.0},"114":{"tf":2.0},"117":{"tf":1.4142135623730951},"119":{"tf":1.7320508075688772},"120":{"tf":1.0},"123":{"tf":1.0},"128":{"tf":1.7320508075688772},"129":{"tf":1.4142135623730951},"130":{"tf":1.0},"14":{"tf":1.4142135623730951},"16":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":2.0},"23":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"27":{"tf":1.0},"29":{"tf":2.8284271247461903},"30":{"tf":1.7320508075688772},"31":{"tf":2.449489742783178},"33":{"tf":1.0},"37":{"tf":1.4142135623730951},"39":{"tf":1.7320508075688772},"4":{"tf":2.0},"40":{"tf":1.7320508075688772},"45":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.4142135623730951},"49":{"tf":1.0},"5":{"tf":1.4142135623730951},"50":{"tf":1.0},"52":{"tf":1.4142135623730951},"53":{"tf":1.4142135623730951},"54":{"tf":1.4142135623730951},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":2.0},"58":{"tf":1.7320508075688772},"6":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.4142135623730951},"67":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.4142135623730951},"74":{"tf":1.0},"75":{"tf":1.4142135623730951},"76":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.4142135623730951},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"85":{"tf":1.0},"87":{"tf":1.4142135623730951},"88":{"tf":1.0},"89":{"tf":1.7320508075688772},"9":{"tf":1.0},"90":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0},"93":{"tf":2.449489742783178},"98":{"tf":1.0},"99":{"tf":1.7320508075688772}},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"(":{"\\"":{"/":{"df":0,"docs":{},"w":{"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"a":{"c":{"df":0,"docs":{},"h":{"df":3,"docs":{"15":{"tf":1.0},"21":{"tf":1.0},"63":{"tf":1.0}}},"k":{"df":1,"docs":{"68":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":12,"docs":{"11":{"tf":1.0},"118":{"tf":1.0},"12":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.0},"132":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"33":{"tf":1.0},"52":{"tf":1.0},"55":{"tf":1.0},"73":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"18":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}}}},"o":{"df":1,"docs":{"29":{"tf":1.0}},"m":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"117":{"tf":1.0},"27":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"66":{"tf":1.0}}}}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":10,"docs":{"111":{"tf":1.0},"2":{"tf":1.0},"85":{"tf":1.4142135623730951},"86":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"89":{"tf":1.4142135623730951},"90":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"73":{"tf":1.0}}},"df":0,"docs":{}}}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"110":{"tf":1.0},"18":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0}}}}},"df":0,"docs":{}},"x":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"93":{"tf":1.4142135623730951}}}}}},"b":{"(":{"b":{"df":1,"docs":{"32":{"tf":1.0}}},"df":0,"docs":{}},"a":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"2":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"g":{"<":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"44":{"tf":1.0}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"44":{"tf":1.0}}}}},"t":{"df":1,"docs":{"44":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"e":{"df":4,"docs":{"5":{"tf":1.0},"63":{"tf":1.0},"66":{"tf":1.0},"77":{"tf":1.0}}},"i":{"c":{"df":1,"docs":{"45":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"111":{"tf":1.0},"118":{"tf":1.0}}}},"df":0,"docs":{}}},"df":17,"docs":{"10":{"tf":1.7320508075688772},"108":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"120":{"tf":2.6457513110645907},"13":{"tf":1.4142135623730951},"20":{"tf":1.0},"28":{"tf":2.0},"32":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.4142135623730951},"43":{"tf":1.0},"5":{"tf":2.0},"6":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"73":{"tf":1.4142135623730951}},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":3,"docs":{"62":{"tf":1.0},"64":{"tf":1.0},"76":{"tf":1.0}}}}},"df":3,"docs":{"129":{"tf":1.0},"15":{"tf":1.0},"78":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":7,"docs":{"52":{"tf":1.0},"58":{"tf":1.7320508075688772},"76":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"1":{"0":{"0":{"0":{"0":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"78":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"v":{"df":1,"docs":{"113":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":4,"docs":{"113":{"tf":1.0},"132":{"tf":1.0},"75":{"tf":1.0},"84":{"tf":1.0}}}}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"110":{"tf":1.0}}}},"w":{"df":5,"docs":{"10":{"tf":1.0},"22":{"tf":1.0},"38":{"tf":1.0},"66":{"tf":1.0},"71":{"tf":1.0}}}}},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"2":{"tf":1.0}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"/":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"/":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"2":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":1,"docs":{"2":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":3,"docs":{"119":{"tf":1.0},"25":{"tf":1.0},"37":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"d":{"(":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"111":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"110":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":1,"docs":{"8":{"tf":1.0}}},"t":{"df":3,"docs":{"107":{"tf":1.0},"132":{"tf":1.0},"31":{"tf":1.0}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"23":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"21":{"tf":1.0}}},"l":{"df":5,"docs":{"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"44":{"tf":1.0},"73":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"n":{"b":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"44":{"tf":1.0}}}},"df":0,"docs":{}},"df":4,"docs":{"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"44":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"<":{"\'":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":1,"docs":{"16":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":1,"docs":{"16":{"tf":1.4142135623730951}}}}}},"t":{"df":0,"docs":{},"h":{"df":4,"docs":{"113":{"tf":1.0},"115":{"tf":1.0},"12":{"tf":1.0},"29":{"tf":1.0}}}},"x":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"<":{"\'":{"df":1,"docs":{"84":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"32":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"114":{"tf":1.0},"89":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":3,"docs":{"107":{"tf":1.0},"113":{"tf":1.0},"54":{"tf":1.0}}}}}}},"c":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"11":{"tf":1.0}}}}}}},"c":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"a":{"c":{"df":0,"docs":{},"h":{"df":3,"docs":{"111":{"tf":1.0},"57":{"tf":3.605551275463989},"87":{"tf":1.4142135623730951}},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{":":{":":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"57":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"87":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":2,"docs":{"11":{"tf":1.7320508075688772},"72":{"tf":1.7320508075688772}}}}},"df":0,"docs":{},"l":{"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"76":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":24,"docs":{"110":{"tf":1.4142135623730951},"130":{"tf":1.0},"16":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.0},"33":{"tf":1.0},"39":{"tf":1.0},"5":{"tf":1.0},"52":{"tf":1.0},"57":{"tf":1.4142135623730951},"58":{"tf":1.0},"6":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"73":{"tf":1.4142135623730951},"76":{"tf":2.0},"78":{"tf":1.4142135623730951},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.4142135623730951},"83":{"tf":1.0},"84":{"tf":1.0},"98":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"n":{"_":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"p":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"74":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"91":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"75":{"tf":1.0}}},"g":{"df":0,"docs":{},"o":{"df":2,"docs":{"2":{"tf":1.0},"8":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":2,"docs":{"11":{"tf":1.0},"130":{"tf":1.0}}},"t":{"df":2,"docs":{"61":{"tf":1.0},"62":{"tf":1.0}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"69":{"tf":1.0}}}}},"d":{"df":2,"docs":{"2":{"tf":1.0},"8":{"tf":1.0}}},"df":9,"docs":{"10":{"tf":1.4142135623730951},"13":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"28":{"tf":2.0},"32":{"tf":2.0},"36":{"tf":1.4142135623730951},"70":{"tf":1.0},"71":{"tf":1.0},"73":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"121":{"tf":1.0}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"65":{"tf":1.4142135623730951}}}},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"17":{"tf":1.0},"27":{"tf":1.0}}}},"r":{"df":1,"docs":{"54":{"tf":1.4142135623730951}},"s":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"x":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":3,"docs":{"52":{"tf":1.4142135623730951},"53":{"tf":1.0},"56":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":8,"docs":{"116":{"tf":1.0},"121":{"tf":1.0},"55":{"tf":1.4142135623730951},"60":{"tf":1.0},"75":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"88":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"l":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"72":{"tf":1.7320508075688772}}}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"13":{"tf":1.0},"36":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.4142135623730951}}}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"s":{"df":2,"docs":{"37":{"tf":1.0},"87":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"24":{"tf":1.0}}}}}}},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":2,"docs":{"29":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}},"e":{"\\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"(":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":4,"docs":{"113":{"tf":1.0},"68":{"tf":1.0},"81":{"tf":1.0},"87":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":7,"docs":{"21":{"tf":1.0},"26":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"52":{"tf":1.4142135623730951},"73":{"tf":1.0},"87":{"tf":1.0}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.0}}}}},"u":{"d":{"df":1,"docs":{"89":{"tf":1.0}}},"df":0,"docs":{}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":18,"docs":{"0":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.0},"129":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.0},"22":{"tf":1.0},"25":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"72":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.4142135623730951},"76":{"tf":1.0},"78":{"tf":1.7320508075688772},"79":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":5,"docs":{"116":{"tf":1.0},"127":{"tf":1.4142135623730951},"45":{"tf":1.0},"57":{"tf":1.0},"98":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":1,"docs":{"75":{"tf":1.0}}},"m":{"a":{"df":2,"docs":{"34":{"tf":1.0},"43":{"tf":1.0}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":6,"docs":{"107":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"5":{"tf":1.0},"54":{"tf":1.0},"93":{"tf":1.0}}}}},"p":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":5,"docs":{"22":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":4,"docs":{"116":{"tf":1.0},"48":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0}}},"x":{"df":6,"docs":{"11":{"tf":1.0},"121":{"tf":1.0},"68":{"tf":1.4142135623730951},"71":{"tf":2.6457513110645907},"72":{"tf":2.6457513110645907},"86":{"tf":1.0}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":5,"docs":{"11":{"tf":2.23606797749979},"111":{"tf":1.0},"123":{"tf":1.0},"129":{"tf":1.0},"52":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"s":{"df":1,"docs":{"132":{"tf":2.0}},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"115":{"tf":1.0},"132":{"tf":1.7320508075688772}},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"132":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"120":{"tf":1.0},"121":{"tf":1.0}}},"df":0,"docs":{}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"t":{"(":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"114":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"114":{"tf":2.449489742783178}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.4142135623730951},"42":{"tf":1.7320508075688772},"43":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":3,"docs":{"110":{"tf":1.0},"47":{"tf":1.0},"82":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"49":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"129":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"130":{"tf":1.0}}}}},"j":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{}}}},"n":{"df":1,"docs":{"14":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"14":{"tf":1.4142135623730951},"16":{"tf":1.0},"17":{"tf":1.0},"58":{"tf":2.23606797749979}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"58":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"35":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"120":{"tf":1.0},"45":{"tf":1.0},"58":{"tf":1.0}}}}},"t":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"19":{"tf":1.0}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"30":{"tf":1.0},"62":{"tf":1.0},"75":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"129":{"tf":1.0}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"130":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.0},"86":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":3,"docs":{"44":{"tf":1.7320508075688772},"59":{"tf":1.0},"73":{"tf":1.4142135623730951}}}},"x":{"df":0,"docs":{},"t":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"<":{"\'":{"_":{"df":9,"docs":{"110":{"tf":1.0},"111":{"tf":1.0},"114":{"tf":1.4142135623730951},"14":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"52":{"tf":1.4142135623730951},"53":{"tf":1.0},"73":{"tf":1.0}}},"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":1,"docs":{"16":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":6,"docs":{"14":{"tf":1.7320508075688772},"15":{"tf":1.4142135623730951},"16":{"tf":1.7320508075688772},"17":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"131":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"118":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"57":{"tf":2.6457513110645907}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.0}}},"t":{"df":1,"docs":{"27":{"tf":1.0}}}},"r":{"df":0,"docs":{},"s":{"df":3,"docs":{"28":{"tf":1.0},"29":{"tf":1.0},"9":{"tf":1.0}}},"t":{"df":2,"docs":{"21":{"tf":1.0},"62":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"33":{"tf":2.0}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"i":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"52":{"tf":1.4142135623730951},"73":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"118":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"5":{"tf":1.0},"57":{"tf":1.4142135623730951},"87":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"54":{"tf":1.4142135623730951},"72":{"tf":2.0}}}}},"w":{"<":{"\'":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"44":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"u":{"df":1,"docs":{"2":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":89,"docs":{"10":{"tf":1.0},"100":{"tf":1.7320508075688772},"103":{"tf":1.7320508075688772},"104":{"tf":1.7320508075688772},"107":{"tf":1.0},"108":{"tf":1.4142135623730951},"11":{"tf":1.0},"112":{"tf":1.0},"114":{"tf":1.4142135623730951},"115":{"tf":1.4142135623730951},"117":{"tf":1.0},"119":{"tf":1.0},"12":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.4142135623730951},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.4142135623730951},"13":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0},"14":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.7320508075688772},"19":{"tf":1.4142135623730951},"20":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.4142135623730951},"25":{"tf":1.0},"26":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.7320508075688772},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.7320508075688772},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"43":{"tf":1.4142135623730951},"44":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.7320508075688772},"5":{"tf":1.0},"50":{"tf":1.0},"52":{"tf":1.4142135623730951},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":2.0},"64":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"87":{"tf":1.0},"91":{"tf":1.0},"95":{"tf":1.7320508075688772},"96":{"tf":1.7320508075688772},"99":{"tf":1.7320508075688772}},"s":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":1,"docs":{"89":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":22,"docs":{"107":{"tf":1.0},"114":{"tf":1.0},"115":{"tf":1.0},"118":{"tf":1.4142135623730951},"121":{"tf":1.4142135623730951},"14":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.4142135623730951},"24":{"tf":1.0},"28":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"53":{"tf":1.0},"6":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"77":{"tf":1.0},"80":{"tf":1.0},"90":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"118":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"x":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{"\\"":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{":":{":":{"<":{"d":{"b":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{">":{"(":{")":{"?":{".":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"14":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"16":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"16":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{":":{":":{"<":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"52":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"d":{":":{":":{"<":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"111":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"a":{"d":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{">":{"(":{")":{".":{"0":{"df":1,"docs":{"73":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"<":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"df":1,"docs":{"110":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{"\\"":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"_":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"_":{"a":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"(":{")":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"\\"":{"a":{"\\"":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"20":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"\\"":{")":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"\\"":{"c":{"\\"":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"20":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":15,"docs":{"110":{"tf":1.0},"111":{"tf":1.4142135623730951},"14":{"tf":1.0},"16":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"52":{"tf":1.4142135623730951},"76":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"114":{"tf":1.0},"115":{"tf":1.0},"126":{"tf":1.0},"75":{"tf":1.0},"93":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":2.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"114":{"tf":2.23606797749979}}}},"df":0,"docs":{}}}}},"df":10,"docs":{"107":{"tf":1.0},"113":{"tf":1.4142135623730951},"114":{"tf":1.4142135623730951},"115":{"tf":1.0},"132":{"tf":2.449489742783178},"21":{"tf":1.0},"56":{"tf":1.0},"63":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"75":{"tf":1.0}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"<":{"df":0,"docs":{},"i":{"3":{"2":{"df":1,"docs":{"56":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"d":{"(":{"d":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"t":{"a":{"(":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"111":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"b":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"17":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"s":{"3":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"18":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"b":{"a":{"df":0,"docs":{},"s":{"df":5,"docs":{"109":{"tf":1.0},"112":{"tf":1.4142135623730951},"14":{"tf":1.7320508075688772},"33":{"tf":1.0},"47":{"tf":1.0}}}},"df":0,"docs":{}},"df":18,"docs":{"103":{"tf":1.0},"109":{"tf":1.0},"112":{"tf":1.4142135623730951},"121":{"tf":1.4142135623730951},"14":{"tf":2.0},"15":{"tf":1.4142135623730951},"16":{"tf":2.23606797749979},"17":{"tf":1.7320508075688772},"18":{"tf":2.0},"20":{"tf":1.0},"35":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"68":{"tf":1.0},"75":{"tf":1.0},"81":{"tf":1.0},"88":{"tf":1.0}},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":2,"docs":{"111":{"tf":1.7320508075688772},"118":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"c":{"2":{"8":{"2":{"2":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"24":{"tf":1.0}}}}}}},"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"24":{"tf":1.0}}}}},"df":0,"docs":{}},"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{"3":{"3":{"9":{"(":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"24":{"tf":1.0}}}}},"df":0,"docs":{}},"df":1,"docs":{"24":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"c":{"2":{"8":{"2":{"2":{"(":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"24":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{"3":{"3":{"9":{"(":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{".":{"0":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":1,"docs":{"24":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"b":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"17":{"tf":1.0}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":1,"docs":{"14":{"tf":2.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"16":{"tf":1.0}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}}}}}},"df":3,"docs":{"20":{"tf":1.0},"32":{"tf":2.0},"71":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}},"l":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"113":{"tf":1.0},"115":{"tf":1.0},"34":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"70":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"76":{"tf":1.0}}}}}},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"40":{"tf":1.0}}}}}}},"df":12,"docs":{"129":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.7320508075688772},"40":{"tf":1.4142135623730951},"49":{"tf":1.0},"57":{"tf":1.0},"61":{"tf":1.0},"71":{"tf":1.0},"73":{"tf":1.0},"78":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":23,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"103":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.4142135623730951},"11":{"tf":1.7320508075688772},"117":{"tf":1.0},"118":{"tf":1.0},"12":{"tf":1.0},"120":{"tf":1.0},"14":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.0},"34":{"tf":1.0},"38":{"tf":1.0},"42":{"tf":1.0},"45":{"tf":1.7320508075688772},"52":{"tf":1.0},"57":{"tf":1.4142135623730951},"58":{"tf":1.0},"63":{"tf":1.0},"75":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"t":{"df":8,"docs":{"21":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.4142135623730951},"33":{"tf":1.0},"47":{"tf":1.0},"49":{"tf":1.0},"50":{"tf":1.0},"57":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"63":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"68":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":4,"docs":{"4":{"tf":1.4142135623730951},"76":{"tf":1.4142135623730951},"78":{"tf":1.0},"81":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"c":{"df":2,"docs":{"113":{"tf":1.0},"88":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"68":{"tf":1.4142135623730951},"70":{"tf":2.23606797749979},"86":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":6,"docs":{"11":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"28":{"tf":1.0},"44":{"tf":1.0}},"e":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"32":{"tf":2.449489742783178}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"32":{"tf":1.7320508075688772}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"21":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{",":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"17":{"tf":1.0},"23":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":5,"docs":{"18":{"tf":1.0},"22":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"95":{"tf":1.0},"96":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"73":{"tf":1.0}}}}},"q":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":7,"docs":{"120":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":2.6457513110645907},"35":{"tf":1.0},"36":{"tf":1.7320508075688772},"37":{"tf":1.0},"41":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"a":{"c":{"df":3,"docs":{"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"40":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"23":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"108":{"tf":1.0},"26":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":30,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.0},"119":{"tf":1.0},"12":{"tf":1.0},"120":{"tf":1.4142135623730951},"121":{"tf":2.0},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.4142135623730951},"13":{"tf":1.7320508075688772},"130":{"tf":1.0},"131":{"tf":1.0},"20":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"26":{"tf":1.0},"30":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":2.6457513110645907},"44":{"tf":1.0},"47":{"tf":1.0},"52":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"108":{"tf":1.0},"33":{"tf":1.0},"89":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"108":{"tf":1.4142135623730951},"26":{"tf":1.7320508075688772}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":1,"docs":{"58":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":5,"docs":{"20":{"tf":2.0},"21":{"tf":1.0},"58":{"tf":1.0},"61":{"tf":1.0},"65":{"tf":1.0}}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"0":{"tf":1.0}}}}}}}},"i":{"d":{"df":0,"docs":{},"n":{"\'":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"113":{"tf":1.0},"119":{"tf":1.0},"14":{"tf":1.0},"24":{"tf":1.0},"49":{"tf":1.0},"57":{"tf":2.0},"73":{"tf":1.0}}}}}},"g":{"df":1,"docs":{"76":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":17,"docs":{"113":{"tf":2.449489742783178},"114":{"tf":2.6457513110645907},"115":{"tf":2.6457513110645907},"117":{"tf":1.0},"118":{"tf":1.0},"121":{"tf":1.4142135623730951},"122":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.0},"130":{"tf":1.4142135623730951},"131":{"tf":1.0},"132":{"tf":2.449489742783178},"28":{"tf":1.0},"35":{"tf":1.0}},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"114":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"114":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"l":{"df":0,"docs":{},"i":{"df":5,"docs":{"10":{"tf":1.0},"30":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"98":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"s":{"a":{"d":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"66":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"128":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"128":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"91":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"o":{"c":{"df":3,"docs":{"115":{"tf":1.0},"121":{"tf":1.0},"87":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":4,"docs":{"116":{"tf":1.0},"75":{"tf":1.0},"85":{"tf":1.4142135623730951},"89":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"\'":{"df":0,"docs":{},"t":{"df":2,"docs":{"20":{"tf":1.0},"87":{"tf":1.0}}}},"df":0,"docs":{}}}},"n":{"\'":{"df":0,"docs":{},"t":{"df":3,"docs":{"10":{"tf":1.0},"72":{"tf":1.0},"76":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":3,"docs":{"121":{"tf":1.0},"32":{"tf":1.0},"72":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"e":{"df":2,"docs":{"21":{"tf":1.0},"22":{"tf":1.0}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"111":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"g":{"df":4,"docs":{"111":{"tf":1.0},"119":{"tf":1.0},"125":{"tf":1.0},"29":{"tf":1.0}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"(":{"\\"":{"c":{"df":0,"docs":{},"o":{"d":{"df":6,"docs":{"21":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":2,"docs":{"61":{"tf":1.0},"65":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"63":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"63":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"a":{"c":{"df":0,"docs":{},"h":{"df":19,"docs":{"110":{"tf":1.0},"113":{"tf":1.0},"14":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"27":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.0},"37":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"57":{"tf":1.7320508075688772},"67":{"tf":1.0},"69":{"tf":1.0},"71":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"86":{"tf":1.0},"87":{"tf":1.0}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":2,"docs":{"27":{"tf":1.0},"9":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"25":{"tf":1.0}}}}}}},"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"a":{"d":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"n":{"df":1,"docs":{"58":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":9,"docs":{"21":{"tf":1.0},"28":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":2.23606797749979},"66":{"tf":2.449489742783178},"71":{"tf":1.0}},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{}}}},"g":{"df":1,"docs":{"110":{"tf":1.0}}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":1,"docs":{"54":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"27":{"tf":1.0}}}},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"85":{"tf":1.0}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"58":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":19,"docs":{"100":{"tf":1.0},"103":{"tf":1.4142135623730951},"104":{"tf":1.4142135623730951},"111":{"tf":1.0},"114":{"tf":1.0},"117":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.4142135623730951},"22":{"tf":1.0},"23":{"tf":1.0},"30":{"tf":1.4142135623730951},"50":{"tf":1.0},"6":{"tf":1.4142135623730951},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":14,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"111":{"tf":1.0},"114":{"tf":1.0},"117":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"22":{"tf":1.0},"30":{"tf":1.4142135623730951},"6":{"tf":1.4142135623730951},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"99":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{")":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":6,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"18":{"tf":1.0},"50":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":5,"docs":{"117":{"tf":2.0},"55":{"tf":1.0},"64":{"tf":1.0},"67":{"tf":1.4142135623730951},"87":{"tf":1.4142135623730951}},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"117":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"d":{"df":5,"docs":{"109":{"tf":1.0},"111":{"tf":1.0},"58":{"tf":2.449489742783178},"76":{"tf":1.0},"78":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"2":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":6,"docs":{"118":{"tf":2.23606797749979},"119":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.7320508075688772},"129":{"tf":1.4142135623730951},"130":{"tf":2.0}}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"59":{"tf":1.7320508075688772}}}}},"u":{"df":0,"docs":{},"m":{"df":11,"docs":{"27":{"tf":1.7320508075688772},"28":{"tf":3.4641016151377544},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"32":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"52":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"73":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"28":{"tf":1.0}}}}}},"v":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"16":{"tf":1.0},"57":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}}},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}},"q":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"73":{"tf":1.0}},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"d":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"1":{"0":{"0":{"df":1,"docs":{"53":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":1,"docs":{"53":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"54":{"tf":1.4142135623730951},"56":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"r":{"(":{"\\"":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"i":{"d":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"\\"":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":2,"docs":{"52":{"tf":1.0},"53":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":1,"docs":{"65":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"\\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"\\"":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"!":{"(":{"\\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"56":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"107":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}}},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"21":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"\\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}}}}}}}}}}}}},"df":0,"docs":{}},":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"66":{"tf":1.0}}}}}}}}}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"63":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":13,"docs":{"111":{"tf":1.0},"112":{"tf":1.4142135623730951},"12":{"tf":1.0},"14":{"tf":1.4142135623730951},"21":{"tf":3.3166247903554},"52":{"tf":1.0},"59":{"tf":1.7320508075688772},"61":{"tf":2.6457513110645907},"62":{"tf":2.6457513110645907},"63":{"tf":2.8284271247461903},"65":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":4,"docs":{"62":{"tf":1.0},"63":{"tf":1.4142135623730951},"65":{"tf":1.0},"66":{"tf":1.4142135623730951}}}}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}}}}}}}}}}}}}}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}},"t":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":6,"docs":{"118":{"tf":1.0},"120":{"tf":1.0},"129":{"tf":1.0},"22":{"tf":1.0},"30":{"tf":1.0},"93":{"tf":1.0}},"t":{"df":0,"docs":{},"s":{"1":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"23":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"23":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"25":{"tf":1.0}}}}}}}},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":45,"docs":{"10":{"tf":1.0},"100":{"tf":1.0},"101":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"111":{"tf":1.4142135623730951},"115":{"tf":1.0},"116":{"tf":1.4142135623730951},"119":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.0},"126":{"tf":1.0},"130":{"tf":1.0},"14":{"tf":1.4142135623730951},"16":{"tf":1.0},"18":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"26":{"tf":1.0},"27":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.0},"43":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.0},"57":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.4142135623730951},"64":{"tf":1.0},"8":{"tf":2.0},"90":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"97":{"tf":1.0},"99":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"e":{"df":2,"docs":{"70":{"tf":1.0},"71":{"tf":1.0}}},"l":{"df":0,"docs":{},"u":{"d":{"df":1,"docs":{"111":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"80":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":16,"docs":{"110":{"tf":1.0},"113":{"tf":1.4142135623730951},"114":{"tf":1.7320508075688772},"18":{"tf":1.4142135623730951},"47":{"tf":1.0},"48":{"tf":1.4142135623730951},"52":{"tf":1.0},"6":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"75":{"tf":1.0},"76":{"tf":1.4142135623730951},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"82":{"tf":2.23606797749979},"98":{"tf":1.4142135623730951},"99":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"110":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":6,"docs":{"121":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"74":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"53":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"65":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"69":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"16":{"tf":1.0},"34":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"50":{"tf":1.4142135623730951}}}},"s":{"df":7,"docs":{"111":{"tf":1.0},"119":{"tf":1.0},"120":{"tf":1.0},"28":{"tf":1.0},"45":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"63":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{}}},"df":1,"docs":{"64":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"65":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":2,"docs":{"62":{"tf":1.0},"63":{"tf":1.0}}}}}}},"df":7,"docs":{"0":{"tf":1.0},"132":{"tf":1.0},"21":{"tf":1.0},"5":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.4142135623730951},"74":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":26,"docs":{"0":{"tf":1.0},"21":{"tf":1.0},"59":{"tf":1.4142135623730951},"61":{"tf":1.0},"62":{"tf":1.7320508075688772},"63":{"tf":2.449489742783178},"65":{"tf":1.7320508075688772},"67":{"tf":2.0},"74":{"tf":1.7320508075688772},"75":{"tf":2.6457513110645907},"77":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"85":{"tf":1.4142135623730951},"86":{"tf":1.4142135623730951},"87":{"tf":1.7320508075688772},"88":{"tf":1.7320508075688772},"89":{"tf":1.4142135623730951},"90":{"tf":1.4142135623730951},"91":{"tf":1.0},"92":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"c":{"df":1,"docs":{"67":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"<":{"\'":{"_":{"df":8,"docs":{"76":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"75":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"n":{"df":87,"docs":{"10":{"tf":1.0},"100":{"tf":1.7320508075688772},"103":{"tf":1.7320508075688772},"104":{"tf":1.7320508075688772},"107":{"tf":1.0},"108":{"tf":1.4142135623730951},"11":{"tf":1.0},"112":{"tf":1.0},"114":{"tf":1.4142135623730951},"115":{"tf":1.4142135623730951},"117":{"tf":1.0},"119":{"tf":1.4142135623730951},"12":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.4142135623730951},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":2.0},"128":{"tf":1.4142135623730951},"129":{"tf":3.0},"13":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0},"14":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.7320508075688772},"19":{"tf":1.4142135623730951},"20":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.4142135623730951},"26":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.7320508075688772},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"43":{"tf":1.4142135623730951},"44":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.7320508075688772},"5":{"tf":1.0},"50":{"tf":1.0},"52":{"tf":1.4142135623730951},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":2.0},"64":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"87":{"tf":1.0},"95":{"tf":1.7320508075688772},"96":{"tf":1.7320508075688772},"99":{"tf":1.7320508075688772}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"98":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"f":{"3":{"2":{"df":2,"docs":{"29":{"tf":2.8284271247461903},"31":{"tf":2.449489742783178}}},"df":0,"docs":{}},"6":{"4":{"df":2,"docs":{"33":{"tf":1.7320508075688772},"5":{"tf":1.0}}},"df":0,"docs":{}},"a":{"c":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"111":{"tf":1.0}}}}}},"df":1,"docs":{"61":{"tf":1.0}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"114":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":4,"docs":{"21":{"tf":1.0},"22":{"tf":1.0},"32":{"tf":1.0},"52":{"tf":1.0}}}},"l":{"df":0,"docs":{},"s":{"df":3,"docs":{"121":{"tf":1.0},"128":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951}}}}},"df":1,"docs":{"71":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":7,"docs":{"118":{"tf":1.0},"131":{"tf":1.0},"28":{"tf":1.0},"37":{"tf":1.0},"74":{"tf":1.0},"90":{"tf":1.0},"92":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\'":{"df":1,"docs":{"115":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":4,"docs":{"115":{"tf":1.0},"116":{"tf":2.0},"117":{"tf":1.7320508075688772},"118":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"75":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"111":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"110":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"w":{"df":2,"docs":{"11":{"tf":1.0},"57":{"tf":1.4142135623730951}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"df":1,"docs":{"129":{"tf":1.4142135623730951}},"n":{"a":{"df":0,"docs":{},"m":{"df":3,"docs":{"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"40":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"1":{"df":2,"docs":{"34":{"tf":2.23606797749979},"43":{"tf":2.0}}},"2":{"df":2,"docs":{"34":{"tf":2.23606797749979},"43":{"tf":2.0}}},"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"115":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"115":{"tf":1.7320508075688772}}}}}}}}},"df":44,"docs":{"10":{"tf":1.7320508075688772},"11":{"tf":2.0},"114":{"tf":1.7320508075688772},"115":{"tf":1.0},"118":{"tf":1.4142135623730951},"119":{"tf":1.4142135623730951},"120":{"tf":1.7320508075688772},"121":{"tf":2.0},"122":{"tf":1.0},"123":{"tf":1.0},"126":{"tf":1.7320508075688772},"127":{"tf":1.0},"128":{"tf":2.0},"129":{"tf":3.7416573867739413},"13":{"tf":1.4142135623730951},"130":{"tf":1.4142135623730951},"14":{"tf":2.0},"20":{"tf":1.7320508075688772},"24":{"tf":2.23606797749979},"25":{"tf":1.4142135623730951},"29":{"tf":2.6457513110645907},"31":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.7320508075688772},"37":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"43":{"tf":1.0},"47":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.4142135623730951},"52":{"tf":1.4142135623730951},"53":{"tf":1.0},"54":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"69":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.4142135623730951},"72":{"tf":2.23606797749979},"73":{"tf":2.23606797749979},"83":{"tf":2.0},"86":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"64":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"22":{"tf":1.0}}},"m":{"df":1,"docs":{"27":{"tf":1.0}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"100":{"tf":1.0},"49":{"tf":1.0},"98":{"tf":1.7320508075688772},"99":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"76":{"tf":1.0}}}},"d":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"b":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"130":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"_":{"b":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"128":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"129":{"tf":1.0},"130":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"b":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"119":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"a":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"119":{"tf":1.0}},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"119":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"119":{"tf":1.0}},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"119":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"119":{"tf":1.0}}},"df":0,"docs":{}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"120":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":2,"docs":{"119":{"tf":1.7320508075688772},"63":{"tf":1.7320508075688772}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":8,"docs":{"111":{"tf":1.0},"114":{"tf":1.0},"117":{"tf":1.0},"17":{"tf":1.0},"30":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{")":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":4,"docs":{"14":{"tf":1.0},"48":{"tf":1.0},"58":{"tf":2.0},"78":{"tf":1.7320508075688772}}}}},"t":{"df":1,"docs":{"59":{"tf":1.0}}},"x":{"df":1,"docs":{"109":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"32":{"tf":2.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.0}}}},"n":{"df":71,"docs":{"100":{"tf":1.7320508075688772},"103":{"tf":1.0},"104":{"tf":1.0},"107":{"tf":1.4142135623730951},"11":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.4142135623730951},"112":{"tf":1.4142135623730951},"114":{"tf":2.23606797749979},"115":{"tf":1.4142135623730951},"117":{"tf":1.4142135623730951},"119":{"tf":1.7320508075688772},"120":{"tf":1.0},"123":{"tf":1.0},"128":{"tf":1.7320508075688772},"129":{"tf":1.4142135623730951},"130":{"tf":1.0},"132":{"tf":1.0},"14":{"tf":2.0},"16":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":2.0},"23":{"tf":1.4142135623730951},"24":{"tf":2.449489742783178},"26":{"tf":1.4142135623730951},"28":{"tf":1.0},"29":{"tf":2.449489742783178},"30":{"tf":1.4142135623730951},"31":{"tf":2.0},"33":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":2.0},"40":{"tf":2.0},"41":{"tf":1.0},"44":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.4142135623730951},"49":{"tf":1.0},"5":{"tf":1.0},"50":{"tf":1.0},"52":{"tf":2.0},"53":{"tf":1.7320508075688772},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.7320508075688772},"57":{"tf":1.7320508075688772},"58":{"tf":1.0},"6":{"tf":1.4142135623730951},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.4142135623730951},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.4142135623730951},"73":{"tf":1.0},"76":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"87":{"tf":1.4142135623730951},"99":{"tf":1.4142135623730951}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":21,"docs":{"107":{"tf":1.0},"111":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.0},"132":{"tf":1.0},"14":{"tf":1.4142135623730951},"16":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"43":{"tf":1.0},"48":{"tf":1.4142135623730951},"49":{"tf":1.0},"57":{"tf":1.4142135623730951},"70":{"tf":1.0},"72":{"tf":1.0},"88":{"tf":1.0}}}}}},"r":{"c":{"df":1,"docs":{"87":{"tf":1.0}}},"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"!":{"(":{"\\"":{"df":0,"docs":{},"{":{"df":0,"docs":{},"}":{"b":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"44":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"129":{"tf":1.0},"24":{"tf":1.4142135623730951},"50":{"tf":1.0}}}},"df":1,"docs":{"21":{"tf":1.0}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}}}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"29":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"d":{"\\"":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"111":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"75":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":1,"docs":{"75":{"tf":1.0}}}},"o":{"df":0,"docs":{},"m":{"(":{"df":1,"docs":{"28":{"tf":1.0}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":2,"docs":{"24":{"tf":1.0},"26":{"tf":1.0}}}}},"df":0,"docs":{}}},"<":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"c":{"2":{"8":{"2":{"2":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"u":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":1.4142135623730951}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"t":{">":{">":{"(":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"26":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"u":{"df":1,"docs":{"25":{"tf":1.0}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"c":{"<":{"df":0,"docs":{},"u":{"df":1,"docs":{"25":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"111":{"tf":1.0}},"i":{"df":1,"docs":{"0":{"tf":1.0}}}}},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":19,"docs":{"107":{"tf":1.0},"114":{"tf":1.0},"115":{"tf":1.4142135623730951},"117":{"tf":1.0},"119":{"tf":1.0},"14":{"tf":1.7320508075688772},"16":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.4142135623730951},"39":{"tf":1.0},"47":{"tf":1.0},"49":{"tf":1.0},"52":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"73":{"tf":1.4142135623730951},"76":{"tf":2.0},"78":{"tf":1.7320508075688772},"93":{"tf":1.0}}}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"129":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"63":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"78":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{":":{":":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"84":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{"0":{".":{".":{"1":{"0":{"df":2,"docs":{"100":{"tf":1.0},"23":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{"0":{".":{".":{"2":{"0":{"df":1,"docs":{"23":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"{":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"100":{"tf":1.0},"23":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"0":{"tf":1.0},"114":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"34":{"tf":2.449489742783178},"42":{"tf":1.7320508075688772},"43":{"tf":2.0},"44":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"61":{"tf":1.0}}}}},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"53":{"tf":1.0},"87":{"tf":1.0}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"96":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.0}},"h":{"df":0,"docs":{},"u":{"b":{"df":1,"docs":{"111":{"tf":1.0}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":4,"docs":{"111":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.0},"81":{"tf":1.0}},"n":{"df":2,"docs":{"42":{"tf":1.0},"62":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"14":{"tf":1.4142135623730951},"15":{"tf":1.0},"16":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":1,"docs":{"77":{"tf":1.0}},"o":{"d":{"df":1,"docs":{"90":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":2,"docs":{"45":{"tf":1.0},"89":{"tf":1.0}},"q":{"df":0,"docs":{},"l":{"\'":{"df":3,"docs":{"27":{"tf":1.0},"31":{"tf":1.0},"75":{"tf":1.4142135623730951}}},"(":{"c":{"a":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"(":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"x":{"_":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"57":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"57":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":5,"docs":{"11":{"tf":1.0},"111":{"tf":1.0},"123":{"tf":1.0},"129":{"tf":1.0},"72":{"tf":1.4142135623730951}}}}}}},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}},"e":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"34":{"tf":2.0},"43":{"tf":2.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"39":{"tf":1.0},"41":{"tf":1.0}}}}}}},"df":3,"docs":{"39":{"tf":1.4142135623730951},"41":{"tf":1.4142135623730951},"49":{"tf":1.0}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"d":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"24":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"26":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"s":{"c":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":7,"docs":{"117":{"tf":1.0},"118":{"tf":1.0},"119":{"tf":1.7320508075688772},"120":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.0},"130":{"tf":1.0}}}},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":3,"docs":{"127":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":2.0}}}}}}}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"30":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"52":{"tf":1.4142135623730951},"53":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"125":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"12":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":2,"docs":{"119":{"tf":1.0},"129":{"tf":1.0}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"29":{"tf":1.4142135623730951}},"e":{"=":{"\\"":{"a":{"a":{"a":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"44":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"126":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"128":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"129":{"tf":1.4142135623730951}}}}}}}},"s":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}}}},"df":0,"docs":{}},"h":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"123":{"tf":1.4142135623730951},"124":{"tf":1.0},"125":{"tf":1.0}}}},"df":0,"docs":{}},"k":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"10":{"tf":1.4142135623730951}}}}}},"t":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"131":{"tf":1.0}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"121":{"tf":1.4142135623730951}}}}}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"55":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"54":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"73":{"tf":2.0}}}}}},"/":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"_":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"89":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"/":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"105":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"97":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":1,"docs":{"101":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"95":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"s":{"c":{"a":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"107":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"98":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":50,"docs":{"0":{"tf":2.23606797749979},"1":{"tf":1.4142135623730951},"10":{"tf":1.4142135623730951},"107":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"113":{"tf":1.4142135623730951},"114":{"tf":1.0},"115":{"tf":1.4142135623730951},"116":{"tf":1.4142135623730951},"117":{"tf":1.0},"118":{"tf":1.0},"129":{"tf":1.0},"24":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"29":{"tf":2.449489742783178},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"4":{"tf":2.0},"40":{"tf":1.0},"42":{"tf":1.0},"45":{"tf":1.4142135623730951},"47":{"tf":1.0},"48":{"tf":1.0},"5":{"tf":2.23606797749979},"54":{"tf":1.0},"57":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"59":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.4142135623730951},"69":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.7320508075688772},"80":{"tf":1.7320508075688772},"85":{"tf":1.4142135623730951},"88":{"tf":1.0},"89":{"tf":1.7320508075688772},"9":{"tf":1.4142135623730951},"90":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0},"93":{"tf":2.449489742783178},"98":{"tf":2.0}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"103":{"tf":1.0}}}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{":":{":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"&":{"*":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{")":{")":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":1,"docs":{"104":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"54":{"tf":2.23606797749979}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"19":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"111":{"tf":1.0}}}}}},"u":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"52":{"tf":2.23606797749979},"53":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"52":{"tf":1.7320508075688772}}}}}}},"h":{"a":{"df":0,"docs":{},"n":{"d":{"df":4,"docs":{"11":{"tf":1.0},"20":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.0}},"i":{"df":1,"docs":{"28":{"tf":1.0}}},"l":{"df":1,"docs":{"21":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"d":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"108":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"74":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"18":{"tf":1.0},"19":{"tf":2.8284271247461903},"57":{"tf":1.0}}}}},"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":1,"docs":{"2":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"\\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"117":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"117":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"19":{"tf":1.0}}}},"p":{"df":2,"docs":{"25":{"tf":1.0},"89":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":7,"docs":{"103":{"tf":1.0},"21":{"tf":1.0},"27":{"tf":1.0},"5":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.4142135623730951},"85":{"tf":1.0}}}}},"i":{"d":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"73":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"e":{"df":1,"docs":{"73":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":1,"docs":{"0":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"0":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":3,"docs":{"75":{"tf":1.0},"77":{"tf":1.0},"79":{"tf":1.4142135623730951}}}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"57":{"tf":1.0}}},"s":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"b":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"2":{"tf":1.0}}}},"t":{"df":0,"docs":{},"p":{":":{":":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"_":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":2,"docs":{"19":{"tf":1.4142135623730951},"57":{"tf":1.0}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.0}}}}}}}}},"s":{":":{"/":{"/":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"v":{"1":{".":{"0":{"df":1,"docs":{"132":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":3,"docs":{"101":{"tf":1.0},"105":{"tf":1.0},"97":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":1,"docs":{"82":{"tf":1.0}}}},"m":{"a":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"128":{"tf":2.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"128":{"tf":2.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"n":{"d":{"df":0,"docs":{},"r":{"df":1,"docs":{"109":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{"\'":{"df":0,"docs":{},"v":{"df":1,"docs":{"1":{"tf":1.0}}}},".":{"df":1,"docs":{"13":{"tf":1.0}}},"3":{"2":{":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"39":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":34,"docs":{"10":{"tf":1.7320508075688772},"100":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"108":{"tf":1.4142135623730951},"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"120":{"tf":2.0},"14":{"tf":1.0},"17":{"tf":1.0},"20":{"tf":2.0},"22":{"tf":1.4142135623730951},"23":{"tf":1.7320508075688772},"32":{"tf":2.449489742783178},"33":{"tf":1.0},"34":{"tf":2.0},"37":{"tf":1.0},"39":{"tf":2.6457513110645907},"40":{"tf":3.605551275463989},"41":{"tf":2.0},"43":{"tf":2.0},"47":{"tf":1.0},"49":{"tf":1.4142135623730951},"5":{"tf":2.0},"50":{"tf":1.7320508075688772},"52":{"tf":1.4142135623730951},"53":{"tf":2.449489742783178},"56":{"tf":2.23606797749979},"57":{"tf":1.7320508075688772},"58":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"73":{"tf":1.7320508075688772},"95":{"tf":1.0},"96":{"tf":1.0}}},"df":0,"docs":{}},"6":{"4":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"d":{"df":12,"docs":{"110":{"tf":4.58257569495584},"111":{"tf":2.0},"119":{"tf":4.58257569495584},"121":{"tf":2.449489742783178},"126":{"tf":2.23606797749979},"127":{"tf":2.0},"128":{"tf":3.1622776601683795},"129":{"tf":3.1622776601683795},"130":{"tf":2.23606797749979},"131":{"tf":1.7320508075688772},"14":{"tf":1.7320508075688772},"54":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"87":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"128":{"tf":1.4142135623730951}}}}}},"m":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"110":{"tf":1.0}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"<":{"df":0,"docs":{},"t":{"df":1,"docs":{"44":{"tf":1.0}}}},"df":62,"docs":{"100":{"tf":1.7320508075688772},"107":{"tf":1.0},"11":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.4142135623730951},"112":{"tf":1.4142135623730951},"114":{"tf":2.23606797749979},"117":{"tf":1.0},"119":{"tf":1.0},"120":{"tf":1.0},"123":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.4142135623730951},"130":{"tf":1.0},"14":{"tf":2.0},"16":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":2.0},"23":{"tf":2.0},"24":{"tf":2.0},"25":{"tf":1.4142135623730951},"26":{"tf":1.0},"28":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"33":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.4142135623730951},"5":{"tf":1.0},"50":{"tf":1.0},"52":{"tf":2.0},"53":{"tf":1.7320508075688772},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.7320508075688772},"57":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"99":{"tf":1.0}},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":35,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"107":{"tf":1.4142135623730951},"108":{"tf":1.0},"112":{"tf":1.4142135623730951},"114":{"tf":1.0},"118":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.0},"129":{"tf":1.0},"130":{"tf":1.0},"16":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"28":{"tf":1.0},"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.7320508075688772},"43":{"tf":1.0},"5":{"tf":1.0},"59":{"tf":1.0},"63":{"tf":1.4142135623730951},"65":{"tf":1.0},"66":{"tf":1.7320508075688772},"75":{"tf":1.0},"78":{"tf":1.0},"80":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"89":{"tf":1.0},"9":{"tf":1.0},"93":{"tf":1.0},"98":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":4,"docs":{"1":{"tf":1.0},"115":{"tf":1.0},"121":{"tf":1.0},"132":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":2,"docs":{"57":{"tf":1.0},"87":{"tf":1.0}}}}}}},"n":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":2.0}}}},"df":0,"docs":{}}}}},"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"32":{"tf":1.0}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"125":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":5,"docs":{"10":{"tf":1.0},"113":{"tf":1.0},"132":{"tf":1.0},"25":{"tf":1.0},"88":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"m":{"df":2,"docs":{"75":{"tf":1.0},"78":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"_":{"df":0,"docs":{},"w":{"df":1,"docs":{"104":{"tf":1.0}}}},"df":1,"docs":{"103":{"tf":1.0}}}},"i":{"c":{"df":4,"docs":{"122":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.0}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"30":{"tf":1.0}}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"u":{"df":1,"docs":{"118":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{">":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":1,"docs":{"99":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"o":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"83":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"65":{"tf":1.4142135623730951},"83":{"tf":1.0}},"r":{"df":0,"docs":{},"m":{"df":5,"docs":{"113":{"tf":1.0},"59":{"tf":1.0},"61":{"tf":1.0},"76":{"tf":1.0},"8":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"30":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"36":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"54":{"tf":1.0},"55":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"12":{"tf":1.0},"34":{"tf":1.0}}}},"df":0,"docs":{}}},"df":14,"docs":{"107":{"tf":1.0},"115":{"tf":2.23606797749979},"12":{"tf":1.4142135623730951},"129":{"tf":1.0},"130":{"tf":1.0},"21":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":2.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"41":{"tf":1.0},"54":{"tf":1.0}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":6,"docs":{"12":{"tf":1.0},"120":{"tf":1.0},"33":{"tf":2.0},"34":{"tf":2.23606797749979},"37":{"tf":1.4142135623730951},"54":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"34":{"tf":2.0},"37":{"tf":1.0}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"<":{"df":0,"docs":{},"i":{"3":{"2":{"df":1,"docs":{"56":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"107":{"tf":1.0},"24":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"19":{"tf":2.0},"75":{"tf":1.0}}}}},"i":{"d":{"df":3,"docs":{"16":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"18":{"tf":1.0}}},"df":0,"docs":{}},"t":{"a":{"df":0,"docs":{},"n":{"c":{"df":3,"docs":{"114":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"42":{"tf":1.4142135623730951},"43":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":4,"docs":{"22":{"tf":1.0},"25":{"tf":1.0},"66":{"tf":1.0},"87":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"o":{"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"126":{"tf":1.4142135623730951},"127":{"tf":1.0},"128":{"tf":1.0}}}},"df":0,"docs":{}}}},"t":{"df":8,"docs":{"115":{"tf":1.4142135623730951},"120":{"tf":1.4142135623730951},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"125":{"tf":1.7320508075688772},"129":{"tf":1.4142135623730951},"44":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"g":{"df":3,"docs":{"107":{"tf":1.4142135623730951},"21":{"tf":1.0},"49":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"49":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":5,"docs":{"4":{"tf":1.7320508075688772},"8":{"tf":1.0},"91":{"tf":1.0},"93":{"tf":1.0},"98":{"tf":1.0}}}},"r":{"df":0,"docs":{},"f":{"a":{"c":{"df":6,"docs":{"22":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":2.23606797749979},"30":{"tf":1.4142135623730951},"31":{"tf":1.7320508075688772},"40":{"tf":1.0}},"e":{"\'":{"df":1,"docs":{"29":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"o":{":":{":":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"29":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"73":{"tf":2.449489742783178}}}},"df":0,"docs":{}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"126":{"tf":1.0}}}}}}}}}},"p":{"df":1,"docs":{"54":{"tf":1.4142135623730951}}},"s":{"_":{"a":{"d":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"(":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":1,"docs":{"73":{"tf":1.0}}}}},"df":0,"docs":{}},"df":1,"docs":{"73":{"tf":2.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"d":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"(":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"73":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":1,"docs":{"118":{"tf":1.0}}}}},"t":{"\'":{"df":13,"docs":{"125":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"27":{"tf":1.0},"36":{"tf":1.0},"69":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.4142135623730951},"78":{"tf":2.0},"88":{"tf":1.0},"89":{"tf":1.0},"9":{"tf":1.0},"90":{"tf":1.0}},"l":{"df":0,"docs":{},"l":{"df":4,"docs":{"75":{"tf":1.0},"78":{"tf":1.0},"80":{"tf":1.0},"82":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":2,"docs":{"27":{"tf":1.0},"73":{"tf":1.7320508075688772}}}}}},"j":{"a":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"76":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"28":{"tf":1.0}}}},"y":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":6,"docs":{"119":{"tf":1.4142135623730951},"120":{"tf":1.0},"121":{"tf":1.0},"126":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":11,"docs":{"111":{"tf":1.0},"112":{"tf":1.4142135623730951},"118":{"tf":1.7320508075688772},"119":{"tf":1.7320508075688772},"120":{"tf":3.872983346207417},"121":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"36":{"tf":1.0},"59":{"tf":1.0},"65":{"tf":1.4142135623730951},"87":{"tf":2.0}},"{":{"a":{"df":1,"docs":{"120":{"tf":1.0}}},"df":0,"docs":{}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":2,"docs":{"20":{"tf":1.0},"76":{"tf":1.0}}}}}},"l":{"a":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"10":{"tf":1.0}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"5":{"tf":1.0},"50":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"g":{"df":2,"docs":{"69":{"tf":1.0},"87":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"58":{"tf":2.449489742783178},"80":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"98":{"tf":1.0}}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"83":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"118":{"tf":1.0}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"90":{"tf":1.0}}}}},"df":0,"docs":{},"n":{"df":1,"docs":{"44":{"tf":1.7320508075688772}},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"54":{"tf":2.23606797749979}}}}}},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"54":{"tf":2.23606797749979},"64":{"tf":1.0}}}},"t":{"\'":{"df":2,"docs":{"11":{"tf":1.0},"76":{"tf":1.0}}},"df":2,"docs":{"109":{"tf":1.0},"63":{"tf":1.0}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":3,"docs":{"32":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0}}}}}},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.0},"4":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"l":{"df":3,"docs":{"75":{"tf":1.0},"79":{"tf":1.4142135623730951},"84":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"16":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"(":{"5":{"df":1,"docs":{"71":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"5":{"df":1,"docs":{"70":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":6,"docs":{"113":{"tf":1.0},"22":{"tf":1.0},"68":{"tf":1.0},"70":{"tf":2.0},"71":{"tf":2.0},"72":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"k":{"df":3,"docs":{"117":{"tf":1.0},"121":{"tf":1.0},"132":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":6,"docs":{"37":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.7320508075688772},"69":{"tf":1.0},"72":{"tf":1.4142135623730951},"93":{"tf":1.0}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":4,"docs":{"129":{"tf":1.0},"18":{"tf":1.0},"31":{"tf":1.0},"92":{"tf":1.0}}}}}},"o":{"a":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"111":{"tf":1.0},"112":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"112":{"tf":1.4142135623730951},"87":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"i":{"d":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"111":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"<":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"112":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"u":{"6":{"4":{"df":1,"docs":{"111":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"112":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":2,"docs":{"111":{"tf":1.0},"112":{"tf":1.0}}}}},"df":0,"docs":{}},"c":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"28":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":2.0}}}}}}},"t":{"df":8,"docs":{"114":{"tf":1.0},"115":{"tf":2.449489742783178},"131":{"tf":1.0},"132":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":1,"docs":{"90":{"tf":1.0}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"90":{"tf":1.4142135623730951},"92":{"tf":1.0}}}}},"i":{"c":{"df":4,"docs":{"24":{"tf":1.0},"76":{"tf":2.0},"78":{"tf":1.0},"83":{"tf":1.4142135623730951}}},"df":0,"docs":{},"n":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"48":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"48":{"tf":1.4142135623730951}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":10,"docs":{"109":{"tf":1.0},"118":{"tf":1.4142135623730951},"119":{"tf":1.4142135623730951},"120":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.4142135623730951},"47":{"tf":1.0},"61":{"tf":1.0}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"119":{"tf":1.0}}}}}},"t":{"df":4,"docs":{"74":{"tf":1.4142135623730951},"75":{"tf":1.0},"76":{"tf":1.0},"85":{"tf":1.0}}}}},"m":{"a":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":8,"docs":{"0":{"tf":1.0},"108":{"tf":1.4142135623730951},"11":{"tf":2.449489742783178},"114":{"tf":1.0},"115":{"tf":1.0},"132":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"117":{"tf":1.0},"15":{"tf":1.0}}}},"k":{"df":0,"docs":{},"e":{"df":4,"docs":{"1":{"tf":1.0},"109":{"tf":1.0},"129":{"tf":1.0},"22":{"tf":1.0}}}},"n":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":1,"docs":{"110":{"tf":1.0}}},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"117":{"tf":1.0},"30":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"p":{"(":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"49":{"tf":1.0}}}}}},"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"(":{"a":{"df":0,"docs":{},"r":{"c":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"111":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"|":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":2,"docs":{"21":{"tf":1.0},"62":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"66":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"k":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"111":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":3,"docs":{"10":{"tf":1.7320508075688772},"5":{"tf":1.0},"59":{"tf":1.0}}},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"35":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"107":{"tf":1.0},"114":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"54":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}}},"df":0,"docs":{}},"x":{"_":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"57":{"tf":1.4142135623730951}},"e":{"=":{"3":{"0":{"df":1,"docs":{"57":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"6":{"0":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":2,"docs":{"54":{"tf":1.0},"55":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":2,"docs":{"70":{"tf":1.0},"71":{"tf":1.0}}}}}}},"y":{"b":{"df":1,"docs":{"73":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"n":{"df":3,"docs":{"0":{"tf":1.0},"37":{"tf":1.0},"45":{"tf":1.0}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"57":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"11":{"tf":1.0},"32":{"tf":2.23606797749979},"55":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"g":{"df":3,"docs":{"22":{"tf":2.0},"23":{"tf":1.7320508075688772},"57":{"tf":1.0}},"e":{"d":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"22":{"tf":1.4142135623730951},"23":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"23":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":6,"docs":{"14":{"tf":1.0},"21":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"65":{"tf":1.0},"70":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"131":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":6,"docs":{"117":{"tf":1.0},"29":{"tf":2.0},"34":{"tf":1.0},"43":{"tf":1.0},"50":{"tf":1.0},"58":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"91":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"d":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"75":{"tf":1.0},"76":{"tf":2.8284271247461903}},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"76":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"76":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"126":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":1,"docs":{"57":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"73":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"68":{"tf":1.0}}}}}},"o":{"d":{"df":1,"docs":{"28":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":3,"docs":{"113":{"tf":1.4142135623730951},"31":{"tf":1.0},"74":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"22":{"tf":1.0}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"89":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":12,"docs":{"101":{"tf":1.0},"105":{"tf":1.0},"108":{"tf":1.0},"116":{"tf":1.0},"121":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"58":{"tf":1.0},"65":{"tf":1.4142135623730951},"76":{"tf":1.0},"8":{"tf":1.0},"97":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"109":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":3,"docs":{"18":{"tf":1.0},"58":{"tf":1.0},"99":{"tf":1.0}}},"i":{"df":1,"docs":{"22":{"tf":1.0}},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.7320508075688772}}}}}}},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":12,"docs":{"112":{"tf":1.4142135623730951},"116":{"tf":1.0},"118":{"tf":1.0},"120":{"tf":1.0},"122":{"tf":1.0},"19":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772},"28":{"tf":1.0},"34":{"tf":1.0},"43":{"tf":1.0},"54":{"tf":1.0},"57":{"tf":1.4142135623730951}},"e":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"t":{"a":{"df":0,"docs":{},"t":{"df":9,"docs":{"22":{"tf":1.0},"33":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"48":{"tf":2.6457513110645907},"5":{"tf":1.0},"6":{"tf":1.0},"82":{"tf":1.0},"98":{"tf":1.0}}}},"df":3,"docs":{"18":{"tf":1.0},"49":{"tf":1.0},"58":{"tf":1.7320508075688772}}}},"v":{"df":1,"docs":{"108":{"tf":2.0}}},"y":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"39":{"tf":1.7320508075688772},"40":{"tf":1.4142135623730951},"41":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"21":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"73":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"63":{"tf":1.0}}}}}}}}}}}}}}}}}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"63":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"21":{"tf":1.4142135623730951},"63":{"tf":1.7320508075688772}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"41":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"a":{"c":{"df":2,"docs":{"30":{"tf":2.449489742783178},"40":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"61":{"tf":1.0},"65":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{"d":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":7,"docs":{"78":{"tf":2.0},"79":{"tf":1.4142135623730951},"80":{"tf":1.4142135623730951},"81":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951},"83":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"b":{"df":0,"docs":{},"j":{"(":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":1,"docs":{"40":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":5,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"20":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"73":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"(":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":3,"docs":{"10":{"tf":1.4142135623730951},"14":{"tf":1.4142135623730951},"30":{"tf":2.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"12":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":2,"docs":{"30":{"tf":1.0},"99":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"1":{"0":{"0":{"df":1,"docs":{"56":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":1,"docs":{"56":{"tf":2.0}}},"df":0,"docs":{}},"u":{"df":1,"docs":{"108":{"tf":1.0}}}}},"df":0,"docs":{}}}},"n":{"+":{"1":{"df":2,"docs":{"109":{"tf":1.0},"118":{"tf":1.0}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"110":{"tf":1.0},"111":{"tf":1.0}}}}}}},"df":0,"docs":{}},".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":1,"docs":{"111":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"o":{"_":{"c":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"_":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},":":{"\\"":{"\\"":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":21,"docs":{"110":{"tf":4.47213595499958},"111":{"tf":2.0},"12":{"tf":1.0},"127":{"tf":1.4142135623730951},"128":{"tf":1.4142135623730951},"129":{"tf":1.4142135623730951},"131":{"tf":1.4142135623730951},"14":{"tf":1.0},"18":{"tf":1.0},"22":{"tf":1.4142135623730951},"23":{"tf":1.0},"26":{"tf":1.0},"27":{"tf":1.4142135623730951},"29":{"tf":3.3166247903554},"30":{"tf":2.0},"34":{"tf":1.4142135623730951},"42":{"tf":1.4142135623730951},"44":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.0},"8":{"tf":1.0}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"20":{"tf":1.0}}}}}},"df":4,"docs":{"110":{"tf":1.0},"54":{"tf":3.0},"56":{"tf":2.0},"58":{"tf":1.0}},"e":{"df":0,"docs":{},"e":{"d":{"df":19,"docs":{"103":{"tf":1.0},"107":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.4142135623730951},"114":{"tf":1.0},"121":{"tf":1.0},"129":{"tf":1.0},"130":{"tf":1.4142135623730951},"14":{"tf":1.0},"16":{"tf":1.4142135623730951},"22":{"tf":1.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"34":{"tf":1.0},"4":{"tf":1.7320508075688772},"45":{"tf":1.0},"53":{"tf":1.4142135623730951},"63":{"tf":1.0},"76":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{"df":5,"docs":{"120":{"tf":1.4142135623730951},"121":{"tf":1.0},"130":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"70":{"tf":1.4142135623730951}},"e":{"d":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"120":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"87":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"45":{"tf":1.0}}}}},"w":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"53":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"df":1,"docs":{"56":{"tf":1.0}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}}}}},"df":5,"docs":{"12":{"tf":1.0},"126":{"tf":1.0},"24":{"tf":1.0},"28":{"tf":1.0},"62":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"88":{"tf":1.0}}}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"x":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"(":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"76":{"tf":1.0},"78":{"tf":1.4142135623730951},"81":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":5,"docs":{"79":{"tf":1.0},"80":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0}}}}},"df":0,"docs":{}},"df":1,"docs":{"76":{"tf":1.7320508075688772}}}}}},"df":8,"docs":{"76":{"tf":2.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"<":{"\'":{"_":{"df":1,"docs":{"82":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"m":{"df":0,"docs":{},"i":{"d":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"<":{"\'":{"_":{"df":1,"docs":{"76":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"<":{"\'":{"_":{"df":1,"docs":{"80":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"<":{"\'":{"_":{"df":1,"docs":{"79":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"<":{"\'":{"_":{"df":1,"docs":{"78":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"<":{"\'":{"_":{"df":1,"docs":{"83":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"e":{"<":{"\'":{"_":{"df":1,"docs":{"84":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"\'":{"_":{"df":1,"docs":{"81":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"11":{"tf":1.0},"72":{"tf":1.0}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"129":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"l":{"df":4,"docs":{"0":{"tf":1.0},"132":{"tf":1.0},"34":{"tf":1.0},"43":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":12,"docs":{"11":{"tf":1.0},"115":{"tf":1.0},"12":{"tf":1.0},"121":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.0},"16":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"34":{"tf":1.0},"43":{"tf":1.0},"72":{"tf":1.0}}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"63":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}},"i":{"c":{"df":2,"docs":{"109":{"tf":1.0},"119":{"tf":1.0}}},"df":0,"docs":{}}},"w":{"df":3,"docs":{"103":{"tf":1.0},"2":{"tf":1.0},"88":{"tf":1.0}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}}}},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"54":{"tf":1.7320508075688772},"70":{"tf":1.0},"71":{"tf":1.0}},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"58":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"o":{"b":{"df":0,"docs":{},"j":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"20":{"tf":1.0},"30":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"20":{"tf":1.4142135623730951}},"e":{"c":{"df":0,"docs":{},"t":{"\'":{"df":2,"docs":{"22":{"tf":1.0},"29":{"tf":1.0}}},"(":{"c":{"a":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"(":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"x":{"_":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"57":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":59,"docs":{"10":{"tf":1.7320508075688772},"100":{"tf":1.0},"11":{"tf":1.7320508075688772},"110":{"tf":1.4142135623730951},"114":{"tf":1.0},"115":{"tf":2.449489742783178},"117":{"tf":1.0},"119":{"tf":2.449489742783178},"120":{"tf":1.4142135623730951},"128":{"tf":1.0},"129":{"tf":1.0},"13":{"tf":1.0},"130":{"tf":1.0},"132":{"tf":1.0},"14":{"tf":2.0},"16":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":3.0},"23":{"tf":1.0},"24":{"tf":1.0},"29":{"tf":2.23606797749979},"30":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"34":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.4142135623730951},"43":{"tf":1.0},"45":{"tf":2.8284271247461903},"47":{"tf":2.449489742783178},"48":{"tf":2.23606797749979},"49":{"tf":1.4142135623730951},"5":{"tf":1.7320508075688772},"50":{"tf":1.0},"52":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.4142135623730951},"58":{"tf":1.0},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"9":{"tf":1.0},"99":{"tf":1.0}}}},"df":0,"docs":{}}}},"c":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":4,"docs":{"12":{"tf":1.0},"14":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"2":{"0":{"2":{"1":{"df":1,"docs":{"80":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"0":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"116":{"tf":1.0},"37":{"tf":1.0},"89":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"k":{"(":{"\\"":{"2":{"3":{"4":{"a":{"df":3,"docs":{"21":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{"df":0,"docs":{},"i":{"d":{")":{"?":{".":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"14":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"110":{"tf":1.0}}}},"df":1,"docs":{"65":{"tf":1.4142135623730951}}},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"x":{":":{":":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"a":{"df":0,"docs":{},"s":{"(":{"\\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"111":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{")":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"b":{"df":1,"docs":{"107":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},":":{":":{"<":{"_":{"df":3,"docs":{"18":{"tf":1.0},"58":{"tf":1.0},"99":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"52":{"tf":1.0},"53":{"tf":1.0},"56":{"tf":1.0}}},"l":{"d":{"df":1,"docs":{"88":{"tf":1.0}}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"125":{"tf":1.0},"132":{"tf":1.0},"54":{"tf":1.0}}}}},"n":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"63":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":15,"docs":{"0":{"tf":1.0},"115":{"tf":1.0},"118":{"tf":1.0},"121":{"tf":1.0},"126":{"tf":1.0},"19":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"27":{"tf":1.0},"37":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.0},"80":{"tf":1.0},"82":{"tf":1.0}},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":1,"docs":{"113":{"tf":1.0}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"91":{"tf":1.7320508075688772}}}}}}}}}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"82":{"tf":1.0}},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"82":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":2,"docs":{"113":{"tf":1.0},"128":{"tf":1.0}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}}}},"t":{"df":1,"docs":{"115":{"tf":2.23606797749979}},"i":{"df":0,"docs":{},"m":{"df":4,"docs":{"109":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.0},"20":{"tf":1.0}}},"o":{"df":0,"docs":{},"n":{"<":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"3":{"2":{"df":1,"docs":{"58":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"130":{"tf":1.0}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":4,"docs":{"111":{"tf":1.0},"18":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"87":{"tf":1.0}}}}},"t":{"df":5,"docs":{"25":{"tf":1.0},"26":{"tf":1.0},"34":{"tf":1.7320508075688772},"43":{"tf":1.4142135623730951},"5":{"tf":1.0}}},"u":{"3":{"2":{"df":1,"docs":{"129":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"6":{"4":{"df":1,"docs":{"115":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"26":{"tf":1.0}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}},"e":{"d":{"2":{"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"26":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"t":{"df":1,"docs":{"26":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":6,"docs":{"129":{"tf":1.4142135623730951},"14":{"tf":1.0},"25":{"tf":1.0},"33":{"tf":1.0},"45":{"tf":1.0},"5":{"tf":1.4142135623730951}}}}}}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"129":{"tf":1.0},"130":{"tf":1.7320508075688772},"28":{"tf":1.0},"76":{"tf":1.0}}}}},"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"121":{"tf":2.0}},"i":{"df":0,"docs":{},"z":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\'":{"df":1,"docs":{"89":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"74":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"25":{"tf":1.0},"28":{"tf":1.0}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"12":{"tf":1.0}}}}}}}}},"u":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"128":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":4,"docs":{"116":{"tf":1.0},"32":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0}},"g":{"df":0,"docs":{},"o":{"df":1,"docs":{"75":{"tf":1.0}}}},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"128":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":10,"docs":{"107":{"tf":1.4142135623730951},"12":{"tf":1.0},"132":{"tf":1.0},"24":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.0},"7":{"tf":1.0},"78":{"tf":1.0},"86":{"tf":1.0},"98":{"tf":1.4142135623730951}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":3,"docs":{"34":{"tf":1.4142135623730951},"43":{"tf":1.7320508075688772},"44":{"tf":1.4142135623730951}},"e":{">":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"(":{")":{")":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"44":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"126":{"tf":1.0},"63":{"tf":1.0},"72":{"tf":1.0}},"r":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"121":{"tf":1.0},"126":{"tf":1.4142135623730951}},"e":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"126":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"19":{"tf":1.0},"65":{"tf":1.0}}}}}}}}},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"126":{"tf":1.0}}}}}}}}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":1,"docs":{"58":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"m":{"df":3,"docs":{"115":{"tf":1.4142135623730951},"34":{"tf":1.0},"43":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":10,"docs":{"114":{"tf":1.0},"25":{"tf":1.0},"34":{"tf":1.0},"39":{"tf":1.7320508075688772},"43":{"tf":1.0},"49":{"tf":1.4142135623730951},"53":{"tf":1.7320508075688772},"54":{"tf":1.0},"57":{"tf":2.0},"72":{"tf":1.0}}}},"s":{"(":{"df":0,"docs":{},"i":{"3":{"2":{"df":1,"docs":{"34":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"34":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951}}}}}}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"34":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"32":{"tf":1.0},"36":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"28":{"tf":1.4142135623730951}}}}},"s":{"df":6,"docs":{"107":{"tf":1.4142135623730951},"21":{"tf":1.7320508075688772},"62":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0},"80":{"tf":1.0}},"e":{"(":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":2,"docs":{"107":{"tf":1.0},"24":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"80":{"tf":1.7320508075688772}}}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":5,"docs":{"21":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"63":{"tf":1.0},"66":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"21":{"tf":1.0},"62":{"tf":1.0},"66":{"tf":1.4142135623730951}}}}}}}}}}}},"t":{"df":2,"docs":{"35":{"tf":1.0},"37":{"tf":1.0}},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"72":{"tf":1.0}},"e":{"df":0,"docs":{},"q":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"52":{"tf":1.4142135623730951},"73":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"121":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"s":{"df":5,"docs":{"103":{"tf":1.0},"129":{"tf":1.4142135623730951},"34":{"tf":1.0},"43":{"tf":1.0},"53":{"tf":1.0}},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"35":{"tf":1.0},"48":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"104":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"49":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"0":{"tf":1.0},"118":{"tf":1.0},"20":{"tf":1.4142135623730951},"57":{"tf":1.0},"67":{"tf":1.4142135623730951},"87":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"87":{"tf":1.7320508075688772}}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"h":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":2,"docs":{"30":{"tf":1.0},"72":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"66":{"tf":1.0}}}},"df":0,"docs":{}}}},"l":{"a":{"c":{"df":0,"docs":{},"e":{"df":3,"docs":{"129":{"tf":1.0},"21":{"tf":1.0},"63":{"tf":1.0}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"89":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"95":{"tf":1.0}}}}}},"{":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"96":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":4,"docs":{"93":{"tf":1.4142135623730951},"94":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0}}}},"o":{"df":0,"docs":{},"l":{"df":6,"docs":{"110":{"tf":1.0},"111":{"tf":1.0},"112":{"tf":1.0},"14":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":2,"docs":{"129":{"tf":1.4142135623730951},"130":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"123":{"tf":1.7320508075688772},"124":{"tf":1.4142135623730951},"125":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"42":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":1,"docs":{"69":{"tf":2.449489742783178}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"112":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"s":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"113":{"tf":1.0},"37":{"tf":1.0}}}}}}},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"68":{"tf":1.4142135623730951}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"78":{"tf":1.0},"79":{"tf":2.23606797749979}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":2,"docs":{"19":{"tf":1.0},"65":{"tf":1.0}}}}}}},"i":{"c":{"df":0,"docs":{},"e":{"df":1,"docs":{"129":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"120":{"tf":2.0}}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"50":{"tf":1.0}},"l":{"df":0,"docs":{},"n":{"df":1,"docs":{"50":{"tf":1.0}}}}}},"v":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"57":{"tf":2.0}}}},"df":0,"docs":{}}},"o":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}},"c":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"0":{"tf":1.0},"22":{"tf":1.0}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":7,"docs":{"15":{"tf":1.0},"2":{"tf":1.0},"20":{"tf":1.0},"28":{"tf":1.0},"76":{"tf":1.0},"77":{"tf":1.4142135623730951},"78":{"tf":1.4142135623730951}}}}}},"d":{"df":0,"docs":{},"u":{"c":{"df":1,"docs":{"115":{"tf":1.0}},"t":{"\\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"128":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":7,"docs":{"121":{"tf":2.23606797749979},"126":{"tf":2.23606797749979},"127":{"tf":1.4142135623730951},"128":{"tf":2.6457513110645907},"129":{"tf":2.449489742783178},"130":{"tf":1.7320508075688772},"57":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"88":{"tf":1.0}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"i":{"d":{"df":21,"docs":{"113":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.7320508075688772},"128":{"tf":2.0},"129":{"tf":1.4142135623730951},"28":{"tf":1.7320508075688772},"48":{"tf":1.0},"5":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"61":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"63":{"tf":1.0},"66":{"tf":1.4142135623730951},"67":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"89":{"tf":1.0},"91":{"tf":1.0},"98":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"128":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"u":{"b":{"df":15,"docs":{"13":{"tf":1.7320508075688772},"130":{"tf":1.0},"14":{"tf":1.0},"18":{"tf":1.0},"26":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.7320508075688772},"32":{"tf":2.449489742783178},"34":{"tf":2.0},"35":{"tf":1.0},"36":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772},"56":{"tf":1.0},"63":{"tf":1.4142135623730951},"87":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"57":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":7,"docs":{"129":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"68":{"tf":1.0},"76":{"tf":1.0},"78":{"tf":1.0}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":66,"docs":{"0":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"109":{"tf":1.7320508075688772},"110":{"tf":2.0},"111":{"tf":2.0},"114":{"tf":1.4142135623730951},"117":{"tf":1.4142135623730951},"118":{"tf":1.0},"119":{"tf":1.7320508075688772},"120":{"tf":1.7320508075688772},"128":{"tf":1.7320508075688772},"129":{"tf":1.4142135623730951},"130":{"tf":1.7320508075688772},"14":{"tf":1.7320508075688772},"15":{"tf":1.0},"16":{"tf":2.0},"17":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":2.6457513110645907},"21":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772},"23":{"tf":1.0},"24":{"tf":2.0},"30":{"tf":2.0},"37":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":2.23606797749979},"5":{"tf":2.0},"50":{"tf":1.4142135623730951},"52":{"tf":1.0},"53":{"tf":1.4142135623730951},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"57":{"tf":2.449489742783178},"58":{"tf":2.0},"6":{"tf":2.23606797749979},"61":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"64":{"tf":1.4142135623730951},"65":{"tf":1.4142135623730951},"67":{"tf":1.7320508075688772},"68":{"tf":1.7320508075688772},"69":{"tf":2.0},"7":{"tf":1.0},"70":{"tf":2.449489742783178},"71":{"tf":2.6457513110645907},"72":{"tf":1.7320508075688772},"75":{"tf":1.0},"77":{"tf":1.4142135623730951},"78":{"tf":1.0},"80":{"tf":2.449489742783178},"81":{"tf":1.7320508075688772},"82":{"tf":2.23606797749979},"86":{"tf":1.0},"87":{"tf":3.0},"88":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"98":{"tf":1.4142135623730951},"99":{"tf":1.0}}},"y":{"(":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.0}}}}}}}}}}}},":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":2,"docs":{"22":{"tf":1.0},"23":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":2,"docs":{"57":{"tf":1.0},"67":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":2,"docs":{"100":{"tf":1.4142135623730951},"99":{"tf":1.4142135623730951}}}}}}}}},"i":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"118":{"tf":1.0},"62":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"3":{"tf":1.0},"60":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"93":{"tf":1.0}}}},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"u":{"df":3,"docs":{"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"33":{"tf":1.0}}}}},"df":0,"docs":{},"w":{"df":1,"docs":{"80":{"tf":1.0}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":2,"docs":{"116":{"tf":1.0},"20":{"tf":1.0}},"m":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"m":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"63":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":1,"docs":{"0":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":2,"docs":{"114":{"tf":1.0},"78":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"d":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"u":{"c":{"df":1,"docs":{"87":{"tf":1.0}}},"df":0,"docs":{}}},"df":1,"docs":{"6":{"tf":1.0}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"118":{"tf":1.4142135623730951},"121":{"tf":1.0},"87":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"c":{"df":2,"docs":{"30":{"tf":1.7320508075688772},"45":{"tf":1.0}}},"df":0,"docs":{}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"=":{"df":0,"docs":{},"r":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":1,"docs":{"54":{"tf":1.0}}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"111":{"tf":1.0},"114":{"tf":1.0},"30":{"tf":1.7320508075688772},"45":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{":":{":":{"<":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"a":{"c":{"df":1,"docs":{"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"i":{"6":{"4":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":1,"docs":{"48":{"tf":1.0}},"i":{"df":1,"docs":{"30":{"tf":1.4142135623730951}}}}}}}},"l":{"a":{"df":0,"docs":{},"t":{"df":3,"docs":{"130":{"tf":1.0},"15":{"tf":1.0},"69":{"tf":1.4142135623730951}},"e":{"d":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":2.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"y":{"\'":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.7320508075688772}}}},"df":0,"docs":{},"v":{"df":1,"docs":{"115":{"tf":1.0}}}},"i":{"df":1,"docs":{"57":{"tf":1.0}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":2.449489742783178}},"e":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{":":{":":{"a":{"df":1,"docs":{"28":{"tf":1.0}}},"b":{"df":1,"docs":{"28":{"tf":1.0}}},"c":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"28":{"tf":1.7320508075688772}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"28":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":1.4142135623730951}}}}}}}},"v":{"df":1,"docs":{"31":{"tf":1.0}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":3,"docs":{"108":{"tf":1.4142135623730951},"27":{"tf":1.0},"33":{"tf":1.0}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"24":{"tf":1.0}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"109":{"tf":1.0}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}},"o":{"df":1,"docs":{"8":{"tf":1.0}},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"2":{"tf":1.0},"88":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":7,"docs":{"8":{"tf":1.4142135623730951},"86":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"90":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0}}}}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":2,"docs":{"37":{"tf":1.0},"72":{"tf":1.0}}}}}},"q":{"df":1,"docs":{"104":{"tf":1.0}},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"78":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":14,"docs":{"103":{"tf":1.4142135623730951},"111":{"tf":1.0},"119":{"tf":1.0},"16":{"tf":1.0},"18":{"tf":2.0},"20":{"tf":1.0},"75":{"tf":2.0},"78":{"tf":2.23606797749979},"79":{"tf":2.0},"84":{"tf":1.4142135623730951},"87":{"tf":1.0},"95":{"tf":1.0},"98":{"tf":1.4142135623730951},"99":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"r":{"df":10,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"119":{"tf":1.0},"127":{"tf":1.4142135623730951},"129":{"tf":3.0},"130":{"tf":1.7320508075688772},"23":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.4142135623730951},"5":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"129":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"59":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"110":{"tf":1.0},"29":{"tf":1.0}}}},"v":{"df":25,"docs":{"11":{"tf":2.23606797749979},"110":{"tf":1.7320508075688772},"114":{"tf":1.4142135623730951},"117":{"tf":1.0},"118":{"tf":1.7320508075688772},"119":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.0},"122":{"tf":1.0},"129":{"tf":1.4142135623730951},"130":{"tf":1.4142135623730951},"14":{"tf":1.7320508075688772},"16":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.0},"29":{"tf":1.4142135623730951},"34":{"tf":1.0},"43":{"tf":1.0},"47":{"tf":1.0},"49":{"tf":1.0},"52":{"tf":1.0},"57":{"tf":2.0},"61":{"tf":1.0},"82":{"tf":1.7320508075688772},"83":{"tf":2.449489742783178}},"e":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"114":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"114":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"<":{"\'":{"_":{"df":1,"docs":{"114":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"<":{"\'":{"_":{"df":1,"docs":{"83":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"p":{"df":1,"docs":{"99":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"75":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":10,"docs":{"126":{"tf":1.0},"18":{"tf":1.0},"61":{"tf":1.0},"65":{"tf":1.0},"69":{"tf":1.4142135623730951},"75":{"tf":1.0},"78":{"tf":1.7320508075688772},"82":{"tf":1.0},"84":{"tf":1.4142135623730951},"86":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"21":{"tf":1.0},"32":{"tf":1.0},"59":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"&":{"\'":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":1,"docs":{"16":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"48":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"<":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"112":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"u":{"6":{"4":{"df":1,"docs":{"111":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"112":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"i":{"3":{"2":{"df":9,"docs":{"21":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"21":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"47":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":4,"docs":{"110":{"tf":1.0},"111":{"tf":1.0},"14":{"tf":1.0},"48":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"49":{"tf":1.0}}}},"df":0,"docs":{}}}}},"t":{"df":1,"docs":{"21":{"tf":1.0}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"81":{"tf":1.0}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":24,"docs":{"111":{"tf":1.0},"119":{"tf":1.0},"120":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"14":{"tf":1.4142135623730951},"21":{"tf":1.0},"39":{"tf":1.0},"52":{"tf":1.4142135623730951},"53":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":2.23606797749979},"64":{"tf":1.0},"67":{"tf":1.4142135623730951},"7":{"tf":1.0},"78":{"tf":1.7320508075688772},"79":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951},"83":{"tf":1.4142135623730951},"99":{"tf":1.0}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":2,"docs":{"64":{"tf":1.0},"65":{"tf":1.0}}}}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":17,"docs":{"11":{"tf":1.0},"110":{"tf":1.0},"114":{"tf":1.0},"14":{"tf":2.0},"16":{"tf":1.0},"21":{"tf":2.0},"37":{"tf":1.0},"39":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.4142135623730951},"52":{"tf":1.0},"61":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.4142135623730951},"99":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"s":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"42":{"tf":1.0}}}},"df":0,"docs":{}},"df":2,"docs":{"24":{"tf":1.0},"34":{"tf":1.0}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"121":{"tf":2.0}}}}}}},"f":{"c":{"2":{"8":{"2":{"2":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":1,"docs":{"68":{"tf":1.4142135623730951}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"93":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"52":{"tf":3.1622776601683795}},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"d":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{":":{":":{"a":{"d":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{")":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"d":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"52":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"52":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":1,"docs":{"52":{"tf":2.449489742783178}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"t":{"df":3,"docs":{"47":{"tf":1.4142135623730951},"48":{"tf":1.7320508075688772},"49":{"tf":1.4142135623730951}}}},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"18":{"tf":1.0},"96":{"tf":1.0}},"e":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":4,"docs":{"118":{"tf":1.0},"119":{"tf":1.0},"120":{"tf":1.0},"128":{"tf":1.4142135623730951}}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":4,"docs":{"11":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"81":{"tf":1.0}}}},"n":{"df":4,"docs":{"75":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.4142135623730951},"8":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"12":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"\'":{"df":3,"docs":{"0":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0}}},"df":9,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.4142135623730951},"128":{"tf":1.0},"129":{"tf":1.4142135623730951},"22":{"tf":1.0},"5":{"tf":1.0},"66":{"tf":1.0},"9":{"tf":1.0},"93":{"tf":1.0}},"f":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}}}}},"s":{"3":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"e":{"df":14,"docs":{"11":{"tf":1.0},"110":{"tf":1.0},"112":{"tf":1.0},"118":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"24":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"36":{"tf":1.0},"57":{"tf":1.0},"76":{"tf":1.0},"84":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"87":{"tf":1.0}}}}},"c":{"a":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"!":{"(":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"108":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}},"e":{"d":{"2":{"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":4,"docs":{"107":{"tf":2.449489742783178},"108":{"tf":1.7320508075688772},"24":{"tf":1.4142135623730951},"5":{"tf":1.0}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"107":{"tf":1.0},"24":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"29":{"tf":1.0}}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}},"e":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"99":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"s":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"50":{"tf":1.0}}}},"df":0,"docs":{}}},":":{":":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"(":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":7,"docs":{"114":{"tf":1.0},"117":{"tf":1.0},"30":{"tf":1.0},"50":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0}}},"y":{":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":6,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"111":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"6":{"tf":1.0},"98":{"tf":1.0}}}}},"df":0,"docs":{}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"6":{"tf":1.0}}},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":2,"docs":{"100":{"tf":1.0},"99":{"tf":1.0}}}}}}}}}}}},"df":2,"docs":{"22":{"tf":1.0},"23":{"tf":1.0}}}}},"s":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"50":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"<":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"18":{"tf":1.0},"30":{"tf":1.0}}},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"99":{"tf":1.0}}}}}}}}}}}},"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"117":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":40,"docs":{"0":{"tf":1.0},"100":{"tf":1.0},"103":{"tf":2.0},"104":{"tf":1.4142135623730951},"111":{"tf":1.4142135623730951},"114":{"tf":1.4142135623730951},"115":{"tf":1.0},"117":{"tf":1.7320508075688772},"118":{"tf":1.0},"119":{"tf":1.0},"120":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.0},"128":{"tf":1.4142135623730951},"131":{"tf":1.0},"132":{"tf":2.0},"14":{"tf":1.0},"15":{"tf":1.0},"17":{"tf":1.7320508075688772},"18":{"tf":1.7320508075688772},"22":{"tf":1.0},"23":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.7320508075688772},"34":{"tf":1.0},"43":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":2.23606797749979},"5":{"tf":1.4142135623730951},"50":{"tf":2.0},"6":{"tf":1.4142135623730951},"67":{"tf":1.7320508075688772},"69":{"tf":1.0},"70":{"tf":1.4142135623730951},"71":{"tf":1.4142135623730951},"89":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.0}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":2,"docs":{"111":{"tf":1.0},"57":{"tf":2.0}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"121":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"50":{"tf":1.7320508075688772}}}},"df":2,"docs":{"29":{"tf":2.23606797749979},"31":{"tf":2.0}},"e":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":2,"docs":{"20":{"tf":1.0},"37":{"tf":1.0}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"14":{"tf":1.0},"49":{"tf":1.0},"57":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"21":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"89":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":9,"docs":{"10":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"8":{"tf":1.0}},"n":{"df":2,"docs":{"21":{"tf":1.0},"76":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":5,"docs":{"110":{"tf":4.47213595499958},"111":{"tf":1.4142135623730951},"115":{"tf":1.0},"129":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"20":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"f":{")":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"63":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},".":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"53":{"tf":1.0}}}}},"df":1,"docs":{"11":{"tf":1.0}}},"b":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"53":{"tf":1.0},"56":{"tf":1.0}}}},"df":0,"docs":{}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"u":{"df":2,"docs":{"29":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"z":{"df":1,"docs":{"129":{"tf":1.0}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"114":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"129":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"i":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"29":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},":":{":":{"a":{"df":1,"docs":{"28":{"tf":1.0}}},"b":{"df":1,"docs":{"28":{"tf":1.0}}},"c":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"111":{"tf":1.0},"112":{"tf":1.4142135623730951}}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":2,"docs":{"111":{"tf":1.0},"112":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":16,"docs":{"129":{"tf":1.0},"14":{"tf":1.4142135623730951},"16":{"tf":1.0},"24":{"tf":1.0},"26":{"tf":1.0},"28":{"tf":1.0},"52":{"tf":2.0},"53":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"63":{"tf":1.0},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0}}}},"n":{"d":{"df":3,"docs":{"16":{"tf":1.0},"78":{"tf":1.0},"87":{"tf":1.4142135623730951}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}}},"t":{"df":1,"docs":{"14":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":5,"docs":{"121":{"tf":1.0},"130":{"tf":1.0},"23":{"tf":1.0},"34":{"tf":1.0},"43":{"tf":1.0}}}},"df":0,"docs":{}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"48":{"tf":1.0}}}}}}}},"r":{"d":{"df":2,"docs":{"108":{"tf":1.0},"26":{"tf":1.0}},"e":{":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"108":{"tf":1.0}}}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"108":{"tf":1.0}}}}}},"{":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"108":{"tf":1.0},"26":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{"&":{"df":0,"docs":{},"r":{"df":1,"docs":{"7":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"82":{"tf":1.0}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"113":{"tf":1.0},"117":{"tf":1.0},"8":{"tf":1.0},"85":{"tf":1.0},"93":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"80":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":2,"docs":{"114":{"tf":1.4142135623730951},"83":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}}}}}},"i":{"c":{"df":4,"docs":{"116":{"tf":1.0},"59":{"tf":1.0},"68":{"tf":1.0},"89":{"tf":1.0}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"57":{"tf":1.0}}}}}}},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"87":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"37":{"tf":1.0},"59":{"tf":1.0}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"1":{"tf":1.0},"37":{"tf":1.0},"75":{"tf":1.0},"77":{"tf":1.0},"93":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":2,"docs":{"29":{"tf":2.0},"31":{"tf":1.7320508075688772}}}},"r":{"df":0,"docs":{},"e":{"a":{"b":{"df":0,"docs":{},"l":{"df":4,"docs":{"122":{"tf":1.4142135623730951},"123":{"tf":1.7320508075688772},"124":{"tf":1.4142135623730951},"125":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":1,"docs":{"125":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"129":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"129":{"tf":1.7320508075688772},"130":{"tf":1.0}}}}}}}}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":2.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"29":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"w":{"df":4,"docs":{"14":{"tf":1.0},"16":{"tf":1.0},"21":{"tf":1.0},"57":{"tf":1.0}},"n":{"df":2,"docs":{"34":{"tf":1.0},"43":{"tf":1.0}}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"48":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"48":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":4,"docs":{"31":{"tf":1.4142135623730951},"33":{"tf":1.0},"47":{"tf":1.0},"93":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":7,"docs":{"110":{"tf":1.0},"115":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"90":{"tf":1.0},"92":{"tf":1.0},"93":{"tf":1.0}},"e":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":9,"docs":{"10":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"14":{"tf":1.0},"18":{"tf":1.0},"33":{"tf":1.4142135623730951},"43":{"tf":1.0},"52":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"115":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"i":{"df":2,"docs":{"108":{"tf":1.0},"11":{"tf":1.0}},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"111":{"tf":1.0}}}}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":2,"docs":{"116":{"tf":1.0},"120":{"tf":1.0}}}}},"z":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"129":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":5,"docs":{"129":{"tf":3.1622776601683795},"130":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.0},"87":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"113":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"49":{"tf":1.0}}}}}}}}},"n":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"_":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"111":{"tf":1.0}}}},"v":{"df":2,"docs":{"1":{"tf":1.0},"130":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"52":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"3":{"df":1,"docs":{"115":{"tf":1.4142135623730951}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}}}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"130":{"tf":1.0}}},"df":0,"docs":{}}}},"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"115":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"115":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}}},"t":{"df":1,"docs":{"34":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"43":{"tf":1.4142135623730951}}}}}}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"43":{"tf":1.4142135623730951}}}}}}}}},"t":{"df":1,"docs":{"43":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"<":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"34":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"34":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"34":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"34":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772}}}}}}}}}},"t":{"df":0,"docs":{},"h":{"df":4,"docs":{"125":{"tf":1.0},"126":{"tf":1.0},"129":{"tf":1.0},"18":{"tf":1.0}}},"i":{"df":0,"docs":{},"m":{"df":6,"docs":{"11":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"20":{"tf":1.0},"24":{"tf":1.0},"53":{"tf":1.0}}}},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"34":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772}}}}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"75":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"74":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"111":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"c":{"df":4,"docs":{"37":{"tf":1.0},"59":{"tf":1.0},"80":{"tf":1.4142135623730951},"88":{"tf":1.0}},"i":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"37":{"tf":1.0},"66":{"tf":1.7320508075688772},"72":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":3,"docs":{"0":{"tf":1.0},"58":{"tf":1.7320508075688772},"89":{"tf":1.0}},"i":{"df":4,"docs":{"34":{"tf":1.0},"42":{"tf":1.0},"49":{"tf":1.0},"57":{"tf":1.0}}}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}}}},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"111":{"tf":1.0}},"x":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"112":{"tf":1.0}}}}}}},"p":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"111":{"tf":1.0},"112":{"tf":1.0}}}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"a":{"df":0,"docs":{},"s":{"(":{"\\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"110":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"29":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}},"e":{"\\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"66":{"tf":1.4142135623730951},"80":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":1,"docs":{"27":{"tf":1.0}},"t":{".":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{")":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{")":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"n":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":4,"docs":{"117":{"tf":1.0},"58":{"tf":2.0},"76":{"tf":1.0},"82":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"16":{"tf":1.0}}},"i":{"c":{"df":1,"docs":{"87":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{":":{":":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"44":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"108":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"n":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"100":{"tf":1.0},"18":{"tf":1.0},"99":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"f":{"3":{"2":{":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{":":{":":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"y":{"df":4,"docs":{"21":{"tf":1.0},"61":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"21":{"tf":1.0},"62":{"tf":1.0},"66":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":1,"docs":{"21":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{":":{":":{"a":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"111":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{":":{":":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"49":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":10,"docs":{"28":{"tf":1.0},"49":{"tf":2.0},"67":{"tf":1.0},"75":{"tf":1.4142135623730951},"76":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"129":{"tf":1.0},"37":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"16":{"tf":1.0},"67":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":1,"docs":{"87":{"tf":1.4142135623730951}}}}}}},"df":11,"docs":{"100":{"tf":1.0},"114":{"tf":1.0},"19":{"tf":1.0},"30":{"tf":1.0},"44":{"tf":1.0},"6":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"80":{"tf":1.0},"99":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"m":{"<":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":4,"docs":{"100":{"tf":1.0},"21":{"tf":1.0},"23":{"tf":1.4142135623730951},"49":{"tf":1.0}}}}}}},"df":2,"docs":{"49":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"100":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"(":{"\\"":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"128":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"44":{"tf":1.0}}}},"df":0,"docs":{}},"df":31,"docs":{"107":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.4142135623730951},"114":{"tf":2.0},"115":{"tf":2.23606797749979},"117":{"tf":1.0},"119":{"tf":1.4142135623730951},"127":{"tf":1.4142135623730951},"128":{"tf":1.4142135623730951},"129":{"tf":1.4142135623730951},"13":{"tf":2.449489742783178},"131":{"tf":1.7320508075688772},"14":{"tf":1.7320508075688772},"16":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"21":{"tf":1.4142135623730951},"24":{"tf":1.7320508075688772},"29":{"tf":1.7320508075688772},"30":{"tf":2.0},"34":{"tf":2.23606797749979},"35":{"tf":1.4142135623730951},"36":{"tf":2.449489742783178},"37":{"tf":2.0},"43":{"tf":2.0},"44":{"tf":1.4142135623730951},"47":{"tf":1.0},"48":{"tf":2.0},"54":{"tf":2.0},"69":{"tf":1.4142135623730951},"87":{"tf":2.0}},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"b":{"df":1,"docs":{"107":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"i":{"6":{"4":{"df":1,"docs":{"107":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":87,"docs":{"10":{"tf":1.4142135623730951},"100":{"tf":1.4142135623730951},"103":{"tf":1.0},"104":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.4142135623730951},"112":{"tf":1.0},"114":{"tf":1.7320508075688772},"115":{"tf":1.0},"117":{"tf":1.0},"118":{"tf":1.0},"119":{"tf":1.4142135623730951},"12":{"tf":1.0},"120":{"tf":2.0},"121":{"tf":2.0},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.4142135623730951},"129":{"tf":1.7320508075688772},"13":{"tf":1.7320508075688772},"130":{"tf":1.4142135623730951},"131":{"tf":1.0},"14":{"tf":2.0},"16":{"tf":1.0},"17":{"tf":2.0},"18":{"tf":1.4142135623730951},"19":{"tf":1.0},"20":{"tf":1.7320508075688772},"21":{"tf":1.4142135623730951},"22":{"tf":2.23606797749979},"23":{"tf":2.0},"24":{"tf":1.7320508075688772},"26":{"tf":1.7320508075688772},"29":{"tf":1.4142135623730951},"30":{"tf":2.0},"31":{"tf":1.4142135623730951},"32":{"tf":2.449489742783178},"33":{"tf":1.7320508075688772},"34":{"tf":2.8284271247461903},"35":{"tf":1.0},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772},"39":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"43":{"tf":2.6457513110645907},"44":{"tf":1.0},"47":{"tf":1.4142135623730951},"48":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.0},"50":{"tf":1.0},"52":{"tf":1.7320508075688772},"53":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.4142135623730951},"57":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.4142135623730951},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"9":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"11":{"tf":1.0},"25":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}},"u":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":2,"docs":{"88":{"tf":1.4142135623730951},"89":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"u":{"b":{"df":1,"docs":{"8":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}}}}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":10,"docs":{"116":{"tf":1.0},"118":{"tf":1.0},"121":{"tf":2.23606797749979},"122":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":2.0},"127":{"tf":1.7320508075688772},"128":{"tf":1.0},"129":{"tf":2.0},"130":{"tf":1.0}}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"8":{"tf":1.0}}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"20":{"tf":1.4142135623730951},"72":{"tf":1.0}}}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"49":{"tf":1.0},"84":{"tf":1.7320508075688772}},"e":{"<":{"\'":{"df":1,"docs":{"84":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":14,"docs":{"100":{"tf":1.4142135623730951},"104":{"tf":1.0},"21":{"tf":1.7320508075688772},"22":{"tf":1.4142135623730951},"23":{"tf":2.23606797749979},"45":{"tf":1.4142135623730951},"49":{"tf":2.23606797749979},"5":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.0},"78":{"tf":1.0},"84":{"tf":1.0},"96":{"tf":1.0},"98":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"1":{"df":1,"docs":{"23":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"1":{"df":1,"docs":{"23":{"tf":1.4142135623730951}}},"2":{"df":1,"docs":{"23":{"tf":1.7320508075688772}}},":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"23":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"100":{"tf":1.7320508075688772}}}}}}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"19":{"tf":1.0}}}},"t":{"df":1,"docs":{"31":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"h":{"df":3,"docs":{"121":{"tf":1.0},"5":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":4,"docs":{"116":{"tf":1.0},"125":{"tf":1.0},"130":{"tf":1.0},"132":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"75":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":8,"docs":{"1":{"tf":1.0},"107":{"tf":1.0},"114":{"tf":1.0},"115":{"tf":1.0},"117":{"tf":1.7320508075688772},"32":{"tf":1.0},"37":{"tf":1.0},"93":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"129":{"tf":1.0},"22":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"\\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"115":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"115":{"tf":1.0}}}}},"df":0,"docs":{}},"n":{"c":{"df":3,"docs":{"16":{"tf":1.0},"28":{"tf":1.0},"87":{"tf":1.0}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"x":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":4,"docs":{"113":{"tf":1.4142135623730951},"115":{"tf":1.7320508075688772},"132":{"tf":2.0},"9":{"tf":1.0}}}}}}}},"t":{"a":{"df":0,"docs":{},"g":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"131":{"tf":1.0}}}},"df":0,"docs":{}}},"df":1,"docs":{"131":{"tf":1.7320508075688772}}},"k":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":4,"docs":{"109":{"tf":1.0},"11":{"tf":1.0},"126":{"tf":1.4142135623730951},"128":{"tf":1.0}},"n":{"df":1,"docs":{"57":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"k":{"df":1,"docs":{"111":{"tf":1.0}}}}},"df":2,"docs":{"25":{"tf":1.0},"44":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"131":{"tf":1.4142135623730951},"89":{"tf":1.0}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":2,"docs":{"28":{"tf":1.0},"62":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"80":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{"1":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"40":{"tf":1.0}}},"2":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"40":{"tf":1.0}}},"3":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"40":{"tf":1.0}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"115":{"tf":2.23606797749979}}}}},"df":0,"docs":{}}},":":{":":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"(":{"\\"":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"115":{"tf":1.0}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"115":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":1,"docs":{"26":{"tf":1.0}}}},"df":0,"docs":{}}}},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"29":{"tf":1.0},"37":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"76":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"63":{"tf":1.4142135623730951}}}}}}}}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":3,"docs":{"129":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":2,"docs":{"118":{"tf":1.0},"129":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":2,"docs":{"119":{"tf":1.0},"45":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":3,"docs":{"63":{"tf":1.0},"66":{"tf":1.0},"75":{"tf":1.0}}}}}}}},"i":{"c":{"df":0,"docs":{},"k":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"100":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"4":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":5,"docs":{"1":{"tf":1.0},"110":{"tf":1.0},"121":{"tf":1.0},"24":{"tf":1.0},"57":{"tf":1.0}}}},"p":{"df":1,"docs":{"22":{"tf":1.0}}},"t":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}},"o":{"_":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"107":{"tf":1.0},"24":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"110":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":18,"docs":{"110":{"tf":2.8284271247461903},"111":{"tf":1.4142135623730951},"112":{"tf":1.4142135623730951},"114":{"tf":1.0},"22":{"tf":2.0},"24":{"tf":1.7320508075688772},"30":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"47":{"tf":1.0},"48":{"tf":1.4142135623730951},"52":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"64":{"tf":1.0},"72":{"tf":1.4142135623730951}},"i":{"d":{"df":1,"docs":{"112":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"121":{"tf":1.0},"29":{"tf":1.0},"65":{"tf":1.0}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"18":{"tf":1.4142135623730951},"48":{"tf":1.0}}}},"i":{"df":0,"docs":{},"o":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"111":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"49":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"49":{"tf":1.0}}}}},"p":{"df":1,"docs":{"32":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"106":{"tf":1.0}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}}}}}}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"e":{"df":5,"docs":{"67":{"tf":1.7320508075688772},"88":{"tf":1.7320508075688772},"89":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.7320508075688772}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":11,"docs":{"114":{"tf":1.0},"25":{"tf":1.7320508075688772},"28":{"tf":1.4142135623730951},"42":{"tf":1.0},"44":{"tf":1.4142135623730951},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.4142135623730951},"75":{"tf":1.4142135623730951},"87":{"tf":1.4142135623730951}}}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"29":{"tf":1.0},"32":{"tf":1.0}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":2,"docs":{"30":{"tf":1.0},"45":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":1,"docs":{"76":{"tf":1.0}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"76":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"e":{"df":2,"docs":{"128":{"tf":1.0},"73":{"tf":1.4142135623730951}}}},"y":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"111":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"o":{"df":10,"docs":{"111":{"tf":1.0},"113":{"tf":1.0},"24":{"tf":1.0},"34":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"57":{"tf":1.0},"62":{"tf":1.0},"72":{"tf":1.0},"98":{"tf":1.0}}}},"y":{"df":3,"docs":{"29":{"tf":2.0},"30":{"tf":1.4142135623730951},"40":{"tf":2.449489742783178}},"p":{"df":0,"docs":{},"e":{"\\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"115":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"44":{"tf":1.0}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"115":{"tf":1.7320508075688772},"132":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}},"df":49,"docs":{"0":{"tf":1.0},"107":{"tf":1.7320508075688772},"108":{"tf":1.0},"111":{"tf":1.4142135623730951},"112":{"tf":2.449489742783178},"113":{"tf":2.23606797749979},"115":{"tf":2.23606797749979},"118":{"tf":1.7320508075688772},"119":{"tf":1.4142135623730951},"12":{"tf":1.0},"120":{"tf":1.7320508075688772},"121":{"tf":1.4142135623730951},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.4142135623730951},"125":{"tf":1.4142135623730951},"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.4142135623730951},"129":{"tf":1.7320508075688772},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":2.0},"14":{"tf":1.4142135623730951},"21":{"tf":1.7320508075688772},"22":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"25":{"tf":1.7320508075688772},"28":{"tf":2.6457513110645907},"29":{"tf":1.0},"30":{"tf":2.0},"32":{"tf":1.4142135623730951},"34":{"tf":2.6457513110645907},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":2.23606797749979},"44":{"tf":1.7320508075688772},"45":{"tf":1.7320508075688772},"5":{"tf":2.0},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0},"69":{"tf":1.4142135623730951},"72":{"tf":1.7320508075688772},"73":{"tf":1.4142135623730951},"9":{"tf":1.0},"99":{"tf":1.0}},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"42":{"tf":1.0},"44":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}}}},"u":{"3":{"2":{"df":3,"docs":{"115":{"tf":1.4142135623730951},"125":{"tf":1.7320508075688772},"129":{"tf":2.0}}},"df":0,"docs":{}},"6":{"4":{"df":5,"docs":{"110":{"tf":1.0},"111":{"tf":1.4142135623730951},"121":{"tf":2.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":2,"docs":{"26":{"tf":1.0},"50":{"tf":1.4142135623730951}},"n":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"54":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"20":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"31":{"tf":1.7320508075688772},"32":{"tf":2.8284271247461903}}}},"q":{"df":0,"docs":{},"u":{"df":4,"docs":{"22":{"tf":1.0},"23":{"tf":1.0},"42":{"tf":1.0},"87":{"tf":1.4142135623730951}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"11":{"tf":1.0},"32":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"73":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"121":{"tf":1.0}}}}}}}},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"129":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"p":{"d":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":3,"docs":{"118":{"tf":1.4142135623730951},"32":{"tf":1.0},"47":{"tf":1.0}}},"r":{"df":0,"docs":{},"l":{"df":2,"docs":{"132":{"tf":1.0},"54":{"tf":1.4142135623730951}}}},"s":{"df":97,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"10":{"tf":1.0},"100":{"tf":2.0},"103":{"tf":1.7320508075688772},"104":{"tf":1.7320508075688772},"107":{"tf":1.4142135623730951},"108":{"tf":2.23606797749979},"11":{"tf":1.4142135623730951},"111":{"tf":2.0},"112":{"tf":1.0},"113":{"tf":1.0},"114":{"tf":1.7320508075688772},"115":{"tf":2.0},"117":{"tf":1.4142135623730951},"118":{"tf":1.4142135623730951},"119":{"tf":2.23606797749979},"12":{"tf":1.7320508075688772},"120":{"tf":1.7320508075688772},"121":{"tf":1.7320508075688772},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.4142135623730951},"126":{"tf":1.7320508075688772},"127":{"tf":1.4142135623730951},"128":{"tf":1.4142135623730951},"129":{"tf":2.8284271247461903},"13":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.4142135623730951},"132":{"tf":1.4142135623730951},"14":{"tf":1.7320508075688772},"16":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":2.0},"19":{"tf":2.23606797749979},"20":{"tf":1.4142135623730951},"21":{"tf":1.7320508075688772},"22":{"tf":1.4142135623730951},"23":{"tf":1.7320508075688772},"24":{"tf":1.4142135623730951},"25":{"tf":1.0},"26":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"29":{"tf":1.7320508075688772},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"33":{"tf":2.0},"34":{"tf":2.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.7320508075688772},"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.7320508075688772},"44":{"tf":2.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":2.23606797749979},"5":{"tf":1.4142135623730951},"50":{"tf":1.4142135623730951},"52":{"tf":1.7320508075688772},"53":{"tf":1.7320508075688772},"54":{"tf":1.4142135623730951},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":2.23606797749979},"58":{"tf":1.4142135623730951},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.4142135623730951},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.4142135623730951},"73":{"tf":1.0},"75":{"tf":1.7320508075688772},"78":{"tf":2.0},"79":{"tf":1.7320508075688772},"80":{"tf":1.7320508075688772},"81":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951},"83":{"tf":1.4142135623730951},"84":{"tf":1.7320508075688772},"87":{"tf":1.4142135623730951},"9":{"tf":1.0},"93":{"tf":1.0},"95":{"tf":1.7320508075688772},"96":{"tf":1.7320508075688772},"98":{"tf":1.4142135623730951},"99":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"r":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"47":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"110":{"tf":1.0},"111":{"tf":1.0}}},"df":0,"docs":{}}},"b":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":18,"docs":{"11":{"tf":1.4142135623730951},"110":{"tf":5.0},"111":{"tf":2.0},"112":{"tf":1.4142135623730951},"119":{"tf":4.123105625617661},"120":{"tf":2.449489742783178},"121":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.4142135623730951},"22":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.7320508075688772},"47":{"tf":1.4142135623730951},"48":{"tf":1.7320508075688772},"57":{"tf":1.0},"61":{"tf":1.0},"73":{"tf":1.0},"78":{"tf":1.4142135623730951}},"i":{"d":{"df":1,"docs":{"112":{"tf":1.0}}},"df":0,"docs":{}},"n":{"a":{"df":0,"docs":{},"m":{"df":4,"docs":{"119":{"tf":2.449489742783178},"35":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"111":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}}}},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"_":{"a":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"33":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"z":{"df":2,"docs":{"44":{"tf":1.0},"72":{"tf":1.4142135623730951}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":4,"docs":{"127":{"tf":1.0},"22":{"tf":1.0},"49":{"tf":1.0},"80":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"51":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"i":{"d":{"=":{"df":0,"docs":{},"v":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":1,"docs":{"54":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"v":{"2":{"df":1,"docs":{"117":{"tf":1.4142135623730951}}},"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"81":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":8,"docs":{"121":{"tf":1.0},"54":{"tf":2.449489742783178},"55":{"tf":1.0},"56":{"tf":1.0},"72":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":2.23606797749979},"89":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":24,"docs":{"0":{"tf":1.0},"10":{"tf":1.4142135623730951},"107":{"tf":2.0},"108":{"tf":1.0},"11":{"tf":1.0},"111":{"tf":1.0},"112":{"tf":1.4142135623730951},"114":{"tf":2.449489742783178},"119":{"tf":1.0},"129":{"tf":1.4142135623730951},"14":{"tf":1.4142135623730951},"16":{"tf":1.0},"24":{"tf":2.0},"38":{"tf":1.7320508075688772},"39":{"tf":3.1622776601683795},"40":{"tf":2.449489742783178},"49":{"tf":1.7320508075688772},"53":{"tf":2.23606797749979},"54":{"tf":1.0},"56":{"tf":2.0},"57":{"tf":1.0},"59":{"tf":1.0},"72":{"tf":2.0},"73":{"tf":1.4142135623730951}},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"14":{"tf":1.0},"72":{"tf":1.0}}}}}}},"df":0,"docs":{}},".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"114":{"tf":1.0}}}}},"df":0,"docs":{}},"x":{"df":1,"docs":{"26":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"1":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"57":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":4,"docs":{"26":{"tf":1.0},"41":{"tf":1.0},"52":{"tf":1.0},"57":{"tf":1.4142135623730951}}},"2":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"57":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":4,"docs":{"26":{"tf":1.0},"41":{"tf":1.0},"52":{"tf":1.0},"57":{"tf":1.0}}},"3":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"57":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"41":{"tf":1.0},"57":{"tf":1.0}}},":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"0":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"c":{"2":{"8":{"2":{"2":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{"3":{"3":{"9":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"107":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"114":{"tf":1.4142135623730951}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"107":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"_":{"d":{"b":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}},"e":{"d":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"26":{"tf":1.0}}}}}},"2":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"26":{"tf":1.0}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{".":{"0":{"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"72":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":3,"docs":{"16":{"tf":1.0},"72":{"tf":1.0},"80":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"s":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"80":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}},"df":2,"docs":{"50":{"tf":1.4142135623730951},"54":{"tf":1.4142135623730951}},"e":{"c":{"!":{"[":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"128":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"<":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"128":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"81":{"tf":1.0}}}}}}}}}}}},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"55":{"tf":1.0}}}}},"t":{"df":3,"docs":{"25":{"tf":1.4142135623730951},"44":{"tf":1.0},"5":{"tf":1.0}}},"u":{"df":0,"docs":{},"s":{"df":3,"docs":{"22":{"tf":1.4142135623730951},"33":{"tf":1.0},"37":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"25":{"tf":1.0}}},"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":1,"docs":{"64":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":2,"docs":{"58":{"tf":1.0},"69":{"tf":1.0}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":8,"docs":{"100":{"tf":1.0},"114":{"tf":1.0},"30":{"tf":1.0},"6":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"99":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"17":{"tf":1.0},"54":{"tf":1.4142135623730951}}}}}}}},"i":{"a":{"df":2,"docs":{"119":{"tf":1.0},"28":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"73":{"tf":2.449489742783178}}}},"df":0,"docs":{}}}}},"w":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":14,"docs":{"11":{"tf":1.0},"111":{"tf":1.0},"121":{"tf":1.4142135623730951},"129":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951},"32":{"tf":1.0},"34":{"tf":1.4142135623730951},"37":{"tf":1.0},"63":{"tf":1.0},"73":{"tf":1.0},"76":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0}}}},"r":{"df":1,"docs":{"27":{"tf":1.0}},"p":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":2,"docs":{"100":{"tf":1.0},"99":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"98":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{")":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"(":{"(":{"[":{"0":{"df":2,"docs":{"100":{"tf":1.0},"99":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"{":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":6,"docs":{"100":{"tf":1.0},"18":{"tf":1.4142135623730951},"4":{"tf":1.4142135623730951},"93":{"tf":1.4142135623730951},"98":{"tf":1.4142135623730951},"99":{"tf":1.0}}}},"s":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"19":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"y":{"df":8,"docs":{"21":{"tf":1.0},"42":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"72":{"tf":1.0},"74":{"tf":1.0}}}},"df":0,"docs":{},"e":{"\'":{"df":0,"docs":{},"r":{"df":1,"docs":{"129":{"tf":1.0}}}},"b":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"<":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"<":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{")":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"103":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"<":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"103":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"p":{"a":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":6,"docs":{"102":{"tf":1.0},"105":{"tf":1.0},"4":{"tf":1.4142135623730951},"76":{"tf":1.0},"8":{"tf":1.0},"93":{"tf":1.7320508075688772}},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"98":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"129":{"tf":2.449489742783178},"130":{"tf":1.0}},"s":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"129":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"129":{"tf":2.23606797749979}}},"df":0,"docs":{},"t":{"df":1,"docs":{"129":{"tf":1.0}}}}}}}}}}}}},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"21":{"tf":1.0},"28":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":1,"docs":{"16":{"tf":1.0}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"82":{"tf":1.4142135623730951}}}}}},"i":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"35":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"113":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"132":{"tf":1.0},"6":{"tf":1.0},"74":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"\'":{"df":0,"docs":{},"t":{"df":2,"docs":{"25":{"tf":1.0},"76":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"d":{"df":1,"docs":{"76":{"tf":1.0}}},"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":5,"docs":{"11":{"tf":1.0},"22":{"tf":1.0},"66":{"tf":1.0},"88":{"tf":1.0},"89":{"tf":1.0}}},"l":{"d":{"df":1,"docs":{"19":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"130":{"tf":1.0},"72":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"n":{"\'":{"df":0,"docs":{},"t":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"28":{"tf":1.0}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"25":{"tf":1.7320508075688772},"29":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":4,"docs":{"11":{"tf":1.4142135623730951},"131":{"tf":1.0},"33":{"tf":1.0},"5":{"tf":1.0}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"129":{"tf":1.0}}}}}}},"x":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"26":{"tf":1.0}}}}}}},"df":3,"docs":{"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"125":{"tf":1.4142135623730951}}},"y":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"123":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":3,"docs":{"123":{"tf":1.0},"124":{"tf":1.4142135623730951},"125":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"43":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"\'":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":4,"docs":{"75":{"tf":1.0},"76":{"tf":1.0},"77":{"tf":1.0},"87":{"tf":1.0}}}},"r":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":0,"docs":{},"r":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"18":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"63":{"tf":1.0},"93":{"tf":1.0}}}}}}}}}},"z":{"df":1,"docs":{"125":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"breadcrumbs":{"root":{"0":{"df":6,"docs":{"100":{"tf":1.7320508075688772},"123":{"tf":1.0},"39":{"tf":1.0},"49":{"tf":1.0},"58":{"tf":1.0},"99":{"tf":1.7320508075688772}}},"1":{"\\"":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"128":{"tf":1.0}}}}}}},"df":0,"docs":{}},")":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"0":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},".":{"0":{"df":8,"docs":{"100":{"tf":1.0},"114":{"tf":1.0},"30":{"tf":1.0},"6":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"99":{"tf":1.0}}},"df":0,"docs":{}},"0":{"0":{"0":{"0":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}},"df":2,"docs":{"56":{"tf":1.4142135623730951},"69":{"tf":2.0}}},"df":7,"docs":{"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"41":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.0},"6":{"tf":1.0},"69":{"tf":1.4142135623730951}}},"2":{"3":{"4":{"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"7":{"7":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"8":{"0":{"df":1,"docs":{"27":{"tf":1.0}}},"3":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":6,"docs":{"110":{"tf":4.242640687119285},"111":{"tf":1.0},"115":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951},"57":{"tf":1.0},"71":{"tf":1.0}}},"2":{"\\"":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"128":{"tf":1.0}}}}}}},"df":0,"docs":{}},"0":{"df":1,"docs":{"6":{"tf":1.0}}},"3":{"4":{"a":{"\\"":{".":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"65":{"tf":1.0}},"e":{"(":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":1,"docs":{"66":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":4,"docs":{"111":{"tf":1.0},"115":{"tf":1.4142135623730951},"22":{"tf":1.0},"57":{"tf":1.0}}},"3":{"0":{"df":2,"docs":{"39":{"tf":1.4142135623730951},"57":{"tf":1.0}}},"df":4,"docs":{"111":{"tf":1.0},"115":{"tf":1.4142135623730951},"57":{"tf":1.0},"70":{"tf":1.0}}},"4":{".":{"0":{"df":1,"docs":{"4":{"tf":2.0}}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"21":{"tf":1.0}}},"4":{"df":4,"docs":{"62":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"111":{"tf":1.0}}},"5":{"0":{"0":{"df":1,"docs":{"65":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":6,"docs":{"40":{"tf":1.0},"41":{"tf":1.0},"54":{"tf":1.4142135623730951},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.4142135623730951}}},"6":{"0":{"df":1,"docs":{"57":{"tf":1.0}}},"4":{"df":1,"docs":{"107":{"tf":1.0}}},"df":1,"docs":{"71":{"tf":1.0}}},"8":{"0":{"0":{"0":{")":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"100":{"tf":1.0},"99":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":2,"docs":{"119":{"tf":1.0},"120":{"tf":1.0}}}}},"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":2,"docs":{"114":{"tf":1.4142135623730951},"53":{"tf":1.0}}}}},"df":2,"docs":{"114":{"tf":1.0},"49":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"119":{"tf":1.0},"120":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"119":{"tf":1.0},"120":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":2,"docs":{"119":{"tf":1.4142135623730951},"120":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}}},"a":{"(":{"a":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"81":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"v":{"df":5,"docs":{"32":{"tf":1.0},"34":{"tf":1.0},"43":{"tf":1.0},"64":{"tf":1.0},"93":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"54":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"103":{"tf":1.0},"14":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"21":{"tf":1.0}}}}}}}},"r":{"d":{"df":1,"docs":{"73":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"131":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"15":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"b":{":":{":":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":1,"docs":{"103":{"tf":1.0}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"104":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}},"{":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"b":{"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.0}}},"df":0,"docs":{}}}},"df":6,"docs":{"102":{"tf":1.7320508075688772},"103":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"4":{"tf":1.4142135623730951},"93":{"tf":1.4142135623730951}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":5,"docs":{"15":{"tf":1.0},"53":{"tf":1.7320508075688772},"56":{"tf":1.0},"61":{"tf":1.0},"66":{"tf":1.0}}}},"df":0,"docs":{}}}},"d":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"5":{"tf":1.0},"50":{"tf":1.0}}}}}}},"a":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}},"df":16,"docs":{"107":{"tf":1.0},"108":{"tf":1.0},"121":{"tf":1.4142135623730951},"126":{"tf":1.0},"129":{"tf":1.0},"131":{"tf":1.4142135623730951},"132":{"tf":1.7320508075688772},"33":{"tf":1.4142135623730951},"36":{"tf":1.0},"4":{"tf":1.4142135623730951},"59":{"tf":1.0},"67":{"tf":1.0},"73":{"tf":1.0},"75":{"tf":1.0},"90":{"tf":1.0},"92":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":5,"docs":{"113":{"tf":1.0},"132":{"tf":1.0},"28":{"tf":1.0},"59":{"tf":1.7320508075688772},"61":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"(":{"a":{"d":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"37":{"tf":1.0},"54":{"tf":1.0}}}}}}},"df":4,"docs":{"11":{"tf":1.0},"125":{"tf":1.0},"13":{"tf":1.0},"74":{"tf":1.0}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"11":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"52":{"tf":2.0}}}}},"v":{"a":{"df":0,"docs":{},"n":{"c":{"df":27,"docs":{"106":{"tf":1.7320508075688772},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.0},"112":{"tf":1.0},"113":{"tf":1.0},"114":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"117":{"tf":1.0},"118":{"tf":1.0},"119":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"|":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"g":{"df":1,"docs":{"57":{"tf":1.0}}},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":14,"docs":{"113":{"tf":1.0},"118":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.0},"31":{"tf":1.0},"52":{"tf":1.4142135623730951},"57":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"75":{"tf":1.0},"89":{"tf":1.0},"90":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"111":{"tf":1.0},"23":{"tf":1.0}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"i":{"df":3,"docs":{"24":{"tf":1.0},"37":{"tf":1.0},"74":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"w":{"a":{"df":0,"docs":{},"y":{"df":3,"docs":{"129":{"tf":1.0},"49":{"tf":1.0},"73":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":1,"docs":{"67":{"tf":1.4142135623730951}}}},"t":{"df":1,"docs":{"88":{"tf":1.0}}},"z":{"df":1,"docs":{"86":{"tf":1.7320508075688772}}}}}},"d":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{":":{":":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"(":{"\\"":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"\\"":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":6,"docs":{"126":{"tf":1.4142135623730951},"127":{"tf":1.0},"34":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":1.0},"48":{"tf":1.0}}}}},"y":{"(":{"$":{"1":{"df":1,"docs":{"111":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"68":{"tf":1.0}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":22,"docs":{"115":{"tf":1.0},"116":{"tf":2.0},"117":{"tf":1.4142135623730951},"118":{"tf":1.0},"119":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.4142135623730951},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0},"67":{"tf":2.23606797749979},"87":{"tf":1.7320508075688772},"88":{"tf":2.6457513110645907},"89":{"tf":2.23606797749979}},"t":{"df":0,"docs":{},"r":{"a":{"c":{"df":1,"docs":{"67":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"p":{"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"118":{"tf":1.0},"128":{"tf":1.0}}}},"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"c":{"df":3,"docs":{"17":{"tf":1.0},"75":{"tf":1.0},"91":{"tf":1.0}}},"df":3,"docs":{"115":{"tf":1.0},"122":{"tf":1.0},"32":{"tf":1.0}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.0}}}}}}}}},"r":{"c":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"x":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"111":{"tf":1.0},"112":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"116":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"a":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"29":{"tf":1.0}}},"df":0,"docs":{}},"g":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"29":{"tf":1.0},"40":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"df":1,"docs":{"24":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"129":{"tf":1.0},"14":{"tf":1.4142135623730951},"16":{"tf":1.0},"29":{"tf":1.0},"33":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"89":{"tf":1.0}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"i":{"df":2,"docs":{"75":{"tf":1.0},"87":{"tf":1.0}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"n":{"c":{"/":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"111":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{">":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":7,"docs":{"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"c":{"df":1,"docs":{"67":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"49":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"49":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"80":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"18":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}}},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"98":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"_":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"b":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"{":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"95":{"tf":1.0}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"96":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{")":{".":{"a":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"|":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"99":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"18":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"100":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":3,"docs":{"100":{"tf":1.0},"18":{"tf":1.0},"99":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":88,"docs":{"10":{"tf":1.4142135623730951},"100":{"tf":1.4142135623730951},"103":{"tf":1.4142135623730951},"104":{"tf":1.4142135623730951},"107":{"tf":1.4142135623730951},"108":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"111":{"tf":1.0},"112":{"tf":1.4142135623730951},"114":{"tf":2.0},"115":{"tf":2.0},"117":{"tf":1.4142135623730951},"119":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"120":{"tf":1.4142135623730951},"121":{"tf":2.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"125":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"127":{"tf":1.4142135623730951},"128":{"tf":1.4142135623730951},"129":{"tf":2.0},"13":{"tf":1.4142135623730951},"130":{"tf":1.4142135623730951},"131":{"tf":1.4142135623730951},"132":{"tf":1.4142135623730951},"14":{"tf":1.4142135623730951},"16":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"21":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"26":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"28":{"tf":2.23606797749979},"29":{"tf":1.4142135623730951},"30":{"tf":2.0},"31":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"34":{"tf":2.449489742783178},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"41":{"tf":1.4142135623730951},"43":{"tf":2.0},"44":{"tf":1.4142135623730951},"47":{"tf":1.4142135623730951},"48":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"50":{"tf":1.4142135623730951},"52":{"tf":2.0},"53":{"tf":1.4142135623730951},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"6":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"63":{"tf":2.0},"64":{"tf":1.4142135623730951},"65":{"tf":1.4142135623730951},"67":{"tf":1.4142135623730951},"70":{"tf":1.4142135623730951},"71":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"78":{"tf":2.0},"79":{"tf":1.7320508075688772},"80":{"tf":1.4142135623730951},"81":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951},"83":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951},"87":{"tf":1.4142135623730951},"95":{"tf":1.4142135623730951},"96":{"tf":1.4142135623730951},"99":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"d":{":":{":":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"111":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{":":{":":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":9,"docs":{"114":{"tf":1.4142135623730951},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"87":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":83,"docs":{"0":{"tf":1.4142135623730951},"100":{"tf":1.7320508075688772},"103":{"tf":1.0},"104":{"tf":1.0},"107":{"tf":1.4142135623730951},"11":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.4142135623730951},"112":{"tf":1.4142135623730951},"113":{"tf":1.0},"114":{"tf":2.0},"117":{"tf":1.4142135623730951},"119":{"tf":1.7320508075688772},"120":{"tf":1.0},"123":{"tf":1.0},"128":{"tf":1.7320508075688772},"129":{"tf":1.4142135623730951},"130":{"tf":1.0},"14":{"tf":1.4142135623730951},"16":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":2.0},"23":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"27":{"tf":1.0},"29":{"tf":2.8284271247461903},"30":{"tf":1.7320508075688772},"31":{"tf":2.449489742783178},"33":{"tf":1.0},"37":{"tf":1.4142135623730951},"39":{"tf":1.7320508075688772},"4":{"tf":2.0},"40":{"tf":1.7320508075688772},"45":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.4142135623730951},"49":{"tf":1.0},"5":{"tf":1.4142135623730951},"50":{"tf":1.0},"52":{"tf":1.4142135623730951},"53":{"tf":1.4142135623730951},"54":{"tf":1.4142135623730951},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":2.0},"58":{"tf":1.7320508075688772},"6":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.4142135623730951},"67":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.4142135623730951},"74":{"tf":1.0},"75":{"tf":1.4142135623730951},"76":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.4142135623730951},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"85":{"tf":1.0},"87":{"tf":1.4142135623730951},"88":{"tf":1.0},"89":{"tf":1.7320508075688772},"9":{"tf":1.0},"90":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0},"93":{"tf":2.449489742783178},"98":{"tf":1.0},"99":{"tf":1.7320508075688772}},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"(":{"\\"":{"/":{"df":0,"docs":{},"w":{"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"a":{"c":{"df":0,"docs":{},"h":{"df":3,"docs":{"15":{"tf":1.0},"21":{"tf":1.0},"63":{"tf":1.0}}},"k":{"df":1,"docs":{"68":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":12,"docs":{"11":{"tf":1.0},"118":{"tf":1.0},"12":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.0},"132":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"33":{"tf":1.0},"52":{"tf":1.0},"55":{"tf":1.0},"73":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"18":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}}}},"o":{"df":1,"docs":{"29":{"tf":1.0}},"m":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"117":{"tf":1.0},"27":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"66":{"tf":1.0}}}}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":10,"docs":{"111":{"tf":1.0},"2":{"tf":1.0},"85":{"tf":2.0},"86":{"tf":1.4142135623730951},"87":{"tf":1.4142135623730951},"88":{"tf":1.4142135623730951},"89":{"tf":1.7320508075688772},"90":{"tf":1.4142135623730951},"91":{"tf":1.4142135623730951},"92":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"73":{"tf":1.0}}},"df":0,"docs":{}}}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"110":{"tf":1.0},"18":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0}}}}},"df":0,"docs":{}},"x":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"93":{"tf":1.4142135623730951}}}}}},"b":{"(":{"b":{"df":1,"docs":{"32":{"tf":1.0}}},"df":0,"docs":{}},"a":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"2":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"g":{"<":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"44":{"tf":1.0}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"44":{"tf":1.0}}}}},"t":{"df":1,"docs":{"44":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"e":{"df":4,"docs":{"5":{"tf":1.0},"63":{"tf":1.0},"66":{"tf":1.0},"77":{"tf":1.0}}},"i":{"c":{"df":1,"docs":{"45":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"111":{"tf":1.0},"118":{"tf":1.0}}}},"df":0,"docs":{}}},"df":17,"docs":{"10":{"tf":1.7320508075688772},"108":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"120":{"tf":2.6457513110645907},"13":{"tf":1.4142135623730951},"20":{"tf":1.0},"28":{"tf":2.0},"32":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.4142135623730951},"43":{"tf":1.0},"5":{"tf":2.0},"6":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"73":{"tf":1.4142135623730951}},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":3,"docs":{"62":{"tf":1.0},"64":{"tf":1.0},"76":{"tf":1.0}}}}},"df":3,"docs":{"129":{"tf":1.0},"15":{"tf":1.0},"78":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":7,"docs":{"52":{"tf":1.0},"58":{"tf":1.7320508075688772},"76":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"1":{"0":{"0":{"0":{"0":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"78":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"v":{"df":1,"docs":{"113":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":4,"docs":{"113":{"tf":1.0},"132":{"tf":1.0},"75":{"tf":1.0},"84":{"tf":1.0}}}}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"110":{"tf":1.0}}}},"w":{"df":5,"docs":{"10":{"tf":1.0},"22":{"tf":1.0},"38":{"tf":1.0},"66":{"tf":1.0},"71":{"tf":1.0}}}}},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"2":{"tf":1.0}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"/":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"/":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"2":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":1,"docs":{"2":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":3,"docs":{"119":{"tf":1.0},"25":{"tf":1.0},"37":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"d":{"(":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"111":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"110":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":1,"docs":{"8":{"tf":1.0}}},"t":{"df":3,"docs":{"107":{"tf":1.0},"132":{"tf":1.0},"31":{"tf":1.0}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"23":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"g":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"21":{"tf":1.0}}},"l":{"df":5,"docs":{"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"44":{"tf":1.0},"73":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"n":{"b":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"44":{"tf":1.0}}}},"df":0,"docs":{}},"df":4,"docs":{"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"44":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"<":{"\'":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":1,"docs":{"16":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":1,"docs":{"16":{"tf":1.4142135623730951}}}}}},"t":{"df":0,"docs":{},"h":{"df":4,"docs":{"113":{"tf":1.0},"115":{"tf":1.0},"12":{"tf":1.4142135623730951},"29":{"tf":1.0}}}},"x":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"<":{"\'":{"df":1,"docs":{"84":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"32":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"114":{"tf":1.0},"89":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":3,"docs":{"107":{"tf":1.0},"113":{"tf":1.0},"54":{"tf":1.0}}}}}}},"c":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"11":{"tf":1.0}}}}}}},"c":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"a":{"c":{"df":0,"docs":{},"h":{"df":3,"docs":{"111":{"tf":1.0},"57":{"tf":3.872983346207417},"87":{"tf":1.4142135623730951}},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{":":{":":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"57":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"87":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":2,"docs":{"11":{"tf":1.7320508075688772},"72":{"tf":2.0}}}}},"df":0,"docs":{},"l":{"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"76":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":24,"docs":{"110":{"tf":1.4142135623730951},"130":{"tf":1.0},"16":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.0},"33":{"tf":1.0},"39":{"tf":1.0},"5":{"tf":1.0},"52":{"tf":1.0},"57":{"tf":1.4142135623730951},"58":{"tf":1.0},"6":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"73":{"tf":1.4142135623730951},"76":{"tf":2.0},"78":{"tf":1.4142135623730951},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.4142135623730951},"83":{"tf":1.0},"84":{"tf":1.0},"98":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"n":{"_":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"p":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"74":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"91":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"75":{"tf":1.0}}},"g":{"df":0,"docs":{},"o":{"df":2,"docs":{"2":{"tf":1.0},"8":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":2,"docs":{"11":{"tf":1.0},"130":{"tf":1.0}}},"t":{"df":2,"docs":{"61":{"tf":1.0},"62":{"tf":1.0}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"69":{"tf":1.0}}}}},"d":{"df":2,"docs":{"2":{"tf":1.0},"8":{"tf":1.0}}},"df":9,"docs":{"10":{"tf":1.4142135623730951},"13":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"28":{"tf":2.0},"32":{"tf":2.0},"36":{"tf":1.4142135623730951},"70":{"tf":1.0},"71":{"tf":1.0},"73":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"121":{"tf":1.0}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"65":{"tf":1.7320508075688772}}}},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"17":{"tf":1.0},"27":{"tf":1.0}}}},"r":{"df":1,"docs":{"54":{"tf":1.4142135623730951}},"s":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"x":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":3,"docs":{"52":{"tf":1.4142135623730951},"53":{"tf":1.0},"56":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":8,"docs":{"116":{"tf":1.0},"121":{"tf":1.0},"55":{"tf":1.7320508075688772},"60":{"tf":1.0},"75":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"88":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"l":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"72":{"tf":1.7320508075688772}}}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"13":{"tf":1.0},"36":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.4142135623730951}}}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"s":{"df":2,"docs":{"37":{"tf":1.0},"87":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"24":{"tf":1.0}}}}}}},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":2,"docs":{"29":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}},"e":{"\\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"(":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":4,"docs":{"113":{"tf":1.0},"68":{"tf":1.0},"81":{"tf":1.0},"87":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":7,"docs":{"21":{"tf":1.0},"26":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"52":{"tf":1.4142135623730951},"73":{"tf":1.0},"87":{"tf":1.0}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.0}}}}},"u":{"d":{"df":1,"docs":{"89":{"tf":1.0}}},"df":0,"docs":{}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":18,"docs":{"0":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.0},"129":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.0},"22":{"tf":1.0},"25":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"72":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.4142135623730951},"76":{"tf":1.0},"78":{"tf":1.7320508075688772},"79":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":5,"docs":{"116":{"tf":1.0},"127":{"tf":1.4142135623730951},"45":{"tf":1.0},"57":{"tf":1.0},"98":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":1,"docs":{"75":{"tf":1.0}}},"m":{"a":{"df":2,"docs":{"34":{"tf":1.0},"43":{"tf":1.0}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":6,"docs":{"107":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"5":{"tf":1.0},"54":{"tf":1.0},"93":{"tf":1.0}}}}},"p":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":5,"docs":{"22":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":4,"docs":{"116":{"tf":1.0},"48":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0}}},"x":{"df":8,"docs":{"11":{"tf":1.0},"121":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":3.0},"72":{"tf":3.0},"86":{"tf":1.0}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":5,"docs":{"11":{"tf":2.23606797749979},"111":{"tf":1.0},"123":{"tf":1.0},"129":{"tf":1.0},"52":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"s":{"df":1,"docs":{"132":{"tf":2.0}},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"115":{"tf":1.0},"132":{"tf":2.0}},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"132":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"120":{"tf":1.4142135623730951},"121":{"tf":1.0}}},"df":0,"docs":{}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"t":{"(":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"114":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"114":{"tf":2.449489742783178}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.4142135623730951},"42":{"tf":1.7320508075688772},"43":{"tf":1.4142135623730951}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":3,"docs":{"110":{"tf":1.0},"47":{"tf":1.0},"82":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"49":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"129":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"130":{"tf":1.0}}}}},"j":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{}}}},"n":{"df":1,"docs":{"14":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"14":{"tf":1.4142135623730951},"16":{"tf":1.0},"17":{"tf":1.0},"58":{"tf":2.6457513110645907}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"58":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"35":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"120":{"tf":1.0},"45":{"tf":1.0},"58":{"tf":1.0}}}}},"t":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"19":{"tf":1.0}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"30":{"tf":1.0},"62":{"tf":1.0},"75":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"129":{"tf":1.0}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"130":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.0},"86":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":3,"docs":{"44":{"tf":1.7320508075688772},"59":{"tf":1.0},"73":{"tf":2.0}}}},"x":{"df":0,"docs":{},"t":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"<":{"\'":{"_":{"df":9,"docs":{"110":{"tf":1.0},"111":{"tf":1.0},"114":{"tf":1.4142135623730951},"14":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"52":{"tf":1.4142135623730951},"53":{"tf":1.0},"73":{"tf":1.0}}},"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":1,"docs":{"16":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":7,"docs":{"14":{"tf":1.7320508075688772},"15":{"tf":2.0},"16":{"tf":2.0},"17":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"20":{"tf":1.0}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"131":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"118":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"57":{"tf":3.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.0}}},"t":{"df":1,"docs":{"27":{"tf":1.0}}}},"r":{"df":0,"docs":{},"s":{"df":3,"docs":{"28":{"tf":1.0},"29":{"tf":1.0},"9":{"tf":1.0}}},"t":{"df":2,"docs":{"21":{"tf":1.0},"62":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"33":{"tf":2.0}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"i":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"52":{"tf":1.4142135623730951},"73":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"118":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"5":{"tf":1.0},"57":{"tf":1.4142135623730951},"87":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"54":{"tf":1.4142135623730951},"72":{"tf":2.0}}}}},"w":{"<":{"\'":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"44":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"u":{"df":1,"docs":{"2":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":89,"docs":{"10":{"tf":1.0},"100":{"tf":1.7320508075688772},"103":{"tf":1.7320508075688772},"104":{"tf":1.7320508075688772},"107":{"tf":1.0},"108":{"tf":1.4142135623730951},"11":{"tf":1.0},"112":{"tf":1.0},"114":{"tf":1.4142135623730951},"115":{"tf":1.4142135623730951},"117":{"tf":1.0},"119":{"tf":1.0},"12":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.4142135623730951},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.4142135623730951},"13":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0},"14":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.7320508075688772},"19":{"tf":1.4142135623730951},"20":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.4142135623730951},"25":{"tf":1.0},"26":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.7320508075688772},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.7320508075688772},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"43":{"tf":1.4142135623730951},"44":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.7320508075688772},"5":{"tf":1.0},"50":{"tf":1.0},"52":{"tf":1.4142135623730951},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":2.0},"64":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"87":{"tf":1.0},"91":{"tf":1.0},"95":{"tf":1.7320508075688772},"96":{"tf":1.7320508075688772},"99":{"tf":1.7320508075688772}},"s":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":1,"docs":{"89":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":22,"docs":{"107":{"tf":1.0},"114":{"tf":1.0},"115":{"tf":1.0},"118":{"tf":1.4142135623730951},"121":{"tf":1.7320508075688772},"14":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.4142135623730951},"24":{"tf":1.0},"28":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"53":{"tf":1.0},"6":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"77":{"tf":1.0},"80":{"tf":1.0},"90":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"118":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"x":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{"\\"":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{":":{":":{"<":{"d":{"b":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{">":{"(":{")":{"?":{".":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"14":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"16":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"16":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{":":{":":{"<":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"52":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"d":{":":{":":{"<":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"111":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"a":{"d":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{">":{"(":{")":{".":{"0":{"df":1,"docs":{"73":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"<":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"df":1,"docs":{"110":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{"\\"":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"_":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"_":{"a":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"(":{")":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"\\"":{"a":{"\\"":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"20":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"\\"":{")":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"\\"":{"c":{"\\"":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"20":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":15,"docs":{"110":{"tf":1.0},"111":{"tf":1.4142135623730951},"14":{"tf":1.0},"16":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"52":{"tf":1.4142135623730951},"76":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"114":{"tf":1.0},"115":{"tf":1.0},"126":{"tf":1.0},"75":{"tf":1.0},"93":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":2.449489742783178}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"114":{"tf":2.23606797749979}}}},"df":0,"docs":{}}}}},"df":11,"docs":{"107":{"tf":1.7320508075688772},"108":{"tf":1.0},"113":{"tf":2.0},"114":{"tf":1.7320508075688772},"115":{"tf":1.4142135623730951},"132":{"tf":2.449489742783178},"21":{"tf":1.0},"56":{"tf":1.4142135623730951},"63":{"tf":1.7320508075688772},"72":{"tf":1.7320508075688772},"75":{"tf":1.0}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"<":{"df":0,"docs":{},"i":{"3":{"2":{"df":1,"docs":{"56":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"d":{"(":{"d":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"t":{"a":{"(":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"111":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"b":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"17":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"s":{"3":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"18":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"b":{"a":{"df":0,"docs":{},"s":{"df":5,"docs":{"109":{"tf":1.0},"112":{"tf":1.4142135623730951},"14":{"tf":1.7320508075688772},"33":{"tf":1.0},"47":{"tf":1.0}}}},"df":0,"docs":{}},"df":18,"docs":{"103":{"tf":1.0},"109":{"tf":1.0},"112":{"tf":1.7320508075688772},"121":{"tf":1.4142135623730951},"14":{"tf":2.0},"15":{"tf":1.4142135623730951},"16":{"tf":2.449489742783178},"17":{"tf":2.0},"18":{"tf":2.23606797749979},"20":{"tf":1.0},"35":{"tf":1.4142135623730951},"49":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"68":{"tf":1.0},"75":{"tf":1.0},"81":{"tf":1.0},"88":{"tf":1.0}},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":2,"docs":{"111":{"tf":2.0},"118":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"c":{"2":{"8":{"2":{"2":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"24":{"tf":1.0}}}}}}},"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"24":{"tf":1.0}}}}},"df":0,"docs":{}},"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{"3":{"3":{"9":{"(":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"24":{"tf":1.0}}}}},"df":0,"docs":{}},"df":1,"docs":{"24":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"c":{"2":{"8":{"2":{"2":{"(":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"24":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{"3":{"3":{"9":{"(":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{".":{"0":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":1,"docs":{"24":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"b":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"17":{"tf":1.0}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":1,"docs":{"14":{"tf":2.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"16":{"tf":1.0}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}}}}}},"df":3,"docs":{"20":{"tf":1.0},"32":{"tf":2.0},"71":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}},"l":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"113":{"tf":1.0},"115":{"tf":1.0},"34":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"70":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"76":{"tf":1.0}}}}}},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"40":{"tf":1.0}}}}}}},"df":13,"docs":{"129":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"38":{"tf":2.0},"39":{"tf":2.0},"40":{"tf":1.7320508075688772},"41":{"tf":1.0},"49":{"tf":1.0},"57":{"tf":1.0},"61":{"tf":1.0},"71":{"tf":1.0},"73":{"tf":1.0},"78":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":23,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"103":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.7320508075688772},"11":{"tf":2.0},"117":{"tf":1.0},"118":{"tf":1.0},"12":{"tf":1.0},"120":{"tf":1.4142135623730951},"14":{"tf":1.0},"25":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.0},"34":{"tf":1.0},"38":{"tf":1.0},"42":{"tf":1.0},"45":{"tf":1.7320508075688772},"52":{"tf":1.0},"57":{"tf":1.4142135623730951},"58":{"tf":1.0},"63":{"tf":1.0},"75":{"tf":1.7320508075688772}},"i":{"df":0,"docs":{},"t":{"df":8,"docs":{"21":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.4142135623730951},"33":{"tf":1.0},"47":{"tf":1.0},"49":{"tf":1.0},"50":{"tf":1.0},"57":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"63":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"68":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":4,"docs":{"4":{"tf":1.7320508075688772},"76":{"tf":1.4142135623730951},"78":{"tf":1.0},"81":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"c":{"df":2,"docs":{"113":{"tf":1.0},"88":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":6,"docs":{"68":{"tf":2.0},"69":{"tf":1.0},"70":{"tf":2.6457513110645907},"71":{"tf":1.0},"72":{"tf":1.0},"86":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":7,"docs":{"11":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":2.23606797749979},"25":{"tf":2.0},"26":{"tf":1.0},"28":{"tf":1.0},"44":{"tf":1.0}},"e":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"32":{"tf":2.449489742783178}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"32":{"tf":1.7320508075688772}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"21":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{",":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"17":{"tf":1.0},"23":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":5,"docs":{"18":{"tf":1.0},"22":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"95":{"tf":1.0},"96":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"73":{"tf":1.0}}}}},"q":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":7,"docs":{"120":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":2.6457513110645907},"35":{"tf":1.0},"36":{"tf":1.7320508075688772},"37":{"tf":1.0},"41":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"a":{"c":{"df":3,"docs":{"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"40":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"23":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"108":{"tf":1.0},"26":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":30,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.0},"119":{"tf":1.0},"12":{"tf":1.0},"120":{"tf":1.4142135623730951},"121":{"tf":2.0},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.4142135623730951},"13":{"tf":1.7320508075688772},"130":{"tf":1.0},"131":{"tf":1.0},"20":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"26":{"tf":1.0},"30":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":2.6457513110645907},"44":{"tf":1.0},"47":{"tf":1.0},"52":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"108":{"tf":1.0},"33":{"tf":1.0},"89":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"108":{"tf":1.4142135623730951},"26":{"tf":1.7320508075688772}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":1,"docs":{"58":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":5,"docs":{"20":{"tf":2.0},"21":{"tf":1.0},"58":{"tf":1.0},"61":{"tf":1.0},"65":{"tf":1.0}}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"0":{"tf":1.0}}}}}}}},"i":{"d":{"df":0,"docs":{},"n":{"\'":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"113":{"tf":1.0},"119":{"tf":1.0},"14":{"tf":1.0},"24":{"tf":1.0},"49":{"tf":1.0},"57":{"tf":2.0},"73":{"tf":1.0}}}}}},"g":{"df":1,"docs":{"76":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":17,"docs":{"113":{"tf":2.8284271247461903},"114":{"tf":3.0},"115":{"tf":3.0},"117":{"tf":1.0},"118":{"tf":1.0},"121":{"tf":1.4142135623730951},"122":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.0},"130":{"tf":1.4142135623730951},"131":{"tf":1.0},"132":{"tf":2.449489742783178},"28":{"tf":1.0},"35":{"tf":1.0}},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"114":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"114":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"l":{"df":0,"docs":{},"i":{"df":5,"docs":{"10":{"tf":1.0},"30":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"98":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"8":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"s":{"a":{"d":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"66":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"128":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"128":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"91":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"o":{"c":{"df":3,"docs":{"115":{"tf":1.0},"121":{"tf":1.0},"87":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":4,"docs":{"116":{"tf":1.0},"75":{"tf":1.0},"85":{"tf":1.4142135623730951},"89":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"\'":{"df":0,"docs":{},"t":{"df":2,"docs":{"20":{"tf":1.0},"87":{"tf":1.0}}}},"df":0,"docs":{}}}},"n":{"\'":{"df":0,"docs":{},"t":{"df":3,"docs":{"10":{"tf":1.0},"72":{"tf":1.0},"76":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":3,"docs":{"121":{"tf":1.0},"32":{"tf":1.0},"72":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"e":{"df":2,"docs":{"21":{"tf":1.0},"22":{"tf":1.0}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"111":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"66":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"g":{"df":4,"docs":{"111":{"tf":1.0},"119":{"tf":1.0},"125":{"tf":1.0},"29":{"tf":1.0}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"(":{"\\"":{"c":{"df":0,"docs":{},"o":{"d":{"df":6,"docs":{"21":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":2,"docs":{"61":{"tf":1.0},"65":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"63":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"63":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"a":{"c":{"df":0,"docs":{},"h":{"df":19,"docs":{"110":{"tf":1.0},"113":{"tf":1.0},"14":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"27":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.0},"37":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"57":{"tf":1.7320508075688772},"67":{"tf":1.0},"69":{"tf":1.0},"71":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"86":{"tf":1.0},"87":{"tf":1.0}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":2,"docs":{"27":{"tf":1.0},"9":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"25":{"tf":1.0}}}}}}},"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"a":{"d":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"n":{"df":1,"docs":{"58":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":9,"docs":{"21":{"tf":1.0},"28":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":2.23606797749979},"66":{"tf":2.449489742783178},"71":{"tf":1.0}},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{}}}},"g":{"df":1,"docs":{"110":{"tf":1.0}}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":1,"docs":{"54":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"27":{"tf":1.0}}}},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"85":{"tf":1.0}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"58":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":19,"docs":{"100":{"tf":1.0},"103":{"tf":1.4142135623730951},"104":{"tf":1.4142135623730951},"111":{"tf":1.0},"114":{"tf":1.0},"117":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.4142135623730951},"22":{"tf":1.0},"23":{"tf":1.0},"30":{"tf":1.4142135623730951},"50":{"tf":1.0},"6":{"tf":1.4142135623730951},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":14,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"111":{"tf":1.0},"114":{"tf":1.0},"117":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"22":{"tf":1.0},"30":{"tf":1.4142135623730951},"6":{"tf":1.4142135623730951},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"99":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{")":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":6,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"18":{"tf":1.0},"50":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":5,"docs":{"117":{"tf":2.23606797749979},"55":{"tf":1.0},"64":{"tf":1.0},"67":{"tf":1.4142135623730951},"87":{"tf":1.4142135623730951}},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"117":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"d":{"df":5,"docs":{"109":{"tf":1.0},"111":{"tf":1.0},"58":{"tf":2.449489742783178},"76":{"tf":1.0},"78":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"2":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":6,"docs":{"118":{"tf":2.449489742783178},"119":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":2.0},"129":{"tf":1.4142135623730951},"130":{"tf":2.0}}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"59":{"tf":1.7320508075688772}}}}},"u":{"df":0,"docs":{},"m":{"df":11,"docs":{"27":{"tf":2.23606797749979},"28":{"tf":3.7416573867739413},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"32":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"52":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"73":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"28":{"tf":1.0}}}}}},"v":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"16":{"tf":1.0},"57":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}}},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}},"q":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"73":{"tf":1.0}},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"d":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"1":{"0":{"0":{"df":1,"docs":{"53":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":1,"docs":{"53":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"54":{"tf":1.4142135623730951},"56":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"r":{"(":{"\\"":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"i":{"d":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"\\"":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":2,"docs":{"52":{"tf":1.0},"53":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":1,"docs":{"65":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"\\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"\\"":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"!":{"(":{"\\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"56":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"107":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}}},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"21":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"\\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}}}}}}}}}}}}},"df":0,"docs":{}},":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"66":{"tf":1.0}}}}}}}}}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"63":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":16,"docs":{"111":{"tf":1.0},"112":{"tf":1.4142135623730951},"12":{"tf":1.0},"14":{"tf":1.4142135623730951},"21":{"tf":3.605551275463989},"52":{"tf":1.0},"59":{"tf":2.23606797749979},"60":{"tf":1.0},"61":{"tf":2.8284271247461903},"62":{"tf":2.8284271247461903},"63":{"tf":3.1622776601683795},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":4,"docs":{"62":{"tf":1.4142135623730951},"63":{"tf":1.7320508075688772},"65":{"tf":1.0},"66":{"tf":1.4142135623730951}}}}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}}}}}}}}}}}}}}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}},"t":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":6,"docs":{"118":{"tf":1.0},"120":{"tf":1.0},"129":{"tf":1.0},"22":{"tf":1.0},"30":{"tf":1.0},"93":{"tf":1.0}},"t":{"df":0,"docs":{},"s":{"1":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"23":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"23":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"25":{"tf":1.0}}}}}}}},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":45,"docs":{"10":{"tf":1.0},"100":{"tf":1.4142135623730951},"101":{"tf":1.4142135623730951},"103":{"tf":1.4142135623730951},"104":{"tf":1.4142135623730951},"105":{"tf":1.4142135623730951},"107":{"tf":1.0},"111":{"tf":1.4142135623730951},"115":{"tf":1.0},"116":{"tf":1.4142135623730951},"119":{"tf":1.4142135623730951},"120":{"tf":1.0},"121":{"tf":1.0},"126":{"tf":1.0},"130":{"tf":1.0},"14":{"tf":1.4142135623730951},"16":{"tf":1.0},"18":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"26":{"tf":1.4142135623730951},"27":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.0},"43":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.0},"57":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.7320508075688772},"64":{"tf":1.0},"8":{"tf":2.0},"90":{"tf":1.0},"95":{"tf":1.4142135623730951},"96":{"tf":1.4142135623730951},"97":{"tf":1.4142135623730951},"99":{"tf":1.4142135623730951}}}}}},"c":{"df":0,"docs":{},"e":{"df":2,"docs":{"70":{"tf":1.0},"71":{"tf":1.0}}},"l":{"df":0,"docs":{},"u":{"d":{"df":1,"docs":{"111":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"80":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":16,"docs":{"110":{"tf":1.0},"113":{"tf":1.4142135623730951},"114":{"tf":2.0},"18":{"tf":1.4142135623730951},"47":{"tf":1.0},"48":{"tf":1.4142135623730951},"52":{"tf":1.0},"6":{"tf":1.7320508075688772},"72":{"tf":1.4142135623730951},"75":{"tf":1.0},"76":{"tf":1.4142135623730951},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"82":{"tf":2.449489742783178},"98":{"tf":1.4142135623730951},"99":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"110":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":6,"docs":{"121":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"74":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"53":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"65":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"16":{"tf":1.0},"34":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"50":{"tf":2.0}}}},"s":{"df":7,"docs":{"111":{"tf":1.0},"119":{"tf":1.0},"120":{"tf":1.0},"28":{"tf":1.0},"45":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"63":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{}}},"df":1,"docs":{"64":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"65":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":2,"docs":{"62":{"tf":1.0},"63":{"tf":1.0}}}}}}},"df":7,"docs":{"0":{"tf":1.0},"132":{"tf":1.0},"21":{"tf":1.0},"5":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.4142135623730951},"74":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":30,"docs":{"0":{"tf":1.0},"21":{"tf":1.0},"59":{"tf":2.0},"60":{"tf":1.0},"61":{"tf":1.4142135623730951},"62":{"tf":2.0},"63":{"tf":2.6457513110645907},"64":{"tf":1.0},"65":{"tf":2.23606797749979},"66":{"tf":1.0},"67":{"tf":2.0},"74":{"tf":2.23606797749979},"75":{"tf":3.1622776601683795},"76":{"tf":1.4142135623730951},"77":{"tf":1.7320508075688772},"78":{"tf":2.0},"79":{"tf":1.7320508075688772},"80":{"tf":1.7320508075688772},"81":{"tf":1.7320508075688772},"82":{"tf":1.7320508075688772},"83":{"tf":1.7320508075688772},"84":{"tf":1.7320508075688772},"85":{"tf":2.23606797749979},"86":{"tf":2.0},"87":{"tf":2.23606797749979},"88":{"tf":2.23606797749979},"89":{"tf":2.0},"90":{"tf":2.0},"91":{"tf":1.7320508075688772},"92":{"tf":2.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"c":{"df":1,"docs":{"67":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"<":{"\'":{"_":{"df":8,"docs":{"76":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"75":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"n":{"df":87,"docs":{"10":{"tf":1.0},"100":{"tf":1.7320508075688772},"103":{"tf":1.7320508075688772},"104":{"tf":1.7320508075688772},"107":{"tf":1.0},"108":{"tf":1.4142135623730951},"11":{"tf":1.0},"112":{"tf":1.0},"114":{"tf":1.4142135623730951},"115":{"tf":1.4142135623730951},"117":{"tf":1.0},"119":{"tf":1.4142135623730951},"12":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.4142135623730951},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":2.23606797749979},"128":{"tf":1.4142135623730951},"129":{"tf":3.0},"13":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0},"14":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.7320508075688772},"19":{"tf":1.4142135623730951},"20":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.4142135623730951},"26":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.7320508075688772},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"43":{"tf":1.4142135623730951},"44":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.7320508075688772},"5":{"tf":1.0},"50":{"tf":1.0},"52":{"tf":1.4142135623730951},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":2.0},"64":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"87":{"tf":1.0},"95":{"tf":1.7320508075688772},"96":{"tf":1.7320508075688772},"99":{"tf":1.7320508075688772}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"98":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"f":{"3":{"2":{"df":2,"docs":{"29":{"tf":2.8284271247461903},"31":{"tf":2.449489742783178}}},"df":0,"docs":{}},"6":{"4":{"df":2,"docs":{"33":{"tf":1.7320508075688772},"5":{"tf":1.0}}},"df":0,"docs":{}},"a":{"c":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"111":{"tf":1.0}}}}}},"df":1,"docs":{"61":{"tf":1.0}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"114":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":4,"docs":{"21":{"tf":1.0},"22":{"tf":1.0},"32":{"tf":1.0},"52":{"tf":1.0}}}},"l":{"df":0,"docs":{},"s":{"df":3,"docs":{"121":{"tf":1.0},"128":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951}}}}},"df":1,"docs":{"71":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":7,"docs":{"118":{"tf":1.0},"131":{"tf":1.0},"28":{"tf":1.0},"37":{"tf":1.0},"74":{"tf":1.0},"90":{"tf":1.0},"92":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\'":{"df":1,"docs":{"115":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":18,"docs":{"115":{"tf":1.0},"116":{"tf":2.449489742783178},"117":{"tf":2.23606797749979},"118":{"tf":1.7320508075688772},"119":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"75":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"111":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"110":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"w":{"df":2,"docs":{"11":{"tf":1.0},"57":{"tf":1.4142135623730951}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"df":1,"docs":{"129":{"tf":1.4142135623730951}},"n":{"a":{"df":0,"docs":{},"m":{"df":3,"docs":{"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"40":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"1":{"df":2,"docs":{"34":{"tf":2.23606797749979},"43":{"tf":2.0}}},"2":{"df":2,"docs":{"34":{"tf":2.23606797749979},"43":{"tf":2.0}}},"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"115":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"115":{"tf":1.7320508075688772}}}}}}}}},"df":45,"docs":{"10":{"tf":1.7320508075688772},"11":{"tf":2.0},"114":{"tf":1.7320508075688772},"115":{"tf":1.0},"118":{"tf":1.4142135623730951},"119":{"tf":1.4142135623730951},"120":{"tf":1.7320508075688772},"121":{"tf":2.0},"122":{"tf":1.0},"123":{"tf":1.4142135623730951},"126":{"tf":1.7320508075688772},"127":{"tf":1.0},"128":{"tf":2.0},"129":{"tf":3.7416573867739413},"13":{"tf":1.7320508075688772},"130":{"tf":1.4142135623730951},"14":{"tf":2.0},"20":{"tf":1.7320508075688772},"24":{"tf":2.6457513110645907},"25":{"tf":1.7320508075688772},"26":{"tf":1.0},"29":{"tf":2.6457513110645907},"31":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":2.0},"37":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"41":{"tf":1.4142135623730951},"43":{"tf":1.0},"47":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.4142135623730951},"52":{"tf":2.0},"53":{"tf":1.4142135623730951},"54":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"69":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.4142135623730951},"72":{"tf":2.23606797749979},"73":{"tf":2.23606797749979},"83":{"tf":2.0},"86":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"64":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"22":{"tf":1.0}}},"m":{"df":1,"docs":{"27":{"tf":1.0}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"100":{"tf":1.0},"49":{"tf":1.0},"98":{"tf":1.7320508075688772},"99":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"76":{"tf":1.0}}}},"d":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"b":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"130":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"_":{"b":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"128":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"129":{"tf":1.0},"130":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"b":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"119":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"a":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"119":{"tf":1.0}},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"119":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"119":{"tf":1.0}},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"119":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"119":{"tf":1.0}}},"df":0,"docs":{}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"120":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":2,"docs":{"119":{"tf":1.7320508075688772},"63":{"tf":1.7320508075688772}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":8,"docs":{"111":{"tf":1.0},"114":{"tf":1.0},"117":{"tf":1.0},"17":{"tf":1.0},"30":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{")":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":4,"docs":{"14":{"tf":1.0},"48":{"tf":1.0},"58":{"tf":2.0},"78":{"tf":1.7320508075688772}}}}},"t":{"df":1,"docs":{"59":{"tf":1.0}}},"x":{"df":1,"docs":{"109":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":3,"docs":{"13":{"tf":1.7320508075688772},"32":{"tf":2.23606797749979},"36":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.0}}}},"n":{"df":71,"docs":{"100":{"tf":1.7320508075688772},"103":{"tf":1.0},"104":{"tf":1.0},"107":{"tf":1.4142135623730951},"11":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.4142135623730951},"112":{"tf":1.4142135623730951},"114":{"tf":2.23606797749979},"115":{"tf":1.4142135623730951},"117":{"tf":1.4142135623730951},"119":{"tf":1.7320508075688772},"120":{"tf":1.0},"123":{"tf":1.0},"128":{"tf":1.7320508075688772},"129":{"tf":1.4142135623730951},"130":{"tf":1.0},"132":{"tf":1.0},"14":{"tf":2.0},"16":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":2.0},"23":{"tf":1.4142135623730951},"24":{"tf":2.449489742783178},"26":{"tf":1.4142135623730951},"28":{"tf":1.0},"29":{"tf":2.449489742783178},"30":{"tf":1.4142135623730951},"31":{"tf":2.0},"33":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":2.0},"40":{"tf":2.0},"41":{"tf":1.0},"44":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.4142135623730951},"49":{"tf":1.0},"5":{"tf":1.0},"50":{"tf":1.0},"52":{"tf":2.0},"53":{"tf":1.7320508075688772},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.7320508075688772},"57":{"tf":1.7320508075688772},"58":{"tf":1.0},"6":{"tf":1.4142135623730951},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.4142135623730951},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.4142135623730951},"73":{"tf":1.0},"76":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"87":{"tf":1.4142135623730951},"99":{"tf":1.4142135623730951}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":21,"docs":{"107":{"tf":1.0},"111":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.0},"132":{"tf":1.0},"14":{"tf":1.4142135623730951},"16":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"43":{"tf":1.0},"48":{"tf":1.4142135623730951},"49":{"tf":1.0},"57":{"tf":1.4142135623730951},"70":{"tf":1.0},"72":{"tf":1.0},"88":{"tf":1.0}}}}}},"r":{"c":{"df":1,"docs":{"87":{"tf":1.0}}},"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"!":{"(":{"\\"":{"df":0,"docs":{},"{":{"df":0,"docs":{},"}":{"b":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"44":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"129":{"tf":1.0},"24":{"tf":1.4142135623730951},"50":{"tf":1.0}}}},"df":1,"docs":{"21":{"tf":1.0}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}}}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"29":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"d":{"\\"":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"111":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"75":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":1,"docs":{"75":{"tf":1.0}}}},"o":{"df":0,"docs":{},"m":{"(":{"df":1,"docs":{"28":{"tf":1.0}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":2,"docs":{"24":{"tf":1.0},"26":{"tf":1.0}}}}},"df":0,"docs":{}}},"<":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"c":{"2":{"8":{"2":{"2":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"u":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":1.4142135623730951}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"t":{">":{">":{"(":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"26":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"u":{"df":1,"docs":{"25":{"tf":1.0}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"c":{"<":{"df":0,"docs":{},"u":{"df":1,"docs":{"25":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"111":{"tf":1.0}},"i":{"df":1,"docs":{"0":{"tf":1.0}}}}},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":19,"docs":{"107":{"tf":1.0},"114":{"tf":1.0},"115":{"tf":1.4142135623730951},"117":{"tf":1.0},"119":{"tf":1.0},"14":{"tf":1.7320508075688772},"16":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.4142135623730951},"39":{"tf":1.0},"47":{"tf":1.0},"49":{"tf":1.0},"52":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"73":{"tf":1.4142135623730951},"76":{"tf":2.0},"78":{"tf":1.7320508075688772},"93":{"tf":1.0}}}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"129":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"63":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"78":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{":":{":":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"84":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{"0":{".":{".":{"1":{"0":{"df":2,"docs":{"100":{"tf":1.0},"23":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{"0":{".":{".":{"2":{"0":{"df":1,"docs":{"23":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"{":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"100":{"tf":1.0},"23":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"0":{"tf":1.0},"114":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"34":{"tf":2.6457513110645907},"42":{"tf":2.23606797749979},"43":{"tf":2.23606797749979},"44":{"tf":1.4142135623730951},"48":{"tf":1.0},"49":{"tf":1.0},"61":{"tf":1.4142135623730951}}}}},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"53":{"tf":1.0},"87":{"tf":1.0}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"96":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.0}},"h":{"df":0,"docs":{},"u":{"b":{"df":1,"docs":{"111":{"tf":1.0}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":4,"docs":{"111":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.0},"81":{"tf":1.0}},"n":{"df":2,"docs":{"42":{"tf":1.0},"62":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"14":{"tf":1.4142135623730951},"15":{"tf":1.0},"16":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"15":{"tf":1.0}}}},"df":1,"docs":{"77":{"tf":1.0}},"o":{"d":{"df":1,"docs":{"90":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":2,"docs":{"45":{"tf":1.0},"89":{"tf":1.0}},"q":{"df":0,"docs":{},"l":{"\'":{"df":3,"docs":{"27":{"tf":1.0},"31":{"tf":1.0},"75":{"tf":1.4142135623730951}}},"(":{"c":{"a":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"(":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"x":{"_":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"57":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"57":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":5,"docs":{"11":{"tf":1.0},"111":{"tf":1.0},"123":{"tf":1.0},"129":{"tf":1.0},"72":{"tf":1.4142135623730951}}}}}}},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}},"e":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"34":{"tf":2.0},"43":{"tf":2.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"39":{"tf":1.0},"41":{"tf":1.0}}}}}}},"df":3,"docs":{"39":{"tf":1.4142135623730951},"41":{"tf":1.4142135623730951},"49":{"tf":1.0}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"d":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"24":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"26":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"s":{"c":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":7,"docs":{"117":{"tf":1.0},"118":{"tf":1.0},"119":{"tf":1.7320508075688772},"120":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.0},"130":{"tf":1.0}}}},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":3,"docs":{"127":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":2.0}}}}}}}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"30":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":3,"docs":{"13":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"52":{"tf":1.4142135623730951},"53":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"125":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"12":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":2,"docs":{"119":{"tf":1.0},"129":{"tf":1.0}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"29":{"tf":1.4142135623730951}},"e":{"=":{"\\"":{"a":{"a":{"a":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"44":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"126":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"128":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"129":{"tf":1.4142135623730951}}}}}}}},"s":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}}}},"df":0,"docs":{}},"h":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"123":{"tf":1.4142135623730951},"124":{"tf":1.0},"125":{"tf":1.0}}}},"df":0,"docs":{}},"k":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"10":{"tf":1.4142135623730951}}}}}},"t":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"131":{"tf":1.0}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"121":{"tf":1.4142135623730951}}}}}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"55":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"54":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"73":{"tf":2.0}}}}}},"/":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"_":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"89":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"/":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"105":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"97":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":1,"docs":{"101":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"95":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"s":{"c":{"a":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"107":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"98":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":50,"docs":{"0":{"tf":2.23606797749979},"1":{"tf":1.4142135623730951},"10":{"tf":1.4142135623730951},"107":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"113":{"tf":1.4142135623730951},"114":{"tf":1.0},"115":{"tf":1.4142135623730951},"116":{"tf":1.4142135623730951},"117":{"tf":1.0},"118":{"tf":1.0},"129":{"tf":1.0},"24":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"29":{"tf":2.449489742783178},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"4":{"tf":2.0},"40":{"tf":1.0},"42":{"tf":1.0},"45":{"tf":1.4142135623730951},"47":{"tf":1.0},"48":{"tf":1.0},"5":{"tf":2.23606797749979},"54":{"tf":1.0},"57":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"59":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.4142135623730951},"69":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.7320508075688772},"80":{"tf":1.7320508075688772},"85":{"tf":1.4142135623730951},"88":{"tf":1.0},"89":{"tf":1.7320508075688772},"9":{"tf":1.4142135623730951},"90":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0},"93":{"tf":2.449489742783178},"98":{"tf":2.0}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"103":{"tf":1.0}}}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{":":{":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"&":{"*":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{")":{")":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":1,"docs":{"104":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"54":{"tf":2.23606797749979}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"19":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"111":{"tf":1.0}}}}}},"u":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"52":{"tf":2.6457513110645907},"53":{"tf":2.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"52":{"tf":1.7320508075688772}}}}}}},"h":{"a":{"df":0,"docs":{},"n":{"d":{"df":4,"docs":{"11":{"tf":1.0},"20":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.0}},"i":{"df":1,"docs":{"28":{"tf":1.0}}},"l":{"df":1,"docs":{"21":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"r":{"d":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"108":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"74":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"18":{"tf":1.0},"19":{"tf":3.0},"57":{"tf":1.0}}}}},"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":1,"docs":{"2":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"\\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"117":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"117":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"19":{"tf":1.0}}}},"p":{"df":2,"docs":{"25":{"tf":1.0},"89":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":7,"docs":{"103":{"tf":1.0},"21":{"tf":1.0},"27":{"tf":1.0},"5":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.4142135623730951},"85":{"tf":1.0}}}}},"i":{"d":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"73":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"e":{"df":1,"docs":{"73":{"tf":2.0}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":1,"docs":{"0":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"0":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":3,"docs":{"75":{"tf":1.0},"77":{"tf":1.0},"79":{"tf":1.4142135623730951}}}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"57":{"tf":1.0}}},"s":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"b":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"2":{"tf":1.0}}}},"t":{"df":0,"docs":{},"p":{":":{":":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"_":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":2,"docs":{"19":{"tf":1.4142135623730951},"57":{"tf":1.0}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.0}}}}}}}}},"s":{":":{"/":{"/":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"v":{"1":{".":{"0":{"df":1,"docs":{"132":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":3,"docs":{"101":{"tf":1.0},"105":{"tf":1.0},"97":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":1,"docs":{"82":{"tf":1.0}}}},"m":{"a":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"128":{"tf":2.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"128":{"tf":2.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"n":{"d":{"df":0,"docs":{},"r":{"df":1,"docs":{"109":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{"\'":{"df":0,"docs":{},"v":{"df":1,"docs":{"1":{"tf":1.0}}}},".":{"df":1,"docs":{"13":{"tf":1.0}}},"3":{"2":{":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"39":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":34,"docs":{"10":{"tf":1.7320508075688772},"100":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"108":{"tf":1.4142135623730951},"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"120":{"tf":2.0},"14":{"tf":1.0},"17":{"tf":1.0},"20":{"tf":2.0},"22":{"tf":1.4142135623730951},"23":{"tf":1.7320508075688772},"32":{"tf":2.449489742783178},"33":{"tf":1.0},"34":{"tf":2.0},"37":{"tf":1.0},"39":{"tf":2.6457513110645907},"40":{"tf":3.605551275463989},"41":{"tf":2.0},"43":{"tf":2.0},"47":{"tf":1.0},"49":{"tf":1.4142135623730951},"5":{"tf":2.0},"50":{"tf":1.7320508075688772},"52":{"tf":1.4142135623730951},"53":{"tf":2.449489742783178},"56":{"tf":2.23606797749979},"57":{"tf":1.7320508075688772},"58":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"73":{"tf":1.7320508075688772},"95":{"tf":1.0},"96":{"tf":1.0}}},"df":0,"docs":{}},"6":{"4":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"d":{"df":12,"docs":{"110":{"tf":4.58257569495584},"111":{"tf":2.0},"119":{"tf":4.58257569495584},"121":{"tf":2.449489742783178},"126":{"tf":2.23606797749979},"127":{"tf":2.0},"128":{"tf":3.1622776601683795},"129":{"tf":3.1622776601683795},"130":{"tf":2.23606797749979},"131":{"tf":1.7320508075688772},"14":{"tf":1.7320508075688772},"54":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"87":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"128":{"tf":1.4142135623730951}}}}}},"m":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"110":{"tf":1.0}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"<":{"df":0,"docs":{},"t":{"df":1,"docs":{"44":{"tf":1.0}}}},"df":62,"docs":{"100":{"tf":1.7320508075688772},"107":{"tf":1.0},"11":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.4142135623730951},"112":{"tf":1.4142135623730951},"114":{"tf":2.23606797749979},"117":{"tf":1.0},"119":{"tf":1.0},"120":{"tf":1.0},"123":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.4142135623730951},"130":{"tf":1.0},"14":{"tf":2.0},"16":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":2.0},"23":{"tf":2.0},"24":{"tf":2.0},"25":{"tf":1.4142135623730951},"26":{"tf":1.0},"28":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"33":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.4142135623730951},"5":{"tf":1.0},"50":{"tf":1.0},"52":{"tf":2.0},"53":{"tf":1.7320508075688772},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.7320508075688772},"57":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"99":{"tf":1.0}},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":35,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"107":{"tf":1.4142135623730951},"108":{"tf":1.0},"112":{"tf":1.7320508075688772},"114":{"tf":1.0},"118":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.0},"129":{"tf":1.0},"130":{"tf":1.0},"16":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"28":{"tf":1.0},"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.7320508075688772},"43":{"tf":1.0},"5":{"tf":1.0},"59":{"tf":1.0},"63":{"tf":1.7320508075688772},"65":{"tf":1.0},"66":{"tf":1.7320508075688772},"75":{"tf":1.0},"78":{"tf":1.0},"80":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"89":{"tf":1.0},"9":{"tf":1.0},"93":{"tf":1.0},"98":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":4,"docs":{"1":{"tf":1.0},"115":{"tf":1.0},"121":{"tf":1.0},"132":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":2,"docs":{"57":{"tf":1.0},"87":{"tf":1.0}}}}}}},"n":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":2.0}}}},"df":0,"docs":{}}}}},"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"32":{"tf":1.0}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"125":{"tf":2.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":5,"docs":{"10":{"tf":1.0},"113":{"tf":1.0},"132":{"tf":1.0},"25":{"tf":1.0},"88":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"m":{"df":2,"docs":{"75":{"tf":1.0},"78":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"_":{"df":0,"docs":{},"w":{"df":1,"docs":{"104":{"tf":1.0}}}},"df":1,"docs":{"103":{"tf":1.0}}}},"i":{"c":{"df":4,"docs":{"122":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.0}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"30":{"tf":1.0}}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"u":{"df":1,"docs":{"118":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{">":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":1,"docs":{"99":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"o":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"83":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"65":{"tf":1.4142135623730951},"83":{"tf":1.0}},"r":{"df":0,"docs":{},"m":{"df":5,"docs":{"113":{"tf":1.0},"59":{"tf":1.0},"61":{"tf":1.0},"76":{"tf":1.0},"8":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"30":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"36":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"54":{"tf":1.0},"55":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"12":{"tf":1.0},"34":{"tf":1.0}}}},"df":0,"docs":{}}},"df":16,"docs":{"107":{"tf":1.0},"115":{"tf":2.23606797749979},"12":{"tf":1.7320508075688772},"129":{"tf":1.0},"130":{"tf":1.0},"21":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":2.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"41":{"tf":1.4142135623730951},"54":{"tf":1.7320508075688772},"55":{"tf":1.0},"56":{"tf":1.0}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"12":{"tf":1.0},"120":{"tf":1.0},"33":{"tf":2.449489742783178},"34":{"tf":2.6457513110645907},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"54":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"34":{"tf":2.0},"37":{"tf":1.0}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"<":{"df":0,"docs":{},"i":{"3":{"2":{"df":1,"docs":{"56":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"107":{"tf":1.0},"24":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"19":{"tf":2.0},"75":{"tf":1.0}}}}},"i":{"d":{"df":3,"docs":{"16":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"18":{"tf":1.0}}},"df":0,"docs":{}},"t":{"a":{"df":0,"docs":{},"n":{"c":{"df":3,"docs":{"114":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"42":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":4,"docs":{"22":{"tf":1.0},"25":{"tf":1.0},"66":{"tf":1.0},"87":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"o":{"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"126":{"tf":1.4142135623730951},"127":{"tf":1.0},"128":{"tf":1.0}}}},"df":0,"docs":{}}}},"t":{"df":8,"docs":{"115":{"tf":1.4142135623730951},"120":{"tf":1.4142135623730951},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"125":{"tf":1.7320508075688772},"129":{"tf":1.4142135623730951},"44":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"g":{"df":3,"docs":{"107":{"tf":1.4142135623730951},"21":{"tf":1.0},"49":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"49":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":16,"docs":{"100":{"tf":1.0},"101":{"tf":1.0},"102":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"4":{"tf":1.7320508075688772},"8":{"tf":1.4142135623730951},"91":{"tf":1.0},"93":{"tf":1.7320508075688772},"94":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"97":{"tf":1.0},"98":{"tf":1.4142135623730951},"99":{"tf":1.0}}}},"r":{"df":0,"docs":{},"f":{"a":{"c":{"df":6,"docs":{"22":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":2.6457513110645907},"30":{"tf":2.0},"31":{"tf":1.7320508075688772},"40":{"tf":1.4142135623730951}},"e":{"\'":{"df":1,"docs":{"29":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"o":{":":{":":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"29":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"0":{"tf":1.7320508075688772},"1":{"tf":1.0},"2":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"73":{"tf":2.8284271247461903}}}},"df":0,"docs":{}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"126":{"tf":1.0}}}}}}}}}},"p":{"df":1,"docs":{"54":{"tf":1.4142135623730951}}},"s":{"_":{"a":{"d":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"(":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":1,"docs":{"73":{"tf":1.0}}}}},"df":0,"docs":{}},"df":1,"docs":{"73":{"tf":2.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"d":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"(":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"73":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":1,"docs":{"118":{"tf":1.0}}}}},"t":{"\'":{"df":13,"docs":{"125":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"27":{"tf":1.0},"36":{"tf":1.0},"69":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.4142135623730951},"78":{"tf":2.0},"88":{"tf":1.0},"89":{"tf":1.0},"9":{"tf":1.0},"90":{"tf":1.0}},"l":{"df":0,"docs":{},"l":{"df":4,"docs":{"75":{"tf":1.0},"78":{"tf":1.0},"80":{"tf":1.0},"82":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":2,"docs":{"27":{"tf":1.0},"73":{"tf":1.7320508075688772}}}}}},"j":{"a":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"76":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"7":{"tf":1.7320508075688772}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"28":{"tf":1.0}}}},"y":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":6,"docs":{"119":{"tf":1.4142135623730951},"120":{"tf":1.0},"121":{"tf":1.0},"126":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":11,"docs":{"111":{"tf":1.0},"112":{"tf":1.4142135623730951},"118":{"tf":2.0},"119":{"tf":1.7320508075688772},"120":{"tf":4.0},"121":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"36":{"tf":1.0},"59":{"tf":1.0},"65":{"tf":1.4142135623730951},"87":{"tf":2.0}},"{":{"a":{"df":1,"docs":{"120":{"tf":1.0}}},"df":0,"docs":{}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":2,"docs":{"20":{"tf":1.0},"76":{"tf":1.0}}}}}},"l":{"a":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"10":{"tf":1.0}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"5":{"tf":1.0},"50":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"g":{"df":2,"docs":{"69":{"tf":1.0},"87":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"58":{"tf":2.449489742783178},"80":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"98":{"tf":1.0}}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"83":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"118":{"tf":1.0}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"90":{"tf":1.0}}}}},"df":0,"docs":{},"n":{"df":1,"docs":{"44":{"tf":1.7320508075688772}},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"54":{"tf":2.23606797749979}}}}}},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"54":{"tf":2.23606797749979},"64":{"tf":1.0}}}},"t":{"\'":{"df":2,"docs":{"11":{"tf":1.0},"76":{"tf":1.0}}},"df":2,"docs":{"109":{"tf":1.0},"63":{"tf":1.0}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":3,"docs":{"32":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0}}}}}},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.0},"4":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"l":{"df":3,"docs":{"75":{"tf":1.0},"79":{"tf":1.4142135623730951},"84":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"16":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"(":{"5":{"df":1,"docs":{"71":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"5":{"df":1,"docs":{"70":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":6,"docs":{"113":{"tf":1.0},"22":{"tf":1.0},"68":{"tf":1.0},"70":{"tf":2.23606797749979},"71":{"tf":2.23606797749979},"72":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"k":{"df":3,"docs":{"117":{"tf":1.0},"121":{"tf":1.0},"132":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":6,"docs":{"37":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":2.0},"69":{"tf":1.0},"72":{"tf":1.4142135623730951},"93":{"tf":1.0}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":4,"docs":{"129":{"tf":1.0},"18":{"tf":1.0},"31":{"tf":1.0},"92":{"tf":1.0}}}}}},"o":{"a":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"111":{"tf":1.0},"112":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"112":{"tf":1.4142135623730951},"87":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"i":{"d":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"111":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"<":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"112":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"u":{"6":{"4":{"df":1,"docs":{"111":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"112":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":2,"docs":{"111":{"tf":1.0},"112":{"tf":1.0}}}}},"df":0,"docs":{}},"c":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"28":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":2.0}}}}}}},"t":{"df":8,"docs":{"114":{"tf":1.0},"115":{"tf":2.449489742783178},"131":{"tf":1.0},"132":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":1,"docs":{"90":{"tf":1.0}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"90":{"tf":1.7320508075688772},"92":{"tf":1.0}}}}},"i":{"c":{"df":4,"docs":{"24":{"tf":1.0},"76":{"tf":2.0},"78":{"tf":1.0},"83":{"tf":1.4142135623730951}}},"df":0,"docs":{},"n":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"48":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"48":{"tf":1.4142135623730951}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"20":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":10,"docs":{"109":{"tf":1.0},"118":{"tf":1.4142135623730951},"119":{"tf":1.4142135623730951},"120":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.4142135623730951},"47":{"tf":1.0},"61":{"tf":1.0}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"119":{"tf":1.0}}}}}},"t":{"df":4,"docs":{"74":{"tf":1.4142135623730951},"75":{"tf":1.0},"76":{"tf":1.0},"85":{"tf":1.0}}}}},"m":{"a":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":8,"docs":{"0":{"tf":1.0},"108":{"tf":1.7320508075688772},"11":{"tf":2.449489742783178},"114":{"tf":1.0},"115":{"tf":1.0},"132":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"117":{"tf":1.0},"15":{"tf":1.0}}}},"k":{"df":0,"docs":{},"e":{"df":4,"docs":{"1":{"tf":1.0},"109":{"tf":1.0},"129":{"tf":1.0},"22":{"tf":1.0}}}},"n":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":1,"docs":{"110":{"tf":1.0}}},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"117":{"tf":1.0},"30":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"p":{"(":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"49":{"tf":1.0}}}}}},"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"(":{"a":{"df":0,"docs":{},"r":{"c":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"111":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"|":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":2,"docs":{"21":{"tf":1.0},"62":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"66":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"k":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"111":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":3,"docs":{"10":{"tf":1.7320508075688772},"5":{"tf":1.0},"59":{"tf":1.0}}},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"35":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"107":{"tf":1.0},"114":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"54":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}}},"df":0,"docs":{}},"x":{"_":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"57":{"tf":1.4142135623730951}},"e":{"=":{"3":{"0":{"df":1,"docs":{"57":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"6":{"0":{"df":1,"docs":{"57":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":2,"docs":{"54":{"tf":1.0},"55":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":2,"docs":{"70":{"tf":1.0},"71":{"tf":1.0}}}}}}},"y":{"b":{"df":1,"docs":{"73":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"n":{"df":3,"docs":{"0":{"tf":1.0},"37":{"tf":1.0},"45":{"tf":1.0}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"57":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"11":{"tf":1.0},"32":{"tf":2.23606797749979},"55":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"g":{"df":3,"docs":{"22":{"tf":2.449489742783178},"23":{"tf":2.23606797749979},"57":{"tf":1.0}},"e":{"d":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"22":{"tf":1.4142135623730951},"23":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"23":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":6,"docs":{"14":{"tf":1.0},"21":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"65":{"tf":1.0},"70":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"131":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":6,"docs":{"117":{"tf":1.0},"29":{"tf":2.0},"34":{"tf":1.0},"43":{"tf":1.0},"50":{"tf":1.0},"58":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"91":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"d":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"75":{"tf":1.0},"76":{"tf":3.0}},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"76":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"76":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"126":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":1,"docs":{"57":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"73":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"68":{"tf":1.0}}}}}},"o":{"d":{"df":1,"docs":{"28":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":3,"docs":{"113":{"tf":1.4142135623730951},"31":{"tf":1.0},"74":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"22":{"tf":1.0}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"89":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":12,"docs":{"101":{"tf":1.4142135623730951},"105":{"tf":1.4142135623730951},"108":{"tf":1.0},"116":{"tf":1.0},"121":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"58":{"tf":1.0},"65":{"tf":1.4142135623730951},"76":{"tf":1.0},"8":{"tf":1.0},"97":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"109":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":3,"docs":{"18":{"tf":1.0},"58":{"tf":1.0},"99":{"tf":1.0}}},"i":{"df":1,"docs":{"22":{"tf":1.0}},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.7320508075688772}}}}}}},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":12,"docs":{"112":{"tf":1.7320508075688772},"116":{"tf":1.0},"118":{"tf":1.0},"120":{"tf":1.0},"122":{"tf":1.0},"19":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772},"28":{"tf":1.0},"34":{"tf":1.0},"43":{"tf":1.0},"54":{"tf":1.0},"57":{"tf":1.4142135623730951}},"e":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"t":{"a":{"df":0,"docs":{},"t":{"df":10,"docs":{"22":{"tf":1.0},"33":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"46":{"tf":1.7320508075688772},"47":{"tf":1.0},"48":{"tf":3.0},"5":{"tf":1.0},"6":{"tf":1.0},"82":{"tf":1.0},"98":{"tf":1.0}}}},"df":3,"docs":{"18":{"tf":1.0},"49":{"tf":1.0},"58":{"tf":1.7320508075688772}}}},"v":{"df":1,"docs":{"108":{"tf":2.0}}},"y":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"39":{"tf":1.7320508075688772},"40":{"tf":1.4142135623730951},"41":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"21":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"73":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"63":{"tf":1.0}}}}}}}}}}}}}}}}}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"63":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"21":{"tf":1.4142135623730951},"63":{"tf":1.7320508075688772}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"41":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"a":{"c":{"df":2,"docs":{"30":{"tf":2.449489742783178},"40":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"61":{"tf":1.0},"65":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{"d":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":7,"docs":{"78":{"tf":2.0},"79":{"tf":1.4142135623730951},"80":{"tf":1.4142135623730951},"81":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951},"83":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"b":{"df":0,"docs":{},"j":{"(":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":1,"docs":{"40":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":5,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"20":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"73":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"(":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":3,"docs":{"10":{"tf":1.4142135623730951},"14":{"tf":1.4142135623730951},"30":{"tf":2.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"12":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":2,"docs":{"30":{"tf":1.0},"99":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"1":{"0":{"0":{"df":1,"docs":{"56":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":1,"docs":{"56":{"tf":2.0}}},"df":0,"docs":{}},"u":{"df":1,"docs":{"108":{"tf":1.0}}}}},"df":0,"docs":{}}}},"n":{"+":{"1":{"df":5,"docs":{"109":{"tf":1.7320508075688772},"110":{"tf":1.0},"111":{"tf":1.0},"112":{"tf":1.0},"118":{"tf":1.0}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"110":{"tf":1.0},"111":{"tf":1.0}}}}}}},"df":0,"docs":{}},".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":1,"docs":{"111":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"o":{"_":{"c":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"_":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},":":{"\\"":{"\\"":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":21,"docs":{"110":{"tf":4.47213595499958},"111":{"tf":2.0},"12":{"tf":1.0},"127":{"tf":1.4142135623730951},"128":{"tf":1.4142135623730951},"129":{"tf":1.4142135623730951},"131":{"tf":1.4142135623730951},"14":{"tf":1.0},"18":{"tf":1.0},"22":{"tf":1.4142135623730951},"23":{"tf":1.0},"26":{"tf":1.0},"27":{"tf":1.4142135623730951},"29":{"tf":3.3166247903554},"30":{"tf":2.0},"34":{"tf":1.4142135623730951},"42":{"tf":1.4142135623730951},"44":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.0},"8":{"tf":1.0}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"20":{"tf":1.0}}}}}},"df":4,"docs":{"110":{"tf":1.0},"54":{"tf":3.0},"56":{"tf":2.0},"58":{"tf":1.0}},"e":{"df":0,"docs":{},"e":{"d":{"df":19,"docs":{"103":{"tf":1.0},"107":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.4142135623730951},"114":{"tf":1.0},"121":{"tf":1.0},"129":{"tf":1.0},"130":{"tf":1.4142135623730951},"14":{"tf":1.0},"16":{"tf":1.4142135623730951},"22":{"tf":1.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"34":{"tf":1.0},"4":{"tf":1.7320508075688772},"45":{"tf":1.0},"53":{"tf":1.4142135623730951},"63":{"tf":1.0},"76":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{"df":5,"docs":{"120":{"tf":1.4142135623730951},"121":{"tf":1.0},"130":{"tf":1.7320508075688772},"32":{"tf":1.7320508075688772},"70":{"tf":1.4142135623730951}},"e":{"d":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"120":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"87":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"45":{"tf":1.0}}}}},"w":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"53":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"df":1,"docs":{"56":{"tf":1.0}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}}}}},"df":5,"docs":{"12":{"tf":1.0},"126":{"tf":1.0},"24":{"tf":1.0},"28":{"tf":1.0},"62":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"88":{"tf":1.0}}}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"x":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"(":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"76":{"tf":1.0},"78":{"tf":1.4142135623730951},"81":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":5,"docs":{"79":{"tf":1.0},"80":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0}}}}},"df":0,"docs":{}},"df":1,"docs":{"76":{"tf":1.7320508075688772}}}}}},"df":8,"docs":{"76":{"tf":2.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"<":{"\'":{"_":{"df":1,"docs":{"82":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"m":{"df":0,"docs":{},"i":{"d":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"<":{"\'":{"_":{"df":1,"docs":{"76":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"<":{"\'":{"_":{"df":1,"docs":{"80":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"<":{"\'":{"_":{"df":1,"docs":{"79":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"<":{"\'":{"_":{"df":1,"docs":{"78":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"<":{"\'":{"_":{"df":1,"docs":{"83":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"e":{"<":{"\'":{"_":{"df":1,"docs":{"84":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"\'":{"_":{"df":1,"docs":{"81":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"11":{"tf":1.0},"72":{"tf":1.0}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"129":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"l":{"df":4,"docs":{"0":{"tf":1.0},"132":{"tf":1.0},"34":{"tf":1.0},"43":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":12,"docs":{"11":{"tf":1.0},"115":{"tf":1.0},"12":{"tf":1.0},"121":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.0},"16":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"34":{"tf":1.0},"43":{"tf":1.0},"72":{"tf":1.0}}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"63":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}},"i":{"c":{"df":2,"docs":{"109":{"tf":1.0},"119":{"tf":1.0}}},"df":0,"docs":{}}},"w":{"df":3,"docs":{"103":{"tf":1.0},"2":{"tf":1.0},"88":{"tf":1.0}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}}}},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"54":{"tf":1.7320508075688772},"70":{"tf":1.0},"71":{"tf":1.0}},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"58":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"o":{"b":{"df":0,"docs":{},"j":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"20":{"tf":1.0},"30":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"20":{"tf":1.4142135623730951}},"e":{"c":{"df":0,"docs":{},"t":{"\'":{"df":2,"docs":{"22":{"tf":1.0},"29":{"tf":1.0}}},"(":{"c":{"a":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"(":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"x":{"_":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"57":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":64,"docs":{"10":{"tf":1.7320508075688772},"100":{"tf":1.0},"11":{"tf":1.7320508075688772},"110":{"tf":1.4142135623730951},"114":{"tf":1.0},"115":{"tf":2.449489742783178},"117":{"tf":1.0},"119":{"tf":2.449489742783178},"120":{"tf":1.4142135623730951},"128":{"tf":1.0},"129":{"tf":1.0},"13":{"tf":1.0},"130":{"tf":1.0},"132":{"tf":1.0},"14":{"tf":2.449489742783178},"15":{"tf":1.0},"16":{"tf":1.4142135623730951},"17":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"21":{"tf":1.4142135623730951},"22":{"tf":3.4641016151377544},"23":{"tf":1.7320508075688772},"24":{"tf":1.4142135623730951},"25":{"tf":1.0},"26":{"tf":1.0},"29":{"tf":2.23606797749979},"30":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"34":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.0},"41":{"tf":1.4142135623730951},"42":{"tf":1.4142135623730951},"43":{"tf":1.0},"45":{"tf":2.8284271247461903},"47":{"tf":2.6457513110645907},"48":{"tf":2.449489742783178},"49":{"tf":1.4142135623730951},"5":{"tf":1.7320508075688772},"50":{"tf":1.0},"52":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.4142135623730951},"58":{"tf":1.0},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"9":{"tf":1.0},"99":{"tf":1.0}}}},"df":0,"docs":{}}}},"c":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":4,"docs":{"12":{"tf":1.0},"14":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"2":{"0":{"2":{"1":{"df":1,"docs":{"80":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"0":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"116":{"tf":1.0},"37":{"tf":1.0},"89":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"k":{"(":{"\\"":{"2":{"3":{"4":{"a":{"df":3,"docs":{"21":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{"df":0,"docs":{},"i":{"d":{")":{"?":{".":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"14":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"110":{"tf":1.0}}}},"df":1,"docs":{"65":{"tf":1.4142135623730951}}},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"x":{":":{":":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"a":{"df":0,"docs":{},"s":{"(":{"\\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"111":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{")":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"b":{"df":1,"docs":{"107":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},":":{":":{"<":{"_":{"df":3,"docs":{"18":{"tf":1.0},"58":{"tf":1.0},"99":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"52":{"tf":1.0},"53":{"tf":1.0},"56":{"tf":1.0}}},"l":{"d":{"df":1,"docs":{"88":{"tf":1.0}}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"125":{"tf":1.0},"132":{"tf":1.0},"54":{"tf":1.0}}}}},"n":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"63":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":15,"docs":{"0":{"tf":1.0},"115":{"tf":1.0},"118":{"tf":1.0},"121":{"tf":1.0},"126":{"tf":1.0},"19":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"27":{"tf":1.0},"37":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.0},"80":{"tf":1.0},"82":{"tf":1.0}},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":1,"docs":{"113":{"tf":1.0}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":2.23606797749979}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"91":{"tf":2.0}}}}}}}}}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"82":{"tf":1.0}},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"82":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":2,"docs":{"113":{"tf":1.0},"128":{"tf":1.0}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}}}},"t":{"df":1,"docs":{"115":{"tf":2.23606797749979}},"i":{"df":0,"docs":{},"m":{"df":5,"docs":{"109":{"tf":1.7320508075688772},"110":{"tf":1.4142135623730951},"111":{"tf":1.4142135623730951},"112":{"tf":1.0},"20":{"tf":1.0}}},"o":{"df":0,"docs":{},"n":{"<":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"3":{"2":{"df":1,"docs":{"58":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"130":{"tf":1.0}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":4,"docs":{"111":{"tf":1.0},"18":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"87":{"tf":1.0}}}}},"t":{"df":5,"docs":{"25":{"tf":1.0},"26":{"tf":1.0},"34":{"tf":1.7320508075688772},"43":{"tf":1.4142135623730951},"5":{"tf":1.0}}},"u":{"3":{"2":{"df":1,"docs":{"129":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"6":{"4":{"df":1,"docs":{"115":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"26":{"tf":1.0}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}},"e":{"d":{"2":{"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"26":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"t":{"df":1,"docs":{"26":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":6,"docs":{"129":{"tf":1.4142135623730951},"14":{"tf":1.0},"25":{"tf":1.0},"33":{"tf":1.0},"45":{"tf":1.0},"5":{"tf":1.4142135623730951}}}}}}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"129":{"tf":1.0},"130":{"tf":1.7320508075688772},"28":{"tf":1.0},"76":{"tf":1.0}}}}},"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"121":{"tf":2.0}},"i":{"df":0,"docs":{},"z":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"\'":{"df":1,"docs":{"89":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"74":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"25":{"tf":1.0},"28":{"tf":1.0}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"12":{"tf":1.0}}}}}}}}},"u":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"128":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":4,"docs":{"116":{"tf":1.0},"32":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0}},"g":{"df":0,"docs":{},"o":{"df":1,"docs":{"75":{"tf":1.0}}}},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"128":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":10,"docs":{"107":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"132":{"tf":1.0},"24":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.0},"7":{"tf":1.4142135623730951},"78":{"tf":1.0},"86":{"tf":1.0},"98":{"tf":1.4142135623730951}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":3,"docs":{"34":{"tf":1.4142135623730951},"43":{"tf":1.7320508075688772},"44":{"tf":1.4142135623730951}},"e":{">":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"(":{")":{")":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"44":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"126":{"tf":1.0},"63":{"tf":1.0},"72":{"tf":1.0}},"r":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"121":{"tf":1.0},"126":{"tf":1.7320508075688772}},"e":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"126":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"19":{"tf":1.0},"65":{"tf":1.0}}}}}}}}},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"126":{"tf":1.0}}}}}}}}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":1,"docs":{"58":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"m":{"df":3,"docs":{"115":{"tf":1.4142135623730951},"34":{"tf":1.0},"43":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":10,"docs":{"114":{"tf":1.0},"25":{"tf":1.0},"34":{"tf":1.0},"39":{"tf":1.7320508075688772},"43":{"tf":1.0},"49":{"tf":1.4142135623730951},"53":{"tf":2.0},"54":{"tf":1.0},"57":{"tf":2.0},"72":{"tf":1.0}}}},"s":{"(":{"df":0,"docs":{},"i":{"3":{"2":{"df":1,"docs":{"34":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"34":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951}}}}}}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"34":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"32":{"tf":1.0},"36":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"28":{"tf":1.4142135623730951}}}}},"s":{"df":6,"docs":{"107":{"tf":1.4142135623730951},"21":{"tf":1.7320508075688772},"62":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.0},"80":{"tf":1.0}},"e":{"(":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":2,"docs":{"107":{"tf":1.0},"24":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"80":{"tf":2.0}}}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":5,"docs":{"21":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"63":{"tf":1.0},"66":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"21":{"tf":1.0},"62":{"tf":1.0},"66":{"tf":1.4142135623730951}}}}}}}}}}}},"t":{"df":2,"docs":{"35":{"tf":1.0},"37":{"tf":1.0}},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"72":{"tf":1.0}},"e":{"df":0,"docs":{},"q":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"52":{"tf":1.4142135623730951},"73":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"121":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"s":{"df":5,"docs":{"103":{"tf":1.0},"129":{"tf":1.4142135623730951},"34":{"tf":1.0},"43":{"tf":1.0},"53":{"tf":1.0}},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"35":{"tf":1.0},"48":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0}}}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"104":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"49":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"0":{"tf":1.0},"118":{"tf":1.0},"20":{"tf":1.4142135623730951},"57":{"tf":1.0},"67":{"tf":1.4142135623730951},"87":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"87":{"tf":2.0}}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"h":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":2,"docs":{"30":{"tf":1.0},"72":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"66":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"l":{"a":{"c":{"df":0,"docs":{},"e":{"df":3,"docs":{"129":{"tf":1.0},"21":{"tf":1.0},"63":{"tf":1.0}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"89":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"95":{"tf":1.0}}}}}},"{":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"96":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":5,"docs":{"93":{"tf":1.4142135623730951},"94":{"tf":1.7320508075688772},"95":{"tf":1.4142135623730951},"96":{"tf":1.4142135623730951},"97":{"tf":1.0}}}},"o":{"df":0,"docs":{},"l":{"df":6,"docs":{"110":{"tf":1.0},"111":{"tf":1.0},"112":{"tf":1.0},"14":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":2,"docs":{"129":{"tf":1.4142135623730951},"130":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"123":{"tf":1.7320508075688772},"124":{"tf":1.4142135623730951},"125":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"42":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":1,"docs":{"69":{"tf":2.449489742783178}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"112":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"s":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"113":{"tf":1.0},"37":{"tf":1.0}}}}}}},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"68":{"tf":1.4142135623730951}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"78":{"tf":1.0},"79":{"tf":2.449489742783178}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":2,"docs":{"19":{"tf":1.0},"65":{"tf":1.0}}}}}}},"i":{"c":{"df":0,"docs":{},"e":{"df":1,"docs":{"129":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"120":{"tf":2.23606797749979}}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"50":{"tf":1.0}},"l":{"df":0,"docs":{},"n":{"df":1,"docs":{"50":{"tf":1.0}}}}}},"v":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"57":{"tf":2.0}}}},"df":0,"docs":{}}},"o":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}},"c":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"0":{"tf":1.0},"22":{"tf":1.0}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":7,"docs":{"15":{"tf":1.0},"2":{"tf":1.0},"20":{"tf":1.0},"28":{"tf":1.0},"76":{"tf":1.0},"77":{"tf":1.7320508075688772},"78":{"tf":1.4142135623730951}}}}}},"d":{"df":0,"docs":{},"u":{"c":{"df":1,"docs":{"115":{"tf":1.0}},"t":{"\\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"128":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":7,"docs":{"121":{"tf":2.23606797749979},"126":{"tf":2.23606797749979},"127":{"tf":1.4142135623730951},"128":{"tf":2.6457513110645907},"129":{"tf":2.449489742783178},"130":{"tf":1.7320508075688772},"57":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"88":{"tf":1.0}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"i":{"d":{"df":21,"docs":{"113":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.7320508075688772},"128":{"tf":2.23606797749979},"129":{"tf":1.4142135623730951},"28":{"tf":1.7320508075688772},"48":{"tf":1.0},"5":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"61":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"63":{"tf":1.0},"66":{"tf":1.4142135623730951},"67":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"89":{"tf":1.0},"91":{"tf":1.0},"98":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"128":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"u":{"b":{"df":15,"docs":{"13":{"tf":1.7320508075688772},"130":{"tf":1.0},"14":{"tf":1.0},"18":{"tf":1.0},"26":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.7320508075688772},"32":{"tf":2.449489742783178},"34":{"tf":2.0},"35":{"tf":1.0},"36":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772},"56":{"tf":1.0},"63":{"tf":1.4142135623730951},"87":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"57":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":7,"docs":{"129":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"68":{"tf":1.0},"76":{"tf":1.0},"78":{"tf":1.0}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":68,"docs":{"0":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"109":{"tf":2.23606797749979},"110":{"tf":2.449489742783178},"111":{"tf":2.23606797749979},"112":{"tf":1.0},"114":{"tf":1.4142135623730951},"117":{"tf":1.4142135623730951},"118":{"tf":1.0},"119":{"tf":1.7320508075688772},"120":{"tf":1.7320508075688772},"128":{"tf":1.7320508075688772},"129":{"tf":1.4142135623730951},"130":{"tf":1.7320508075688772},"14":{"tf":1.7320508075688772},"15":{"tf":1.0},"16":{"tf":2.0},"17":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":2.6457513110645907},"21":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772},"23":{"tf":1.0},"24":{"tf":2.0},"30":{"tf":2.0},"37":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.7320508075688772},"47":{"tf":2.6457513110645907},"48":{"tf":1.0},"5":{"tf":2.0},"50":{"tf":1.4142135623730951},"52":{"tf":1.0},"53":{"tf":1.4142135623730951},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"57":{"tf":2.449489742783178},"58":{"tf":2.0},"6":{"tf":2.449489742783178},"61":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"64":{"tf":1.4142135623730951},"65":{"tf":1.4142135623730951},"67":{"tf":1.7320508075688772},"68":{"tf":2.23606797749979},"69":{"tf":2.449489742783178},"7":{"tf":1.4142135623730951},"70":{"tf":2.8284271247461903},"71":{"tf":3.0},"72":{"tf":2.0},"75":{"tf":1.0},"77":{"tf":1.7320508075688772},"78":{"tf":1.0},"80":{"tf":2.449489742783178},"81":{"tf":1.7320508075688772},"82":{"tf":2.23606797749979},"86":{"tf":1.0},"87":{"tf":3.1622776601683795},"88":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"98":{"tf":1.4142135623730951},"99":{"tf":1.0}}},"y":{"(":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.0}}}}}}}}}}}},":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":2,"docs":{"22":{"tf":1.0},"23":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":2,"docs":{"57":{"tf":1.0},"67":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":2,"docs":{"100":{"tf":1.4142135623730951},"99":{"tf":1.4142135623730951}}}}}}}}},"i":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"118":{"tf":1.0},"62":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":7,"docs":{"3":{"tf":1.7320508075688772},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.0},"7":{"tf":1.0},"8":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"93":{"tf":1.0}}}},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"u":{"df":3,"docs":{"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"33":{"tf":1.0}}}}},"df":0,"docs":{},"w":{"df":1,"docs":{"80":{"tf":1.0}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":2,"docs":{"116":{"tf":1.0},"20":{"tf":1.0}},"m":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"m":{"d":{"df":1,"docs":{"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"63":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":1,"docs":{"0":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":2,"docs":{"114":{"tf":1.0},"78":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"d":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"df":0,"docs":{},"u":{"c":{"df":1,"docs":{"87":{"tf":1.0}}},"df":0,"docs":{}}},"df":1,"docs":{"6":{"tf":1.0}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"118":{"tf":1.4142135623730951},"121":{"tf":1.0},"87":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"c":{"df":2,"docs":{"30":{"tf":1.7320508075688772},"45":{"tf":1.0}}},"df":0,"docs":{}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"=":{"df":0,"docs":{},"r":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":1,"docs":{"54":{"tf":1.0}}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"111":{"tf":1.0},"114":{"tf":1.0},"30":{"tf":2.0},"45":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{":":{":":{"<":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"a":{"c":{"df":1,"docs":{"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"i":{"6":{"4":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":1,"docs":{"48":{"tf":1.0}},"i":{"df":1,"docs":{"30":{"tf":1.4142135623730951}}}}}}}},"l":{"a":{"df":0,"docs":{},"t":{"df":3,"docs":{"130":{"tf":1.0},"15":{"tf":1.0},"69":{"tf":1.4142135623730951}},"e":{"d":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":2.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"y":{"\'":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.7320508075688772}}}},"df":0,"docs":{},"v":{"df":1,"docs":{"115":{"tf":1.0}}}},"i":{"df":1,"docs":{"57":{"tf":1.0}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":2.6457513110645907}},"e":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{":":{":":{"a":{"df":1,"docs":{"28":{"tf":1.0}}},"b":{"df":1,"docs":{"28":{"tf":1.0}}},"c":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"28":{"tf":1.7320508075688772}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"28":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":1.4142135623730951}}}}}}}},"v":{"df":1,"docs":{"31":{"tf":1.0}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":3,"docs":{"108":{"tf":1.4142135623730951},"27":{"tf":1.0},"33":{"tf":1.0}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"24":{"tf":1.0}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"109":{"tf":1.0}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}},"o":{"df":1,"docs":{"8":{"tf":1.0}},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"2":{"tf":1.0},"88":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":7,"docs":{"8":{"tf":1.4142135623730951},"86":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"90":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0}}}}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":2,"docs":{"37":{"tf":1.0},"72":{"tf":1.0}}}}}},"q":{"df":1,"docs":{"104":{"tf":1.0}},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"78":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":14,"docs":{"103":{"tf":1.7320508075688772},"111":{"tf":1.0},"119":{"tf":1.0},"16":{"tf":1.0},"18":{"tf":2.23606797749979},"20":{"tf":1.0},"75":{"tf":2.0},"78":{"tf":2.449489742783178},"79":{"tf":2.0},"84":{"tf":1.4142135623730951},"87":{"tf":1.0},"95":{"tf":1.4142135623730951},"98":{"tf":1.4142135623730951},"99":{"tf":1.7320508075688772}}}}},"i":{"df":0,"docs":{},"r":{"df":10,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"119":{"tf":1.0},"127":{"tf":1.4142135623730951},"129":{"tf":3.1622776601683795},"130":{"tf":2.0},"23":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.4142135623730951},"5":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"129":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"59":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"110":{"tf":1.4142135623730951},"29":{"tf":1.0}}}},"v":{"df":25,"docs":{"11":{"tf":2.449489742783178},"110":{"tf":1.7320508075688772},"114":{"tf":1.4142135623730951},"117":{"tf":1.0},"118":{"tf":1.7320508075688772},"119":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.0},"122":{"tf":1.0},"129":{"tf":1.4142135623730951},"130":{"tf":1.4142135623730951},"14":{"tf":1.7320508075688772},"16":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.0},"29":{"tf":1.4142135623730951},"34":{"tf":1.0},"43":{"tf":1.0},"47":{"tf":1.0},"49":{"tf":1.0},"52":{"tf":1.0},"57":{"tf":2.0},"61":{"tf":1.0},"82":{"tf":1.7320508075688772},"83":{"tf":2.6457513110645907}},"e":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"114":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"114":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"<":{"\'":{"_":{"df":1,"docs":{"114":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"<":{"\'":{"_":{"df":1,"docs":{"83":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"p":{"df":1,"docs":{"99":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"75":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":10,"docs":{"126":{"tf":1.0},"18":{"tf":1.0},"61":{"tf":1.0},"65":{"tf":1.0},"69":{"tf":1.4142135623730951},"75":{"tf":1.0},"78":{"tf":1.7320508075688772},"82":{"tf":1.0},"84":{"tf":1.4142135623730951},"86":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"21":{"tf":1.0},"32":{"tf":1.0},"59":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"&":{"\'":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":1,"docs":{"16":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"48":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"<":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"112":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"u":{"6":{"4":{"df":1,"docs":{"111":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"112":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"i":{"3":{"2":{"df":9,"docs":{"21":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"21":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"47":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":4,"docs":{"110":{"tf":1.0},"111":{"tf":1.0},"14":{"tf":1.0},"48":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"49":{"tf":1.0}}}},"df":0,"docs":{}}}}},"t":{"df":1,"docs":{"21":{"tf":1.0}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"81":{"tf":1.0}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":24,"docs":{"111":{"tf":1.0},"119":{"tf":1.0},"120":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"14":{"tf":1.4142135623730951},"21":{"tf":1.0},"39":{"tf":1.0},"52":{"tf":1.4142135623730951},"53":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":2.23606797749979},"64":{"tf":1.0},"67":{"tf":1.4142135623730951},"7":{"tf":1.4142135623730951},"78":{"tf":1.7320508075688772},"79":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951},"83":{"tf":1.4142135623730951},"99":{"tf":1.0}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":2,"docs":{"64":{"tf":1.4142135623730951},"65":{"tf":1.0}}}}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":17,"docs":{"11":{"tf":1.0},"110":{"tf":1.0},"114":{"tf":1.0},"14":{"tf":2.0},"16":{"tf":1.0},"21":{"tf":2.0},"37":{"tf":1.0},"39":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.4142135623730951},"52":{"tf":1.0},"61":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.4142135623730951},"99":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"s":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"42":{"tf":1.0}}}},"df":0,"docs":{}},"df":2,"docs":{"24":{"tf":1.0},"34":{"tf":1.0}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"121":{"tf":2.0}}}}}}},"f":{"c":{"2":{"8":{"2":{"2":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":1,"docs":{"68":{"tf":1.4142135623730951}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"93":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"52":{"tf":3.1622776601683795}},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"d":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{":":{":":{"a":{"d":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{")":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"d":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"52":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"52":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":1,"docs":{"52":{"tf":2.449489742783178}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"t":{"df":3,"docs":{"47":{"tf":1.7320508075688772},"48":{"tf":2.0},"49":{"tf":1.4142135623730951}}}},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"18":{"tf":1.0},"96":{"tf":1.0}},"e":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":4,"docs":{"118":{"tf":1.0},"119":{"tf":1.0},"120":{"tf":1.0},"128":{"tf":1.4142135623730951}}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":4,"docs":{"11":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"81":{"tf":1.0}}}},"n":{"df":4,"docs":{"75":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.4142135623730951},"8":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"12":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"\'":{"df":3,"docs":{"0":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0}}},"df":9,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.4142135623730951},"128":{"tf":1.0},"129":{"tf":1.4142135623730951},"22":{"tf":1.0},"5":{"tf":1.0},"66":{"tf":1.0},"9":{"tf":1.0},"93":{"tf":1.0}},"f":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}}}}}},"s":{"3":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"e":{"df":14,"docs":{"11":{"tf":1.0},"110":{"tf":1.0},"112":{"tf":1.0},"118":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"24":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"36":{"tf":1.0},"57":{"tf":1.0},"76":{"tf":1.0},"84":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"87":{"tf":1.0}}}}},"c":{"a":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"!":{"(":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"108":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}},"e":{"d":{"2":{"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":4,"docs":{"107":{"tf":2.8284271247461903},"108":{"tf":2.449489742783178},"24":{"tf":1.4142135623730951},"5":{"tf":1.0}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"107":{"tf":1.0},"24":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"29":{"tf":1.0}}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}},"e":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"99":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"s":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"50":{"tf":1.0}}}},"df":0,"docs":{}}},":":{":":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"(":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":7,"docs":{"114":{"tf":1.0},"117":{"tf":1.0},"30":{"tf":1.0},"50":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0}}},"y":{":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":6,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"111":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"6":{"tf":1.0},"98":{"tf":1.0}}}}},"df":0,"docs":{}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"6":{"tf":1.0}}},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":2,"docs":{"100":{"tf":1.0},"99":{"tf":1.0}}}}}}}}}}}},"df":2,"docs":{"22":{"tf":1.0},"23":{"tf":1.0}}}}},"s":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"50":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"<":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"18":{"tf":1.0},"30":{"tf":1.0}}},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"99":{"tf":1.0}}}}}}}}}}}},"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"117":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":44,"docs":{"0":{"tf":1.0},"100":{"tf":1.0},"103":{"tf":2.0},"104":{"tf":1.4142135623730951},"111":{"tf":1.4142135623730951},"114":{"tf":1.4142135623730951},"115":{"tf":1.0},"117":{"tf":1.7320508075688772},"118":{"tf":1.0},"119":{"tf":1.0},"120":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.0},"128":{"tf":1.4142135623730951},"131":{"tf":1.0},"132":{"tf":2.0},"14":{"tf":1.0},"15":{"tf":1.0},"17":{"tf":2.0},"18":{"tf":1.7320508075688772},"22":{"tf":1.0},"23":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.7320508075688772},"34":{"tf":1.0},"43":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":2.6457513110645907},"46":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.7320508075688772},"50":{"tf":2.23606797749979},"6":{"tf":1.4142135623730951},"67":{"tf":1.7320508075688772},"69":{"tf":1.0},"70":{"tf":1.4142135623730951},"71":{"tf":1.4142135623730951},"89":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.0}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":2,"docs":{"111":{"tf":1.0},"57":{"tf":2.0}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"121":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"50":{"tf":2.23606797749979}}}},"df":2,"docs":{"29":{"tf":2.23606797749979},"31":{"tf":2.0}},"e":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":2,"docs":{"20":{"tf":1.0},"37":{"tf":1.0}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"14":{"tf":1.0},"49":{"tf":1.0},"57":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"21":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"89":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":9,"docs":{"10":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"8":{"tf":1.0}},"n":{"df":2,"docs":{"21":{"tf":1.0},"76":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":5,"docs":{"110":{"tf":4.47213595499958},"111":{"tf":1.4142135623730951},"115":{"tf":1.0},"129":{"tf":1.4142135623730951},"20":{"tf":1.7320508075688772}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"20":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"f":{")":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"63":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},".":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"53":{"tf":1.0}}}}},"df":1,"docs":{"11":{"tf":1.0}}},"b":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"53":{"tf":1.0},"56":{"tf":1.0}}}},"df":0,"docs":{}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"u":{"df":2,"docs":{"29":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"z":{"df":1,"docs":{"129":{"tf":1.0}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"114":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"129":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"i":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"29":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},":":{":":{"a":{"df":1,"docs":{"28":{"tf":1.0}}},"b":{"df":1,"docs":{"28":{"tf":1.0}}},"c":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"111":{"tf":1.0},"112":{"tf":1.4142135623730951}}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":2,"docs":{"111":{"tf":1.0},"112":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":16,"docs":{"129":{"tf":1.0},"14":{"tf":1.4142135623730951},"16":{"tf":1.0},"24":{"tf":1.0},"26":{"tf":1.0},"28":{"tf":1.0},"52":{"tf":2.0},"53":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"63":{"tf":1.0},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0}}}},"n":{"d":{"df":3,"docs":{"16":{"tf":1.0},"78":{"tf":1.0},"87":{"tf":1.4142135623730951}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.7320508075688772}}}}},"t":{"df":1,"docs":{"14":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":5,"docs":{"121":{"tf":1.0},"130":{"tf":1.0},"23":{"tf":1.0},"34":{"tf":1.0},"43":{"tf":1.0}}}},"df":0,"docs":{}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"48":{"tf":1.0}}}}}}}},"r":{"d":{"df":2,"docs":{"108":{"tf":1.0},"26":{"tf":1.0}},"e":{":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"108":{"tf":1.0}}}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"108":{"tf":1.0}}}}}},"{":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"108":{"tf":1.0},"26":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{"&":{"df":0,"docs":{},"r":{"df":1,"docs":{"7":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"82":{"tf":1.0}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"113":{"tf":1.0},"117":{"tf":1.0},"8":{"tf":1.4142135623730951},"85":{"tf":1.0},"93":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"80":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":2,"docs":{"114":{"tf":1.4142135623730951},"83":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}}}}}},"i":{"c":{"df":4,"docs":{"116":{"tf":1.0},"59":{"tf":1.0},"68":{"tf":1.0},"89":{"tf":1.0}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"57":{"tf":1.0}}}}}}},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"87":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"37":{"tf":1.0},"59":{"tf":1.0}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"1":{"tf":1.0},"37":{"tf":1.0},"75":{"tf":1.0},"77":{"tf":1.0},"93":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":2,"docs":{"29":{"tf":2.0},"31":{"tf":1.7320508075688772}}}},"r":{"df":0,"docs":{},"e":{"a":{"b":{"df":0,"docs":{},"l":{"df":4,"docs":{"122":{"tf":1.7320508075688772},"123":{"tf":2.0},"124":{"tf":1.7320508075688772},"125":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":1,"docs":{"125":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"129":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"129":{"tf":1.7320508075688772},"130":{"tf":1.0}}}}}}}}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":2.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"29":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"w":{"df":4,"docs":{"14":{"tf":1.0},"16":{"tf":1.0},"21":{"tf":1.0},"57":{"tf":1.0}},"n":{"df":2,"docs":{"34":{"tf":1.0},"43":{"tf":1.0}}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"48":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"48":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":4,"docs":{"31":{"tf":1.4142135623730951},"33":{"tf":1.0},"47":{"tf":1.0},"93":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":7,"docs":{"110":{"tf":1.0},"115":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"90":{"tf":1.0},"92":{"tf":1.0},"93":{"tf":1.0}},"e":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":11,"docs":{"10":{"tf":2.0},"11":{"tf":1.7320508075688772},"12":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"18":{"tf":1.0},"33":{"tf":1.4142135623730951},"43":{"tf":1.0},"52":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"115":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"i":{"df":2,"docs":{"108":{"tf":1.0},"11":{"tf":1.0}},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"111":{"tf":1.0}}}}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":2,"docs":{"116":{"tf":1.0},"120":{"tf":1.0}}}}},"z":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"129":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":5,"docs":{"129":{"tf":3.1622776601683795},"130":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.0},"87":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"113":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"49":{"tf":1.0}}}}}}}}},"n":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"_":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"111":{"tf":1.0}}}},"v":{"df":2,"docs":{"1":{"tf":1.0},"130":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"52":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"3":{"df":1,"docs":{"115":{"tf":1.4142135623730951}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}}}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"130":{"tf":1.0}}},"df":0,"docs":{}}}},"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"115":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"115":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}}},"t":{"df":1,"docs":{"34":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"43":{"tf":1.4142135623730951}}}}}}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"43":{"tf":1.4142135623730951}}}}}}}}},"t":{"df":1,"docs":{"43":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"<":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"34":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"34":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"34":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"34":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772}}}}}}}}}},"t":{"df":0,"docs":{},"h":{"df":4,"docs":{"125":{"tf":1.0},"126":{"tf":1.0},"129":{"tf":1.0},"18":{"tf":1.0}}},"i":{"df":0,"docs":{},"m":{"df":6,"docs":{"11":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"20":{"tf":1.0},"24":{"tf":1.0},"53":{"tf":1.0}}}},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"34":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772}}}}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"75":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"74":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"111":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"c":{"df":4,"docs":{"37":{"tf":1.0},"59":{"tf":1.0},"80":{"tf":1.4142135623730951},"88":{"tf":1.0}},"i":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"37":{"tf":1.0},"66":{"tf":1.7320508075688772},"72":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":3,"docs":{"0":{"tf":1.0},"58":{"tf":1.7320508075688772},"89":{"tf":1.0}},"i":{"df":4,"docs":{"34":{"tf":1.0},"42":{"tf":1.0},"49":{"tf":1.0},"57":{"tf":1.0}}}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}}}},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"111":{"tf":1.0}},"x":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"112":{"tf":1.0}}}}}}},"p":{"df":0,"docs":{},"g":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"111":{"tf":1.0},"112":{"tf":1.0}}}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"a":{"df":0,"docs":{},"s":{"(":{"\\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"110":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"29":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}},"e":{"\\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"66":{"tf":1.4142135623730951},"80":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":1,"docs":{"27":{"tf":1.0}},"t":{".":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{")":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{")":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"n":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":4,"docs":{"117":{"tf":1.0},"58":{"tf":2.0},"76":{"tf":1.0},"82":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"16":{"tf":1.0}}},"i":{"c":{"df":1,"docs":{"87":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{":":{":":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"44":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"108":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"n":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"100":{"tf":1.0},"18":{"tf":1.0},"99":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"f":{"3":{"2":{":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{":":{":":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"y":{"df":4,"docs":{"21":{"tf":1.0},"61":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"21":{"tf":1.0},"62":{"tf":1.0},"66":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":1,"docs":{"21":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{":":{":":{"a":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"111":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{":":{":":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"49":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":10,"docs":{"28":{"tf":1.0},"49":{"tf":2.0},"67":{"tf":1.0},"75":{"tf":1.4142135623730951},"76":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"129":{"tf":1.0},"37":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"16":{"tf":1.4142135623730951},"67":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":1,"docs":{"87":{"tf":1.4142135623730951}}}}}}},"df":11,"docs":{"100":{"tf":1.0},"114":{"tf":1.0},"19":{"tf":1.0},"30":{"tf":1.0},"44":{"tf":1.0},"6":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"80":{"tf":1.0},"99":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"m":{"<":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":4,"docs":{"100":{"tf":1.0},"21":{"tf":1.0},"23":{"tf":1.4142135623730951},"49":{"tf":1.0}}}}}}},"df":2,"docs":{"49":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"100":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"(":{"\\"":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"128":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"b":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"44":{"tf":1.0}}}},"df":0,"docs":{}},"df":31,"docs":{"107":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.4142135623730951},"114":{"tf":2.0},"115":{"tf":2.23606797749979},"117":{"tf":1.0},"119":{"tf":1.4142135623730951},"127":{"tf":1.4142135623730951},"128":{"tf":1.4142135623730951},"129":{"tf":1.4142135623730951},"13":{"tf":2.449489742783178},"131":{"tf":1.7320508075688772},"14":{"tf":1.7320508075688772},"16":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"21":{"tf":1.4142135623730951},"24":{"tf":1.7320508075688772},"29":{"tf":1.7320508075688772},"30":{"tf":2.0},"34":{"tf":2.23606797749979},"35":{"tf":1.4142135623730951},"36":{"tf":2.449489742783178},"37":{"tf":2.0},"43":{"tf":2.0},"44":{"tf":1.4142135623730951},"47":{"tf":1.0},"48":{"tf":2.0},"54":{"tf":2.0},"69":{"tf":1.4142135623730951},"87":{"tf":2.0}},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"b":{"df":1,"docs":{"107":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"i":{"6":{"4":{"df":1,"docs":{"107":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":87,"docs":{"10":{"tf":1.4142135623730951},"100":{"tf":1.4142135623730951},"103":{"tf":1.0},"104":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.4142135623730951},"112":{"tf":1.0},"114":{"tf":1.7320508075688772},"115":{"tf":1.0},"117":{"tf":1.0},"118":{"tf":1.0},"119":{"tf":1.4142135623730951},"12":{"tf":1.0},"120":{"tf":2.0},"121":{"tf":2.0},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.4142135623730951},"129":{"tf":1.7320508075688772},"13":{"tf":1.7320508075688772},"130":{"tf":1.4142135623730951},"131":{"tf":1.0},"14":{"tf":2.0},"16":{"tf":1.0},"17":{"tf":2.0},"18":{"tf":1.4142135623730951},"19":{"tf":1.0},"20":{"tf":1.7320508075688772},"21":{"tf":1.4142135623730951},"22":{"tf":2.23606797749979},"23":{"tf":2.0},"24":{"tf":1.7320508075688772},"26":{"tf":1.7320508075688772},"29":{"tf":1.4142135623730951},"30":{"tf":2.0},"31":{"tf":1.4142135623730951},"32":{"tf":2.449489742783178},"33":{"tf":1.7320508075688772},"34":{"tf":2.8284271247461903},"35":{"tf":1.0},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772},"39":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"43":{"tf":2.6457513110645907},"44":{"tf":1.0},"47":{"tf":1.4142135623730951},"48":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.0},"50":{"tf":1.0},"52":{"tf":1.7320508075688772},"53":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.4142135623730951},"57":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.4142135623730951},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"9":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"11":{"tf":1.0},"25":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}},"u":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":2,"docs":{"88":{"tf":1.4142135623730951},"89":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}},"u":{"b":{"df":1,"docs":{"8":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}}}}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":10,"docs":{"116":{"tf":1.0},"118":{"tf":1.0},"121":{"tf":2.23606797749979},"122":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":2.0},"127":{"tf":1.7320508075688772},"128":{"tf":1.0},"129":{"tf":2.0},"130":{"tf":1.0}}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"8":{"tf":1.0}}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"20":{"tf":1.4142135623730951},"72":{"tf":1.0}}}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"49":{"tf":1.0},"84":{"tf":2.0}},"e":{"<":{"\'":{"df":1,"docs":{"84":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":14,"docs":{"100":{"tf":1.7320508075688772},"104":{"tf":1.4142135623730951},"21":{"tf":1.7320508075688772},"22":{"tf":1.7320508075688772},"23":{"tf":2.6457513110645907},"45":{"tf":1.4142135623730951},"49":{"tf":2.6457513110645907},"5":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.0},"78":{"tf":1.0},"84":{"tf":1.0},"96":{"tf":1.4142135623730951},"98":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"1":{"df":1,"docs":{"23":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"1":{"df":1,"docs":{"23":{"tf":1.4142135623730951}}},"2":{"df":1,"docs":{"23":{"tf":1.7320508075688772}}},":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"23":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"100":{"tf":1.7320508075688772}}}}}}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"19":{"tf":1.0}}}},"t":{"df":1,"docs":{"31":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"h":{"df":3,"docs":{"121":{"tf":1.0},"5":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":1,"docs":{"5":{"tf":1.4142135623730951}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":4,"docs":{"116":{"tf":1.0},"125":{"tf":1.0},"130":{"tf":1.0},"132":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"75":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":8,"docs":{"1":{"tf":1.0},"107":{"tf":1.0},"114":{"tf":1.0},"115":{"tf":1.0},"117":{"tf":2.0},"32":{"tf":1.0},"37":{"tf":1.0},"93":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"129":{"tf":1.0},"22":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"\\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"115":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"115":{"tf":1.0}}}}},"df":0,"docs":{}},"n":{"c":{"df":3,"docs":{"16":{"tf":1.0},"28":{"tf":1.0},"87":{"tf":1.0}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"x":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":39,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"113":{"tf":1.4142135623730951},"115":{"tf":2.0},"12":{"tf":1.0},"13":{"tf":1.0},"132":{"tf":2.0},"14":{"tf":1.0},"15":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"26":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"44":{"tf":1.0},"9":{"tf":1.7320508075688772}}}}}}}},"t":{"a":{"df":0,"docs":{},"g":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"131":{"tf":1.0}}}},"df":0,"docs":{}}},"df":1,"docs":{"131":{"tf":2.0}}},"k":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":4,"docs":{"109":{"tf":1.0},"11":{"tf":1.0},"126":{"tf":1.4142135623730951},"128":{"tf":1.0}},"n":{"df":1,"docs":{"57":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"k":{"df":1,"docs":{"111":{"tf":1.0}}}}},"df":2,"docs":{"25":{"tf":1.0},"44":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"131":{"tf":1.4142135623730951},"89":{"tf":1.0}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":2,"docs":{"28":{"tf":1.0},"62":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"80":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{"1":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"40":{"tf":1.0}}},"2":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"40":{"tf":1.0}}},"3":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"40":{"tf":1.0}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"115":{"tf":2.23606797749979}}}}},"df":0,"docs":{}}},":":{":":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"(":{"\\"":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"115":{"tf":1.0}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"115":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":1,"docs":{"26":{"tf":1.0}}}},"df":0,"docs":{}}}},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"69":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"29":{"tf":1.0},"37":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"76":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"63":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"63":{"tf":1.4142135623730951}}}}}}}}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":3,"docs":{"129":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":2,"docs":{"118":{"tf":1.0},"129":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":2,"docs":{"119":{"tf":1.0},"45":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":3,"docs":{"63":{"tf":1.0},"66":{"tf":1.0},"75":{"tf":1.0}}}}}}}},"i":{"c":{"df":0,"docs":{},"k":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"100":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"4":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":5,"docs":{"1":{"tf":1.0},"110":{"tf":1.0},"121":{"tf":1.0},"24":{"tf":1.0},"57":{"tf":1.0}}}},"p":{"df":1,"docs":{"22":{"tf":1.0}}},"t":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.4142135623730951}}}}},"o":{"_":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"107":{"tf":1.0},"24":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"110":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":18,"docs":{"110":{"tf":2.8284271247461903},"111":{"tf":1.4142135623730951},"112":{"tf":1.4142135623730951},"114":{"tf":1.0},"22":{"tf":2.0},"24":{"tf":1.7320508075688772},"30":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"47":{"tf":1.0},"48":{"tf":1.4142135623730951},"52":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"64":{"tf":1.0},"72":{"tf":1.4142135623730951}},"i":{"d":{"df":1,"docs":{"112":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"121":{"tf":1.0},"29":{"tf":1.0},"65":{"tf":1.0}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"18":{"tf":1.4142135623730951},"48":{"tf":1.0}}}},"i":{"df":0,"docs":{},"o":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"111":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"49":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"49":{"tf":1.0}}}}},"p":{"df":1,"docs":{"32":{"tf":1.0}},"i":{"c":{"df":27,"docs":{"106":{"tf":1.7320508075688772},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.0},"112":{"tf":1.0},"113":{"tf":1.0},"114":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"117":{"tf":1.0},"118":{"tf":1.0},"119":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}}}}}}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"e":{"df":5,"docs":{"67":{"tf":2.23606797749979},"88":{"tf":2.0},"89":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":2.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":11,"docs":{"114":{"tf":1.0},"25":{"tf":1.7320508075688772},"28":{"tf":1.4142135623730951},"42":{"tf":1.0},"44":{"tf":1.7320508075688772},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"66":{"tf":1.4142135623730951},"75":{"tf":1.4142135623730951},"87":{"tf":1.4142135623730951}}}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"29":{"tf":1.0},"32":{"tf":1.0}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":2,"docs":{"30":{"tf":1.0},"45":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":1,"docs":{"76":{"tf":1.0}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"76":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"e":{"df":2,"docs":{"128":{"tf":1.0},"73":{"tf":1.4142135623730951}}}},"y":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"111":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"o":{"df":10,"docs":{"111":{"tf":1.0},"113":{"tf":1.0},"24":{"tf":1.0},"34":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"57":{"tf":1.0},"62":{"tf":1.0},"72":{"tf":1.0},"98":{"tf":1.0}}}},"y":{"df":3,"docs":{"29":{"tf":2.0},"30":{"tf":1.4142135623730951},"40":{"tf":2.449489742783178}},"p":{"df":0,"docs":{},"e":{"\\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"115":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"44":{"tf":1.0}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"115":{"tf":1.7320508075688772},"132":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}},"df":67,"docs":{"0":{"tf":1.0},"10":{"tf":1.0},"107":{"tf":1.7320508075688772},"108":{"tf":1.0},"11":{"tf":1.0},"111":{"tf":1.4142135623730951},"112":{"tf":2.6457513110645907},"113":{"tf":2.23606797749979},"115":{"tf":2.449489742783178},"118":{"tf":1.7320508075688772},"119":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"120":{"tf":1.7320508075688772},"121":{"tf":1.4142135623730951},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.7320508075688772},"125":{"tf":1.4142135623730951},"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.4142135623730951},"129":{"tf":1.7320508075688772},"13":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":2.0},"14":{"tf":1.7320508075688772},"15":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":2.0},"22":{"tf":1.7320508075688772},"23":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":2.23606797749979},"26":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":2.8284271247461903},"29":{"tf":1.4142135623730951},"30":{"tf":2.23606797749979},"31":{"tf":1.0},"32":{"tf":1.7320508075688772},"33":{"tf":1.0},"34":{"tf":2.8284271247461903},"35":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.7320508075688772},"38":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.4142135623730951},"43":{"tf":2.449489742783178},"44":{"tf":2.0},"45":{"tf":1.7320508075688772},"5":{"tf":2.0},"61":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0},"69":{"tf":1.4142135623730951},"72":{"tf":1.7320508075688772},"73":{"tf":1.4142135623730951},"9":{"tf":1.7320508075688772},"99":{"tf":1.0}},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"42":{"tf":1.0},"44":{"tf":2.0}}}},"df":0,"docs":{}}}}}},"u":{"3":{"2":{"df":3,"docs":{"115":{"tf":1.4142135623730951},"125":{"tf":1.7320508075688772},"129":{"tf":2.0}}},"df":0,"docs":{}},"6":{"4":{"df":5,"docs":{"110":{"tf":1.0},"111":{"tf":1.4142135623730951},"121":{"tf":2.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":2,"docs":{"26":{"tf":1.0},"50":{"tf":1.4142135623730951}},"n":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"54":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"20":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"31":{"tf":2.23606797749979},"32":{"tf":3.1622776601683795}}}},"q":{"df":0,"docs":{},"u":{"df":4,"docs":{"22":{"tf":1.0},"23":{"tf":1.0},"42":{"tf":1.0},"87":{"tf":1.4142135623730951}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"11":{"tf":1.0},"32":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"73":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"121":{"tf":1.4142135623730951}}}}}}}},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"129":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"p":{"d":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"8":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":3,"docs":{"118":{"tf":1.4142135623730951},"32":{"tf":1.0},"47":{"tf":1.0}}},"r":{"df":0,"docs":{},"l":{"df":2,"docs":{"132":{"tf":1.0},"54":{"tf":1.4142135623730951}}}},"s":{"df":97,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"10":{"tf":1.0},"100":{"tf":2.0},"103":{"tf":1.7320508075688772},"104":{"tf":1.7320508075688772},"107":{"tf":1.4142135623730951},"108":{"tf":2.449489742783178},"11":{"tf":1.4142135623730951},"111":{"tf":2.0},"112":{"tf":1.0},"113":{"tf":1.0},"114":{"tf":1.7320508075688772},"115":{"tf":2.0},"117":{"tf":1.4142135623730951},"118":{"tf":1.4142135623730951},"119":{"tf":2.23606797749979},"12":{"tf":2.0},"120":{"tf":1.7320508075688772},"121":{"tf":1.7320508075688772},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.4142135623730951},"126":{"tf":1.7320508075688772},"127":{"tf":1.4142135623730951},"128":{"tf":1.4142135623730951},"129":{"tf":2.8284271247461903},"13":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.4142135623730951},"132":{"tf":1.4142135623730951},"14":{"tf":1.7320508075688772},"16":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":2.0},"19":{"tf":2.23606797749979},"20":{"tf":1.4142135623730951},"21":{"tf":1.7320508075688772},"22":{"tf":1.4142135623730951},"23":{"tf":1.7320508075688772},"24":{"tf":1.4142135623730951},"25":{"tf":1.0},"26":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"29":{"tf":1.7320508075688772},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"33":{"tf":2.0},"34":{"tf":2.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.7320508075688772},"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.7320508075688772},"44":{"tf":2.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":2.23606797749979},"5":{"tf":1.4142135623730951},"50":{"tf":1.4142135623730951},"52":{"tf":1.7320508075688772},"53":{"tf":2.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":2.23606797749979},"58":{"tf":1.4142135623730951},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"64":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.4142135623730951},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.4142135623730951},"73":{"tf":1.0},"75":{"tf":1.7320508075688772},"78":{"tf":2.0},"79":{"tf":1.7320508075688772},"80":{"tf":1.7320508075688772},"81":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951},"83":{"tf":1.4142135623730951},"84":{"tf":1.7320508075688772},"87":{"tf":1.4142135623730951},"9":{"tf":1.0},"93":{"tf":1.0},"95":{"tf":1.7320508075688772},"96":{"tf":1.7320508075688772},"98":{"tf":1.4142135623730951},"99":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"r":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"47":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"110":{"tf":1.0},"111":{"tf":1.0}}},"df":0,"docs":{}}},"b":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":18,"docs":{"11":{"tf":1.7320508075688772},"110":{"tf":5.0},"111":{"tf":2.0},"112":{"tf":1.4142135623730951},"119":{"tf":4.123105625617661},"120":{"tf":2.449489742783178},"121":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.4142135623730951},"22":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.7320508075688772},"47":{"tf":1.4142135623730951},"48":{"tf":1.7320508075688772},"57":{"tf":1.0},"61":{"tf":1.0},"73":{"tf":1.0},"78":{"tf":1.4142135623730951}},"i":{"d":{"df":1,"docs":{"112":{"tf":1.0}}},"df":0,"docs":{}},"n":{"a":{"df":0,"docs":{},"m":{"df":4,"docs":{"119":{"tf":2.449489742783178},"35":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"111":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}}}},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"_":{"a":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"33":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"z":{"df":2,"docs":{"44":{"tf":1.0},"72":{"tf":1.4142135623730951}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":4,"docs":{"127":{"tf":1.0},"22":{"tf":1.0},"49":{"tf":1.0},"80":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":23,"docs":{"51":{"tf":1.7320508075688772},"52":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"i":{"d":{"=":{"df":0,"docs":{},"v":{"df":1,"docs":{"54":{"tf":1.0}}}},"df":1,"docs":{"54":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"v":{"2":{"df":1,"docs":{"117":{"tf":1.4142135623730951}}},"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"81":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":8,"docs":{"121":{"tf":1.0},"54":{"tf":2.8284271247461903},"55":{"tf":1.4142135623730951},"56":{"tf":1.7320508075688772},"72":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":2.449489742783178},"89":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":26,"docs":{"0":{"tf":1.0},"10":{"tf":1.4142135623730951},"107":{"tf":2.0},"108":{"tf":1.0},"11":{"tf":1.0},"111":{"tf":1.0},"112":{"tf":1.4142135623730951},"114":{"tf":2.449489742783178},"119":{"tf":1.0},"129":{"tf":1.4142135623730951},"14":{"tf":1.4142135623730951},"16":{"tf":1.0},"24":{"tf":2.0},"38":{"tf":2.23606797749979},"39":{"tf":3.3166247903554},"40":{"tf":2.6457513110645907},"41":{"tf":1.0},"49":{"tf":1.7320508075688772},"53":{"tf":2.449489742783178},"54":{"tf":1.7320508075688772},"55":{"tf":1.0},"56":{"tf":2.23606797749979},"57":{"tf":1.0},"59":{"tf":1.0},"72":{"tf":2.0},"73":{"tf":1.4142135623730951}},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"14":{"tf":1.0},"72":{"tf":1.0}}}}}}},"df":0,"docs":{}},".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"114":{"tf":1.0}}}}},"df":0,"docs":{}},"x":{"df":1,"docs":{"26":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"1":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"57":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":4,"docs":{"26":{"tf":1.0},"41":{"tf":1.0},"52":{"tf":1.0},"57":{"tf":1.4142135623730951}}},"2":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"57":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":4,"docs":{"26":{"tf":1.0},"41":{"tf":1.0},"52":{"tf":1.0},"57":{"tf":1.0}}},"3":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"57":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"41":{"tf":1.0},"57":{"tf":1.0}}},":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"0":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"c":{"2":{"8":{"2":{"2":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{"3":{"3":{"9":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"107":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"114":{"tf":1.4142135623730951}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"107":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"_":{"d":{"b":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}},"e":{"d":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"26":{"tf":1.0}}}}}},"2":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"26":{"tf":1.0}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{".":{"0":{"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"72":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":3,"docs":{"16":{"tf":1.0},"72":{"tf":1.0},"80":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"s":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"80":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}},"df":2,"docs":{"50":{"tf":1.4142135623730951},"54":{"tf":1.4142135623730951}},"e":{"c":{"!":{"[":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"128":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"<":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"128":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"81":{"tf":1.0}}}}}}}}}}}},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"55":{"tf":1.0}}}}},"t":{"df":3,"docs":{"25":{"tf":1.4142135623730951},"44":{"tf":1.0},"5":{"tf":1.0}}},"u":{"df":0,"docs":{},"s":{"df":3,"docs":{"22":{"tf":1.4142135623730951},"33":{"tf":1.0},"37":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"25":{"tf":1.0}}},"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":1,"docs":{"64":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":2,"docs":{"58":{"tf":1.0},"69":{"tf":1.0}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":8,"docs":{"100":{"tf":1.0},"114":{"tf":1.0},"30":{"tf":1.0},"6":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"99":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"17":{"tf":1.0},"54":{"tf":1.4142135623730951}}}}}}}},"i":{"a":{"df":2,"docs":{"119":{"tf":1.0},"28":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"73":{"tf":2.449489742783178}}}},"df":0,"docs":{}}}}},"w":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":14,"docs":{"11":{"tf":1.0},"111":{"tf":1.0},"121":{"tf":1.4142135623730951},"129":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951},"32":{"tf":1.0},"34":{"tf":1.4142135623730951},"37":{"tf":1.0},"63":{"tf":1.0},"73":{"tf":1.0},"76":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0}}}},"r":{"df":1,"docs":{"27":{"tf":1.0}},"p":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":2,"docs":{"100":{"tf":1.0},"99":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"98":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{")":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"(":{"(":{"[":{"0":{"df":2,"docs":{"100":{"tf":1.0},"99":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"{":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":7,"docs":{"100":{"tf":1.4142135623730951},"101":{"tf":1.0},"18":{"tf":1.4142135623730951},"4":{"tf":1.4142135623730951},"93":{"tf":1.4142135623730951},"98":{"tf":2.0},"99":{"tf":1.4142135623730951}}}},"s":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"19":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"y":{"df":8,"docs":{"21":{"tf":1.0},"42":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"72":{"tf":1.0},"74":{"tf":1.0}}}},"df":0,"docs":{},"e":{"\'":{"df":0,"docs":{},"r":{"df":1,"docs":{"129":{"tf":1.0}}}},"b":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"<":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"<":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{")":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"103":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"<":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"103":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"p":{"a":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":8,"docs":{"102":{"tf":1.7320508075688772},"103":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"4":{"tf":1.4142135623730951},"76":{"tf":1.0},"8":{"tf":1.4142135623730951},"93":{"tf":1.7320508075688772}},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"98":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"129":{"tf":2.449489742783178},"130":{"tf":1.0}},"s":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"129":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"129":{"tf":2.23606797749979}}},"df":0,"docs":{},"t":{"df":1,"docs":{"129":{"tf":1.0}}}}}}}}}}}}},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"21":{"tf":1.0},"28":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":1,"docs":{"16":{"tf":1.0}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"82":{"tf":1.4142135623730951}}}}}},"i":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"35":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"113":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"132":{"tf":1.0},"6":{"tf":1.0},"74":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"\'":{"df":0,"docs":{},"t":{"df":2,"docs":{"25":{"tf":1.0},"76":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"d":{"df":1,"docs":{"76":{"tf":1.4142135623730951}}},"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":15,"docs":{"11":{"tf":1.0},"22":{"tf":1.0},"66":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"88":{"tf":1.0},"89":{"tf":1.0}}},"l":{"d":{"df":1,"docs":{"19":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"130":{"tf":1.0},"72":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"n":{"\'":{"df":0,"docs":{},"t":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"28":{"tf":1.4142135623730951}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"25":{"tf":2.0},"29":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":4,"docs":{"11":{"tf":1.4142135623730951},"131":{"tf":1.0},"33":{"tf":1.0},"5":{"tf":1.4142135623730951}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"129":{"tf":1.0}}}}}}},"x":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"26":{"tf":1.0}}}}}}},"df":3,"docs":{"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"125":{"tf":1.4142135623730951}}},"y":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"123":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":3,"docs":{"123":{"tf":1.0},"124":{"tf":1.4142135623730951},"125":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"43":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"\'":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":4,"docs":{"75":{"tf":1.0},"76":{"tf":1.0},"77":{"tf":1.0},"87":{"tf":1.0}}}},"r":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":0,"docs":{},"r":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"18":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"63":{"tf":1.0},"93":{"tf":1.0}}}}}}}}}},"z":{"df":1,"docs":{"125":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"title":{"root":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"102":{"tf":1.0}}}}}},"d":{"d":{"df":1,"docs":{"4":{"tf":1.0}}},"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"106":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"df":0,"docs":{},"z":{"df":1,"docs":{"86":{"tf":1.0}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":5,"docs":{"116":{"tf":1.0},"67":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"89":{"tf":1.0}}}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"85":{"tf":1.0}}}}},"df":0,"docs":{}}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"2":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"12":{"tf":1.0}}}}}},"c":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"57":{"tf":1.0}}}},"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"72":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"65":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"55":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":3,"docs":{"68":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"132":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"120":{"tf":1.0}}},"df":0,"docs":{}}}}}},"n":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"43":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"73":{"tf":1.0}}}},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"15":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"57":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"121":{"tf":1.0}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":5,"docs":{"107":{"tf":1.0},"113":{"tf":1.0},"56":{"tf":1.0},"63":{"tf":1.0},"72":{"tf":1.0}}}}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":5,"docs":{"112":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"35":{"tf":1.0}},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"111":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"108":{"tf":1.0},"11":{"tf":1.0},"120":{"tf":1.0},"75":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"4":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"68":{"tf":1.0},"70":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"24":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"113":{"tf":1.0},"114":{"tf":1.0},"115":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"117":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"118":{"tf":1.0},"121":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"m":{"df":2,"docs":{"27":{"tf":1.0},"28":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"21":{"tf":1.0},"59":{"tf":1.0},"63":{"tf":1.0}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":2,"docs":{"62":{"tf":1.0},"63":{"tf":1.0}}}}}}}}}}}},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":12,"docs":{"100":{"tf":1.0},"101":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"119":{"tf":1.0},"26":{"tf":1.0},"60":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"97":{"tf":1.0},"99":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"114":{"tf":1.0},"6":{"tf":1.0},"82":{"tf":1.0}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"69":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"50":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":5,"docs":{"59":{"tf":1.0},"65":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"85":{"tf":1.0}}}},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"127":{"tf":1.0}}}}}}}},"f":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"116":{"tf":1.0},"117":{"tf":1.0}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":8,"docs":{"123":{"tf":1.0},"13":{"tf":1.0},"24":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"52":{"tf":1.0}}},"df":0,"docs":{}}}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":3,"docs":{"13":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"34":{"tf":1.0},"42":{"tf":1.0},"61":{"tf":1.0}}}}}},"u":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"52":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"h":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"21":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"i":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"112":{"tf":1.0},"63":{"tf":1.0}}}}}}}}}},"n":{"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"125":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"12":{"tf":1.0},"41":{"tf":1.0},"54":{"tf":1.0}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"33":{"tf":1.0},"34":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"43":{"tf":1.0}}}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"df":2,"docs":{"8":{"tf":1.0},"93":{"tf":1.0}}}},"r":{"df":0,"docs":{},"f":{"a":{"c":{"df":3,"docs":{"29":{"tf":1.0},"30":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"7":{"tf":1.0}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":2,"docs":{"118":{"tf":1.0},"120":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"4":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"70":{"tf":1.0},"71":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"55":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"90":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"m":{"a":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":1,"docs":{"108":{"tf":1.0}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"30":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"55":{"tf":1.0}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"g":{"df":2,"docs":{"22":{"tf":1.0},"23":{"tf":1.0}}}}},"i":{"d":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"76":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":3,"docs":{"101":{"tf":1.0},"105":{"tf":1.0},"97":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"112":{"tf":1.0}}}}}}},"t":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"46":{"tf":1.0},"48":{"tf":1.0}}}},"df":0,"docs":{}}}},"n":{"+":{"1":{"df":1,"docs":{"109":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"130":{"tf":1.0},"32":{"tf":1.0}}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":6,"docs":{"14":{"tf":1.0},"22":{"tf":1.0},"39":{"tf":1.0},"41":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"91":{"tf":1.0}}}}}}}}}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"109":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"12":{"tf":1.0},"7":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"126":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"53":{"tf":1.0}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"80":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"87":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"66":{"tf":1.0}}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"94":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"120":{"tf":1.0}}}}},"df":0,"docs":{}}},"o":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"77":{"tf":1.0}}}}}},"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"128":{"tf":1.0}}},"df":0,"docs":{}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":12,"docs":{"109":{"tf":1.0},"110":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.0},"6":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"77":{"tf":1.0},"87":{"tf":1.0}}}}},"i":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"e":{"d":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":5,"docs":{"103":{"tf":1.0},"18":{"tf":1.0},"78":{"tf":1.0},"95":{"tf":1.0},"99":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"r":{"df":2,"docs":{"129":{"tf":1.0},"130":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"110":{"tf":1.0}}}},"v":{"df":2,"docs":{"11":{"tf":1.0},"83":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":1.0}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"64":{"tf":1.0}}}}}}}}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":2,"docs":{"47":{"tf":1.0},"48":{"tf":1.0}}}}}},"s":{"c":{"a":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"107":{"tf":1.0},"108":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":3,"docs":{"17":{"tf":1.0},"45":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}}}}},"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"50":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"20":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"8":{"tf":1.0}}}}}}},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"b":{"df":0,"docs":{},"l":{"df":3,"docs":{"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"10":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"16":{"tf":1.0}}}}},"u":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":1,"docs":{"89":{"tf":1.0}}}}},"df":0,"docs":{}}},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"84":{"tf":1.0}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":5,"docs":{"100":{"tf":1.0},"104":{"tf":1.0},"23":{"tf":1.0},"49":{"tf":1.0},"96":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"117":{"tf":1.0}}}}}}}},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":2,"docs":{"115":{"tf":1.0},"9":{"tf":1.0}}}}}}}},"t":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"131":{"tf":1.0}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"106":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"c":{"df":0,"docs":{},"e":{"df":3,"docs":{"67":{"tf":1.0},"88":{"tf":1.0},"92":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"44":{"tf":1.0}}}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":5,"docs":{"112":{"tf":1.0},"115":{"tf":1.0},"124":{"tf":1.0},"25":{"tf":1.0},"9":{"tf":1.0}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"44":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"31":{"tf":1.0},"32":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"121":{"tf":1.0}}}}}}}}},"s":{"df":3,"docs":{"108":{"tf":1.0},"12":{"tf":1.0},"53":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"11":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"51":{"tf":1.0}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"54":{"tf":1.0},"56":{"tf":1.0},"81":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":3,"docs":{"38":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0}}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":1,"docs":{"98":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"b":{"df":2,"docs":{"102":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"76":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"28":{"tf":1.0}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"25":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"5":{"tf":1.0}}}}}}}}}},"lang":"English","pipeline":["trimmer","stopWordFilter","stemmer"],"ref":"id","version":"0.9.5"},"results_options":{"limit_results":30,"teaser_word_count":30},"search_options":{"bool":"OR","expand":true,"fields":{"body":{"boost":1},"breadcrumbs":{"boost":1},"title":{"boost":2}}}}'); \ No newline at end of file diff --git a/en/subscription.html b/en/subscription.html new file mode 100644 index 000000000..232a6c50b --- /dev/null +++ b/en/subscription.html @@ -0,0 +1,247 @@ + + + + + + Subscription - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Subscription

+

The definition of the subscription root object is slightly different from other root objects. Its resolver function always returns a Stream or Result<Stream>, and the field parameters are usually used as data filtering conditions.

+

The following example subscribes to an integer stream, which generates one integer per second. The parameter step specifies the integer step size with a default of 1.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use std::time::Duration;
+use async_graphql::futures_util::stream::Stream;
+use async_graphql::futures_util::StreamExt;
+extern crate tokio_stream;
+extern crate tokio;
+use async_graphql::*;
+
+struct Subscription;
+
+#[Subscription]
+impl Subscription {
+    async fn integers(&self, #[graphql(default = 1)] step: i32) -> impl Stream<Item = i32> {
+        let mut value = 0;
+        tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(Duration::from_secs(1)))
+            .map(move |_| {
+                value += step;
+                value
+            })
+    }
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/toc.html b/en/toc.html new file mode 100644 index 000000000..984806150 --- /dev/null +++ b/en/toc.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + +
  1. Introduction
  2. Quickstart
  3. Type System
    1. SimpleObject
    2. Object
      1. Context
      2. Error handling
      3. Merging Objects / Subscriptions
      4. Derived fields
    3. Enum
    4. Interface
    5. Union
    6. InputObject
    7. OneofObject
    8. Default value
    9. Generics
  4. Schema
    1. Query and Mutation
    2. Subscription
    3. SDL Export
  5. Utilities
    1. Field guard
    2. Input value validators
    3. Cache control
    4. Cursor connections
    5. Error extensions
    6. Apollo Tracing
    7. Query complexity and depth
    8. Hide content in introspection
  6. Extensions
    1. How extensions are working
    2. Available extensions
  7. Integrations
    1. Poem
    2. Warp
    3. Actix-web
  8. Advanced topics
    1. Custom scalars
    2. Optimizing N+1 queries
    3. Custom directive
    4. Apollo Federation
+ + diff --git a/en/toc.js b/en/toc.js new file mode 100644 index 000000000..dd790ff55 --- /dev/null +++ b/en/toc.js @@ -0,0 +1,70 @@ +// Populate the sidebar +// +// This is a script, and not included directly in the page, to control the total size of the book. +// The TOC contains an entry for each page, so if each page includes a copy of the TOC, +// the total size of the page becomes O(n**2). +class MDBookSidebarScrollbox extends HTMLElement { + constructor() { + super(); + } + connectedCallback() { + this.innerHTML = '
  1. Introduction
  2. Quickstart
  3. Type System
    1. SimpleObject
    2. Object
      1. Context
      2. Error handling
      3. Merging Objects / Subscriptions
      4. Derived fields
    3. Enum
    4. Interface
    5. Union
    6. InputObject
    7. OneofObject
    8. Default value
    9. Generics
  4. Schema
    1. Query and Mutation
    2. Subscription
    3. SDL Export
  5. Utilities
    1. Field guard
    2. Input value validators
    3. Cache control
    4. Cursor connections
    5. Error extensions
    6. Apollo Tracing
    7. Query complexity and depth
    8. Hide content in introspection
  6. Extensions
    1. How extensions are working
    2. Available extensions
  7. Integrations
    1. Poem
    2. Warp
    3. Actix-web
  8. Advanced topics
    1. Custom scalars
    2. Optimizing N+1 queries
    3. Custom directive
    4. Apollo Federation
'; + // Set the current, active page, and reveal it if it's hidden + let current_page = document.location.href.toString().split("#")[0].split("?")[0]; + if (current_page.endsWith("/")) { + current_page += "index.html"; + } + var links = Array.prototype.slice.call(this.querySelectorAll("a")); + var l = links.length; + for (var i = 0; i < l; ++i) { + var link = links[i]; + var href = link.getAttribute("href"); + if (href && !href.startsWith("#") && !/^(?:[a-z+]+:)?\/\//.test(href)) { + link.href = path_to_root + href; + } + // The "index" page is supposed to alias the first chapter in the book. + if (link.href === current_page || (i === 0 && path_to_root === "" && current_page.endsWith("/index.html"))) { + link.classList.add("active"); + var parent = link.parentElement; + if (parent && parent.classList.contains("chapter-item")) { + parent.classList.add("expanded"); + } + while (parent) { + if (parent.tagName === "LI" && parent.previousElementSibling) { + if (parent.previousElementSibling.classList.contains("chapter-item")) { + parent.previousElementSibling.classList.add("expanded"); + } + } + parent = parent.parentElement; + } + } + } + // Track and set sidebar scroll position + this.addEventListener('click', function(e) { + if (e.target.tagName === 'A') { + sessionStorage.setItem('sidebar-scroll', this.scrollTop); + } + }, { passive: true }); + var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll'); + sessionStorage.removeItem('sidebar-scroll'); + if (sidebarScrollTop) { + // preserve sidebar scroll position when navigating via links within sidebar + this.scrollTop = sidebarScrollTop; + } else { + // scroll sidebar to current active section when navigating via "next/previous chapter" buttons + var activeSection = document.querySelector('#sidebar .active'); + if (activeSection) { + activeSection.scrollIntoView({ block: 'center' }); + } + } + // Toggle buttons + var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle'); + function toggleSection(ev) { + ev.currentTarget.parentElement.classList.toggle('expanded'); + } + Array.from(sidebarAnchorToggles).forEach(function (el) { + el.addEventListener('click', toggleSection); + }); + } +} +window.customElements.define("mdbook-sidebar-scrollbox", MDBookSidebarScrollbox); diff --git a/en/tomorrow-night.css b/en/tomorrow-night.css new file mode 100644 index 000000000..11752b8a8 --- /dev/null +++ b/en/tomorrow-night.css @@ -0,0 +1,104 @@ +/* Tomorrow Night Theme */ +/* https://github.com/jmblog/color-themes-for-highlightjs */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* https://github.com/jmblog/color-themes-for-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment { + color: #969896; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-attribute, +.hljs-attr, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #cc6666; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #de935f; +} + +/* Tomorrow Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #f0c674; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.hljs-name, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #b5bd68; +} + +/* Tomorrow Aqua */ +.hljs-title, +.hljs-section, +.css .hljs-hexcolor { + color: #8abeb7; +} + +/* Tomorrow Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #81a2be; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #b294bb; +} + +.hljs { + display: block; + overflow-x: auto; + background: #1d1f21; + color: #c5c8c6; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} + +.hljs-addition { + color: #718c00; +} + +.hljs-deletion { + color: #c82829; +} diff --git a/en/typesystem.html b/en/typesystem.html new file mode 100644 index 000000000..a9f5850ed --- /dev/null +++ b/en/typesystem.html @@ -0,0 +1,222 @@ + + + + + + Type System - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Type System

+

Async-graphql implements conversions from GraphQL Objects to Rust structs, and it's easy to use.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/utilities.html b/en/utilities.html new file mode 100644 index 000000000..56b2ee60b --- /dev/null +++ b/en/utilities.html @@ -0,0 +1,221 @@ + + + + + + Utilities - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Utilities

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/en/visibility.html b/en/visibility.html new file mode 100644 index 000000000..3d811e0a7 --- /dev/null +++ b/en/visibility.html @@ -0,0 +1,264 @@ + + + + + + Hide content in introspection - Async-graphql Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Hide content in introspection

+

By default, all types and fields are visible in introspection. But maybe you want to hide some content according to different users to avoid unnecessary misunderstandings. You can add the visible attribute to the type or field to do it.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+#[derive(SimpleObject)]
+struct MyObj {
+    // This field will be visible in introspection.
+    a: i32,
+
+    // This field is always hidden in introspection.
+    #[graphql(visible = false)]
+    b: i32,
+
+    // This field calls the `is_admin` function, which 
+    // is visible if the return value is `true`.
+    #[graphql(visible = "is_admin")]
+    c: i32,
+}
+
+#[derive(Enum, Copy, Clone, Eq, PartialEq)]
+enum MyEnum {
+    // This item will be visible in introspection.
+    A,
+
+    // This item is always hidden in introspection.
+    #[graphql(visible = false)]
+    B,
+
+    // This item calls the `is_admin` function, which 
+    // is visible if the return value is `true`.
+    #[graphql(visible = "is_admin")]
+    C,
+}
+
+struct IsAdmin(bool);
+
+fn is_admin(ctx: &Context<'_>) -> bool {
+    ctx.data_unchecked::<IsAdmin>().0
+}
+
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/examples b/examples deleted file mode 160000 index 0d7c54aaa..000000000 --- a/examples +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0d7c54aaa8ee9a9f35251d3e6c3be308bb89813b diff --git a/docs/index.html b/index.html similarity index 100% rename from docs/index.html rename to index.html diff --git a/integrations/README.md b/integrations/README.md deleted file mode 100644 index 4024ba9ef..000000000 --- a/integrations/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Integrations for async-graphql - -This directory provides various integrations for `async-graphql` to various crates in the ecosystem. - -## Requirements for an HTTP integration - -This is a list of criteria for HTTP integrations with `async-graphql` in order to make sure all -integrations are implemented consistently. - -Integrations may provide additional functionality to better integrate with the specific library, but -they must all internally use the below functions. - -- Conversion from HTTP library's request to `async_graphql::BatchRequest`: - 1. If the request is a `GET` request: - 1. Return the request's query parameters deserialized as an `async_graphql::Request`. - 1. If the request is a `POST` request: - 1. Get the request's `Content-Type` header. - 1. Call `async_graphql::http::receive_batch_body` on the request's body. - 1. Convert `ParseRequestError::PayloadTooLarge` to a 413 Payload Too Large response. - 1. Convert all other errors to a 400 Bad Request response. - 1. Otherwise return a 405 Method Not Allowed. -- Conversion from HTTP library's request to `async_graphql::Request`: - 1. Call the above function to convert the request to an `async_graphql::BatchRequest`. - 1. Call `BatchRequest::into_single` on the result. - 1. Convert all errors to a 400 Bad Request response. -- Conversion from `async_graphql::BatchResponse` to HTTP library's response: - 1. Create a 200 OK response. - 1. If the GraphQL response is ok, set the response's `Cache-Control` header to the response's - cache control value. - 1. Set the response's body to the GraphQL response serialized as JSON, also setting the - `Content-Type` header to `application/json`. -- GraphQL over websocket support: - 1. Create an `async_graphql::http:WebSocket` using `async_graphql::http::WebSocket::with_data`. - 1. Support the basics of the websocket protocol: - - Respond to ping messages with pong messages. - - Treat continuation messages identically to data messages. - 1. Stream all websocket messages that send data (bytes/text/continuations) to the - `async_graphql::http::WebSocket`. - 1. Convert all responses to websocket text responses. - -## Integration Status - -- **Poem**: Complete integration. -- **Actix-web**: Complete integration. -- **Rocket**: Missing websocket support (blocked on [support in Rocket itself](https://github.com/SergioBenitez/Rocket/issues/90)). -- **Warp**: Complete integration. -- **Axum**: Complete integration. diff --git a/integrations/actix-web/Cargo.toml b/integrations/actix-web/Cargo.toml deleted file mode 100644 index d88263a41..000000000 --- a/integrations/actix-web/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -authors = ["sunli ", "Koxiaet"] -categories = ["network-programming", "asynchronous"] -description = "async-graphql for actix-web" -documentation = "https://docs.rs/async-graphql-actix-web/" -edition = "2024" -homepage = "https://github.com/async-graphql/async-graphql" -keywords = ["futures", "async", "graphql"] -license = "MIT OR Apache-2.0" -name = "async-graphql-actix-web" -repository = "https://github.com/async-graphql/async-graphql" -version = "7.0.17" - -[dependencies] -async-graphql.workspace = true - -actix = "0.13.3" -actix-http = "3.6.0" -actix-web = { version = "4.5.1", default-features = false } -actix-web-actors = "4.3.0" -async-channel = "2.2.0" -futures-channel = "0.3.30" -futures-util = { version = "0.3.30", default-features = false } -serde_cbor = { version = "0.11.2", optional = true } -serde_json.workspace = true -thiserror.workspace = true -async-stream = "0.3.5" - -[features] -cbor = ["serde_cbor"] -http2 = ["actix-web/http2"] -default = ["http2"] - -[dev-dependencies] -async-graphql = { workspace = true, features = ["playground"] } -actix-rt = "2.9.0" -async-lock = "3.4.0" -serde = { version = "1", features = ["derive"] } diff --git a/integrations/actix-web/LICENSE-APACHE b/integrations/actix-web/LICENSE-APACHE deleted file mode 100644 index f8e5e5ea0..000000000 --- a/integrations/actix-web/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file diff --git a/integrations/actix-web/LICENSE-MIT b/integrations/actix-web/LICENSE-MIT deleted file mode 100644 index 468cd79a8..000000000 --- a/integrations/actix-web/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -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. \ No newline at end of file diff --git a/integrations/actix-web/src/handler.rs b/integrations/actix-web/src/handler.rs deleted file mode 100644 index 70a05efa9..000000000 --- a/integrations/actix-web/src/handler.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::time::Duration; - -use actix_http::StatusCode; -use actix_web::{Handler, HttpRequest, HttpResponse, Responder}; -use async_graphql::{ - Executor, - http::{create_multipart_mixed_stream, is_accept_multipart_mixed}, -}; -use futures_util::{FutureExt, StreamExt, future::LocalBoxFuture}; - -use crate::{GraphQLRequest, GraphQLResponse}; - -/// A GraphQL handler. -#[derive(Clone)] -pub struct GraphQL { - executor: E, -} - -impl GraphQL { - /// Create a GraphQL handler. - pub fn new(executor: E) -> Self { - Self { executor } - } -} - -impl Handler<(HttpRequest, GraphQLRequest)> for GraphQL { - type Output = HttpResponse; - type Future = LocalBoxFuture<'static, Self::Output>; - - fn call(&self, (http_req, graphql_req): (HttpRequest, GraphQLRequest)) -> Self::Future { - let executor = self.executor.clone(); - async move { - let is_accept_multipart_mixed = http_req - .headers() - .get("accept") - .and_then(|value| value.to_str().ok()) - .map(is_accept_multipart_mixed) - .unwrap_or_default(); - - if is_accept_multipart_mixed { - let stream = executor.execute_stream(graphql_req.0, None); - HttpResponse::build(StatusCode::OK) - .insert_header(("content-type", "multipart/mixed; boundary=graphql")) - .streaming( - create_multipart_mixed_stream(stream, Duration::from_secs(30)) - .map(Ok::<_, actix_web::Error>), - ) - } else { - GraphQLResponse(executor.execute(graphql_req.into_inner()).await.into()) - .respond_to(&http_req) - } - } - .boxed_local() - } -} diff --git a/integrations/actix-web/src/lib.rs b/integrations/actix-web/src/lib.rs deleted file mode 100644 index 8244e4fd4..000000000 --- a/integrations/actix-web/src/lib.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Async-graphql integration with Actix-web -#![forbid(unsafe_code)] -#![allow(clippy::upper_case_acronyms)] -#![warn(missing_docs)] - -mod handler; -mod request; -mod subscription; - -pub use handler::GraphQL; -pub use request::{GraphQLBatchRequest, GraphQLRequest, GraphQLResponse}; -pub use subscription::GraphQLSubscription; diff --git a/integrations/actix-web/src/request.rs b/integrations/actix-web/src/request.rs deleted file mode 100644 index 736a747d3..000000000 --- a/integrations/actix-web/src/request.rs +++ /dev/null @@ -1,240 +0,0 @@ -use std::{ - future::Future, - io::{self, ErrorKind}, - pin::Pin, - str::FromStr, -}; - -use actix_http::{ - body::BoxBody, - error::PayloadError, - header::{HeaderName, HeaderValue}, -}; -use actix_web::{ - Error, FromRequest, HttpRequest, HttpResponse, Responder, Result, - dev::Payload, - error::JsonPayloadError, - http, - http::{Method, StatusCode}, -}; -use async_graphql::{ParseRequestError, http::MultipartOptions}; -use futures_util::{ - StreamExt, TryStreamExt, - future::{self, FutureExt}, -}; - -/// Extractor for GraphQL request. -/// -/// `async_graphql::http::MultipartOptions` allows to configure extraction -/// process. -pub struct GraphQLRequest(pub async_graphql::Request); - -impl GraphQLRequest { - /// Unwraps the value to `async_graphql::Request`. - #[must_use] - pub fn into_inner(self) -> async_graphql::Request { - self.0 - } -} - -type BatchToRequestMapper = - fn(<::Future as Future>::Output) -> Result; - -impl FromRequest for GraphQLRequest { - type Error = Error; - type Future = future::Map<::Future, BatchToRequestMapper>; - - fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - GraphQLBatchRequest::from_request(req, payload).map(|res| { - Ok(Self( - res?.0 - .into_single() - .map_err(actix_web::error::ErrorBadRequest)?, - )) - }) - } -} - -/// Extractor for GraphQL batch request. -/// -/// `async_graphql::http::MultipartOptions` allows to configure extraction -/// process. -pub struct GraphQLBatchRequest(pub async_graphql::BatchRequest); - -impl GraphQLBatchRequest { - /// Unwraps the value to `async_graphql::BatchRequest`. - #[must_use] - pub fn into_inner(self) -> async_graphql::BatchRequest { - self.0 - } -} - -impl FromRequest for GraphQLBatchRequest { - type Error = Error; - type Future = Pin>>>; - - fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - let config = req - .app_data::() - .cloned() - .unwrap_or_default(); - - if req.method() == Method::GET { - let res = async_graphql::http::parse_query_string(req.query_string()) - .map_err(io::Error::other); - Box::pin(async move { Ok(Self(async_graphql::BatchRequest::Single(res?))) }) - } else if req.method() == Method::POST { - let content_type = req - .headers() - .get(http::header::CONTENT_TYPE) - .and_then(|value| value.to_str().ok()) - .map(|value| value.to_string()); - - let (tx, rx) = async_channel::bounded(16); - - // Payload is !Send so we create indirection with a channel - let mut payload = payload.take(); - actix::spawn(async move { - while let Some(item) = payload.next().await { - if tx.send(item).await.is_err() { - return; - } - } - }); - - Box::pin(async move { - Ok(GraphQLBatchRequest( - async_graphql::http::receive_batch_body( - content_type, - rx.map_err(|e| match e { - PayloadError::Incomplete(Some(e)) | PayloadError::Io(e) => e, - PayloadError::Incomplete(None) => { - io::Error::from(ErrorKind::UnexpectedEof) - } - PayloadError::EncodingCorrupted => io::Error::new( - ErrorKind::InvalidData, - "cannot decode content-encoding", - ), - PayloadError::Overflow => io::Error::new( - ErrorKind::InvalidData, - "a payload reached size limit", - ), - PayloadError::UnknownLength => { - io::Error::other("a payload length is unknown") - } - #[cfg(feature = "http2")] - PayloadError::Http2Payload(e) if e.is_io() => e.into_io().unwrap(), - #[cfg(feature = "http2")] - PayloadError::Http2Payload(e) => io::Error::other(e), - _ => io::Error::other(e), - }) - .into_async_read(), - config, - ) - .await - .map_err(|err| match err { - ParseRequestError::PayloadTooLarge => { - actix_web::error::ErrorPayloadTooLarge(err) - } - _ => actix_web::error::ErrorBadRequest(err), - })?, - )) - }) - } else { - Box::pin(async move { - Err(actix_web::error::ErrorMethodNotAllowed( - "GraphQL only supports GET and POST requests", - )) - }) - } - } -} - -/// Responder for a GraphQL response. -/// -/// This contains a batch response, but since regular responses are a type of -/// batch response it works for both. -pub struct GraphQLResponse(pub async_graphql::BatchResponse); - -impl From for GraphQLResponse { - fn from(resp: async_graphql::Response) -> Self { - Self(resp.into()) - } -} - -impl From for GraphQLResponse { - fn from(resp: async_graphql::BatchResponse) -> Self { - Self(resp) - } -} - -#[cfg(feature = "cbor")] -mod cbor { - use core::fmt; - - use actix_web::{ResponseError, http::StatusCode}; - - #[derive(Debug)] - pub struct Error(pub serde_cbor::Error); - impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } - } - impl ResponseError for Error { - fn status_code(&self) -> StatusCode { - StatusCode::INTERNAL_SERVER_ERROR - } - } -} - -impl Responder for GraphQLResponse { - type Body = BoxBody; - - fn respond_to(self, req: &HttpRequest) -> HttpResponse { - let mut builder = HttpResponse::build(StatusCode::OK); - - if self.0.is_ok() { - if let Some(cache_control) = self.0.cache_control().value() { - builder.append_header((http::header::CACHE_CONTROL, cache_control)); - } - } - - let accept = req - .headers() - .get(http::header::ACCEPT) - .and_then(|val| val.to_str().ok()); - let (ct, body) = match accept { - // optional cbor support - #[cfg(feature = "cbor")] - // this avoids copy-pasting the mime type - Some(ct @ "application/cbor") => ( - ct, - match serde_cbor::to_vec(&self.0) { - Ok(body) => body, - Err(e) => return HttpResponse::from_error(cbor::Error(e)), - }, - ), - _ => ( - "application/graphql-response+json", - match serde_json::to_vec(&self.0) { - Ok(body) => body, - Err(e) => return HttpResponse::from_error(JsonPayloadError::Serialize(e)), - }, - ), - }; - - let mut resp = builder.content_type(ct).body(body); - - for (name, value) in self.0.http_headers_iter() { - if let (Ok(name), Ok(value)) = ( - HeaderName::from_str(name.as_str()), - HeaderValue::from_bytes(value.as_bytes()), - ) { - resp.headers_mut().append(name, value); - } - } - - resp - } -} diff --git a/integrations/actix-web/src/subscription.rs b/integrations/actix-web/src/subscription.rs deleted file mode 100644 index 7e3ee621a..000000000 --- a/integrations/actix-web/src/subscription.rs +++ /dev/null @@ -1,303 +0,0 @@ -use std::{ - future::Future, - str::FromStr, - time::{Duration, Instant}, -}; - -use actix::{ - Actor, ActorContext, ActorFutureExt, ActorStreamExt, AsyncContext, ContextFutureSpawner, - StreamHandler, WrapFuture, WrapStream, -}; -use actix_http::{error::PayloadError, ws}; -use actix_web::{Error, HttpRequest, HttpResponse, web::Bytes}; -use actix_web_actors::ws::{CloseReason, Message, ProtocolError, WebsocketContext}; -use async_graphql::{ - Data, Executor, Result, - http::{ - ALL_WEBSOCKET_PROTOCOLS, DefaultOnConnInitType, DefaultOnPingType, WebSocket, - WebSocketProtocols, WsMessage, default_on_connection_init, default_on_ping, - }, -}; -use futures_util::stream::Stream; - -const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); -const CLIENT_TIMEOUT: Duration = Duration::from_secs(10); - -#[derive(thiserror::Error, Debug)] -#[error("failed to parse graphql protocol")] -pub struct ParseGraphQLProtocolError; - -/// A builder for websocket subscription actor. -pub struct GraphQLSubscription { - executor: E, - data: Data, - on_connection_init: OnInit, - on_ping: OnPing, - keepalive_timeout: Option, -} - -impl GraphQLSubscription { - /// Create a GraphQL subscription builder. - pub fn new(executor: E) -> Self { - Self { - executor, - data: Default::default(), - on_connection_init: default_on_connection_init, - on_ping: default_on_ping, - keepalive_timeout: None, - } - } -} - -impl GraphQLSubscription -where - E: Executor, - OnInit: FnOnce(serde_json::Value) -> OnInitFut + Unpin + Send + 'static, - OnInitFut: Future> + Send + 'static, - OnPing: FnOnce(Option<&Data>, Option) -> OnPingFut - + Clone - + Unpin - + Send - + 'static, - OnPingFut: Future>> + Send + 'static, -{ - /// Specify the initial subscription context data, usually you can get - /// something from the incoming request to create it. - #[must_use] - pub fn with_data(self, data: Data) -> Self { - Self { data, ..self } - } - - /// Specify a callback function to be called when the connection is - /// initialized. - /// - /// You can get something from the payload of [`GQL_CONNECTION_INIT` message](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init) to create [`Data`]. - /// The data returned by this callback function will be merged with the data - /// specified by [`with_data`]. - #[must_use] - pub fn on_connection_init(self, callback: F) -> GraphQLSubscription - where - F: FnOnce(serde_json::Value) -> R + Unpin + Send + 'static, - R: Future> + Send + 'static, - { - GraphQLSubscription { - executor: self.executor, - data: self.data, - on_connection_init: callback, - on_ping: self.on_ping, - keepalive_timeout: self.keepalive_timeout, - } - } - - /// Specify a ping callback function. - /// - /// This function if present, will be called with the data sent by the - /// client in the [`Ping` message](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#ping). - /// - /// The function should return the data to be sent in the [`Pong` message](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#pong). - /// - /// NOTE: Only used for the `graphql-ws` protocol. - #[must_use] - pub fn on_ping(self, callback: F) -> GraphQLSubscription - where - F: FnOnce(Option<&Data>, Option) -> R + Send + Clone + 'static, - R: Future>> + Send + 'static, - { - GraphQLSubscription { - executor: self.executor, - data: self.data, - on_connection_init: self.on_connection_init, - on_ping: callback, - keepalive_timeout: self.keepalive_timeout, - } - } - - /// Sets a timeout for receiving an acknowledgement of the keep-alive ping. - /// - /// If the ping is not acknowledged within the timeout, the connection will - /// be closed. - /// - /// NOTE: Only used for the `graphql-ws` protocol. - #[must_use] - pub fn keepalive_timeout(self, timeout: impl Into>) -> Self { - Self { - keepalive_timeout: timeout.into(), - ..self - } - } - - /// Start the subscription actor. - pub fn start(self, request: &HttpRequest, stream: S) -> Result - where - S: Stream> + 'static, - { - let protocol = request - .headers() - .get("sec-websocket-protocol") - .and_then(|value| value.to_str().ok()) - .and_then(|protocols| { - protocols - .split(',') - .find_map(|p| WebSocketProtocols::from_str(p.trim()).ok()) - }) - .ok_or_else(|| actix_web::error::ErrorBadRequest(ParseGraphQLProtocolError))?; - - let actor = GraphQLSubscriptionActor { - executor: self.executor, - data: Some(self.data), - protocol, - last_heartbeat: Instant::now(), - messages: None, - on_connection_init: Some(self.on_connection_init), - on_ping: self.on_ping, - keepalive_timeout: self.keepalive_timeout, - continuation: Vec::new(), - }; - - actix_web_actors::ws::WsResponseBuilder::new(actor, request, stream) - .protocols(&ALL_WEBSOCKET_PROTOCOLS) - .start() - } -} - -struct GraphQLSubscriptionActor { - executor: E, - data: Option, - protocol: WebSocketProtocols, - last_heartbeat: Instant, - messages: Option>>, - on_connection_init: Option, - on_ping: OnPing, - keepalive_timeout: Option, - continuation: Vec, -} - -impl GraphQLSubscriptionActor -where - E: Executor, - OnInit: FnOnce(serde_json::Value) -> OnInitFut + Unpin + Send + 'static, - OnInitFut: Future> + Send + 'static, - OnPing: FnOnce(Option<&Data>, Option) -> OnPingFut - + Clone - + Unpin - + Send - + 'static, - OnPingFut: Future>> + Send + 'static, -{ - fn send_heartbeats(&self, ctx: &mut WebsocketContext) { - ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| { - if Instant::now().duration_since(act.last_heartbeat) > CLIENT_TIMEOUT { - ctx.stop(); - } - ctx.ping(b""); - }); - } -} - -impl Actor for GraphQLSubscriptionActor -where - E: Executor, - OnInit: FnOnce(serde_json::Value) -> OnInitFut + Unpin + Send + 'static, - OnInitFut: Future> + Send + 'static, - OnPing: FnOnce(Option<&Data>, Option) -> OnPingFut - + Clone - + Unpin - + Send - + 'static, - OnPingFut: Future>> + Send + 'static, -{ - type Context = WebsocketContext; - - fn started(&mut self, ctx: &mut Self::Context) { - self.send_heartbeats(ctx); - - let (tx, rx) = async_channel::unbounded(); - - WebSocket::new(self.executor.clone(), rx, self.protocol) - .connection_data(self.data.take().unwrap()) - .on_connection_init(self.on_connection_init.take().unwrap()) - .on_ping(self.on_ping.clone()) - .keepalive_timeout(self.keepalive_timeout) - .into_actor(self) - .map(|response, _act, ctx| match response { - WsMessage::Text(text) => ctx.text(text), - WsMessage::Close(code, msg) => ctx.close(Some(CloseReason { - code: code.into(), - description: Some(msg), - })), - }) - .finish() - .spawn(ctx); - - self.messages = Some(tx); - } -} - -impl StreamHandler> - for GraphQLSubscriptionActor -where - E: Executor, - OnInit: FnOnce(serde_json::Value) -> OnInitFut + Unpin + Send + 'static, - OnInitFut: Future> + Send + 'static, - OnPing: FnOnce(Option<&Data>, Option) -> OnPingFut - + Clone - + Unpin - + Send - + 'static, - OnPingFut: Future>> + Send + 'static, -{ - fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { - let msg = match msg { - Err(_) => { - ctx.stop(); - return; - } - Ok(msg) => msg, - }; - - let message = match msg { - Message::Ping(msg) => { - self.last_heartbeat = Instant::now(); - ctx.pong(&msg); - None - } - Message::Pong(_) => { - self.last_heartbeat = Instant::now(); - None - } - Message::Continuation(item) => match item { - ws::Item::FirstText(bytes) | ws::Item::FirstBinary(bytes) => { - self.continuation = bytes.to_vec(); - None - } - ws::Item::Continue(bytes) => { - self.continuation.extend_from_slice(&bytes); - None - } - ws::Item::Last(bytes) => { - self.continuation.extend_from_slice(&bytes); - Some(std::mem::take(&mut self.continuation)) - } - }, - Message::Text(s) => Some(s.into_bytes().to_vec()), - Message::Binary(bytes) => Some(bytes.to_vec()), - Message::Close(_) => { - ctx.stop(); - None - } - Message::Nop => None, - }; - - if let Some(message) = message { - let sender = self.messages.as_ref().unwrap().clone(); - - async move { sender.send(message).await } - .into_actor(self) - .map(|res, _actor, ctx| match res { - Ok(()) => {} - Err(_) => ctx.stop(), - }) - .spawn(ctx) - } - } -} diff --git a/integrations/actix-web/tests/graphql.rs b/integrations/actix-web/tests/graphql.rs deleted file mode 100644 index e408e14cf..000000000 --- a/integrations/actix-web/tests/graphql.rs +++ /dev/null @@ -1,268 +0,0 @@ -use actix_http::Method; -use actix_web::{App, dev::Service, guard, test, web, web::Data}; -use async_graphql::*; -use serde_json::json; -use test_utils::*; - -mod test_utils; - -#[actix_rt::test] -async fn test_playground() { - let srv = test::init_service( - App::new().service( - web::resource("/") - .guard(guard::Get()) - .to(test_utils::gql_playgound), - ), - ) - .await; - let req = test::TestRequest::with_uri("/").to_request(); - let response = srv.call(req).await.unwrap(); - assert!(response.status().is_success()); - let body = response.into_body(); - assert!( - std::str::from_utf8(&actix_web::body::to_bytes(body).await.unwrap()) - .unwrap() - .contains("graphql") - ); -} - -#[actix_rt::test] -async fn test_add() { - let srv = test::init_service( - App::new() - .app_data(Data::new(Schema::new( - AddQueryRoot, - EmptyMutation, - EmptySubscription, - ))) - .service( - web::resource("/") - .guard(guard::Post()) - .to(gql_handle_schema::), - ), - ) - .await; - let response = srv - .call( - test::TestRequest::with_uri("/") - .method(Method::POST) - .set_payload(r#"{"query":"{ add(a: 10, b: 20) }"}"#) - .to_request(), - ) - .await - .unwrap(); - assert!(response.status().is_success()); - let body = response.into_body(); - assert_eq!( - actix_web::body::to_bytes(body).await.unwrap(), - json!({"data": {"add": 30}}).to_string().into_bytes() - ); -} - -#[actix_rt::test] -async fn test_hello() { - let srv = test::init_service( - App::new() - .app_data(Data::new(Schema::new( - HelloQueryRoot, - EmptyMutation, - EmptySubscription, - ))) - .service( - web::resource("/") - .guard(guard::Post()) - .to(gql_handle_schema::), - ), - ) - .await; - - let response = srv - .call( - test::TestRequest::with_uri("/") - .method(Method::POST) - .set_payload(r#"{"query":"{ hello }"}"#) - .to_request(), - ) - .await - .unwrap(); - assert!(response.status().is_success()); - let body = response.into_body(); - assert_eq!( - actix_web::body::to_bytes(body).await.unwrap(), - json!({"data": {"hello": "Hello, world!"}}) - .to_string() - .into_bytes() - ); -} - -#[actix_rt::test] -async fn test_hello_header() { - let srv = test::init_service( - App::new() - .app_data(Data::new(Schema::new( - HelloQueryRoot, - EmptyMutation, - EmptySubscription, - ))) - .service( - web::resource("/") - .guard(guard::Post()) - .to(gql_handle_schema_with_header::), - ), - ) - .await; - - let response = srv - .call( - test::TestRequest::with_uri("/") - .method(Method::POST) - .insert_header(("Name", "Foo")) - .set_payload(r#"{"query":"{ hello }"}"#) - .to_request(), - ) - .await - .unwrap(); - assert!(response.status().is_success()); - let body = response.into_body(); - assert_eq!( - actix_web::body::to_bytes(body).await.unwrap(), - json!({"data": {"hello": "Hello, Foo!"}}) - .to_string() - .into_bytes() - ); -} - -#[actix_rt::test] -async fn test_count() { - let srv = test::init_service( - App::new() - .app_data(Data::new( - Schema::build(CountQueryRoot, CountMutation, EmptySubscription) - .data(Count::default()) - .finish(), - )) - .service( - web::resource("/") - .guard(guard::Post()) - .to(gql_handle_schema::), - ), - ) - .await; - - let response = srv - .call( - test::TestRequest::with_uri("/") - .method(Method::POST) - .set_payload(r#"{"query":"{ count }"}"#) - .to_request(), - ) - .await - .unwrap(); - assert!(response.status().is_success()); - let body = response.into_body(); - assert_eq!( - actix_web::body::to_bytes(body).await.unwrap(), - json!({"data": {"count": 0}}).to_string().into_bytes() - ); - - let response = srv - .call( - test::TestRequest::with_uri("/") - .method(Method::POST) - .set_payload(r#"{"query":"mutation{ addCount(count: 10) }"}"#) - .to_request(), - ) - .await - .unwrap(); - assert!(response.status().is_success()); - let body = response.into_body(); - assert_eq!( - actix_web::body::to_bytes(body).await.unwrap(), - json!({"data": {"addCount": 10}}).to_string().into_bytes(), - ); - - let response = srv - .call( - test::TestRequest::with_uri("/") - .method(Method::POST) - .set_payload(r#"{"query":"mutation{ subtractCount(count: 2) }"}"#) - .to_request(), - ) - .await - .unwrap(); - assert!(response.status().is_success()); - let body = response.into_body(); - assert_eq!( - actix_web::body::to_bytes(body).await.unwrap(), - json!({"data": {"subtractCount": 8}}) - .to_string() - .into_bytes() - ); - - let response = srv - .call( - test::TestRequest::with_uri("/") - .method(Method::POST) - .set_payload(r#"{"query":"mutation{ subtractCount(count: 2) }"}"#) - .to_request(), - ) - .await - .unwrap(); - assert!(response.status().is_success()); - let body = response.into_body(); - assert_eq!( - actix_web::body::to_bytes(body).await.unwrap(), - json!({"data": {"subtractCount": 6}}) - .to_string() - .into_bytes() - ); -} - -#[cfg(feature = "cbor")] -#[actix_rt::test] -async fn test_cbor() { - let srv = test::init_service( - App::new() - .app_data(Data::new(Schema::new( - AddQueryRoot, - EmptyMutation, - EmptySubscription, - ))) - .service( - web::resource("/") - .guard(guard::Post()) - .to(gql_handle_schema::), - ), - ) - .await; - let response = srv - .call( - test::TestRequest::with_uri("/") - .method(Method::POST) - .set_payload(r#"{"query":"{ add(a: 10, b: 20) }"}"#) - .insert_header((actix_http::header::ACCEPT, "application/cbor")) - .to_request(), - ) - .await - .unwrap(); - assert!(response.status().is_success()); - #[derive(Debug, serde::Deserialize, PartialEq)] - struct Response { - data: ResponseInner, - } - #[derive(Debug, serde::Deserialize, PartialEq)] - struct ResponseInner { - add: i32, - } - let body = actix_web::body::to_bytes(response.into_body()) - .await - .unwrap(); - let response: Response = serde_cbor::from_slice(&body).unwrap(); - assert_eq!( - response, - Response { - data: ResponseInner { add: 30 } - } - ); -} diff --git a/integrations/actix-web/tests/test_utils.rs b/integrations/actix-web/tests/test_utils.rs deleted file mode 100644 index bbbfed000..000000000 --- a/integrations/actix-web/tests/test_utils.rs +++ /dev/null @@ -1,91 +0,0 @@ -use actix_web::{HttpRequest, HttpResponse, web}; -use async_graphql::{ - Context, EmptyMutation, EmptySubscription, Object, ObjectType, Schema, SubscriptionType, - http::{GraphQLPlaygroundConfig, playground_source}, -}; -use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse}; -use async_lock::Mutex; - -pub async fn gql_playgound() -> HttpResponse { - HttpResponse::Ok() - .content_type("text/html; charset=utf-8") - .body(playground_source(GraphQLPlaygroundConfig::new("/"))) -} - -pub(crate) struct AddQueryRoot; - -#[Object] -impl AddQueryRoot { - /// Returns the sum of a and b - async fn add(&self, a: i32, b: i32) -> i32 { - a + b - } -} - -struct Hello(String); - -pub(crate) struct HelloQueryRoot; - -#[Object] -impl HelloQueryRoot { - /// Returns hello - async fn hello<'a>(&self, ctx: &'a Context<'_>) -> String { - let name = ctx.data_opt::().map(|hello| hello.0.as_str()); - format!("Hello, {}!", name.unwrap_or("world")) - } -} - -pub type Count = Mutex; - -pub(crate) struct CountQueryRoot; - -#[Object] -impl CountQueryRoot { - async fn count<'a>(&self, ctx: &'a Context<'_>) -> i32 { - *ctx.data_unchecked::().lock().await - } -} - -pub(crate) struct CountMutation; - -#[Object] -impl CountMutation { - async fn add_count<'a>(&self, ctx: &'a Context<'_>, count: i32) -> i32 { - let mut guard_count = ctx.data_unchecked::().lock().await; - *guard_count += count; - *guard_count - } - - async fn subtract_count<'a>(&self, ctx: &'a Context<'_>, count: i32) -> i32 { - let mut guard_count = ctx.data_unchecked::().lock().await; - *guard_count -= count; - *guard_count - } -} - -pub async fn gql_handle_schema< - Q: ObjectType + 'static, - M: ObjectType + 'static, - S: SubscriptionType + 'static, ->( - schema: web::Data>, - req: GraphQLRequest, -) -> GraphQLResponse { - schema.execute(req.into_inner()).await.into() -} - -pub async fn gql_handle_schema_with_header( - schema: actix_web::web::Data>, - req: HttpRequest, - gql_request: GraphQLRequest, -) -> GraphQLResponse { - let name = req - .headers() - .get("Name") - .and_then(|value| value.to_str().map(|s| Hello(s.to_string())).ok()); - let mut request = gql_request.into_inner(); - if let Some(name) = name { - request = request.data(name); - } - schema.execute(request).await.into() -} diff --git a/integrations/axum/Cargo.toml b/integrations/axum/Cargo.toml deleted file mode 100644 index e42b41b5c..000000000 --- a/integrations/axum/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -authors = ["sunli "] -categories = ["network-programming", "asynchronous"] -description = "async-graphql for axum" -documentation = "https://docs.rs/async-graphql-axum/" -edition = "2024" -homepage = "https://github.com/async-graphql/async-graphql" -keywords = ["futures", "async", "graphql", "axum"] -license = "MIT OR Apache-2.0" -name = "async-graphql-axum" -repository = "https://github.com/async-graphql/async-graphql" -version = "7.0.17" - -[dependencies] -async-graphql.workspace = true - -bytes.workspace = true -futures-util.workspace = true -serde_json.workspace = true -tokio = { version = "1.36.0", features = ["time"] } -tokio-util = { workspace = true, default-features = false, features = [ - "io", - "compat", -] } -tokio-stream = "0.1.15" -tower-service = "0.3" - -[target.'cfg(target_arch = "wasm32")'.dependencies] -axum = { version = "0.8.1", default-features = false } - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -axum = { version = "0.8.1", features = ["ws"] } diff --git a/integrations/axum/LICENSE-APACHE b/integrations/axum/LICENSE-APACHE deleted file mode 100644 index f8e5e5ea0..000000000 --- a/integrations/axum/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file diff --git a/integrations/axum/LICENSE-MIT b/integrations/axum/LICENSE-MIT deleted file mode 100644 index 468cd79a8..000000000 --- a/integrations/axum/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -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. \ No newline at end of file diff --git a/integrations/axum/README.md b/integrations/axum/README.md deleted file mode 100644 index 25e998f26..000000000 --- a/integrations/axum/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Supports axum version `0.6.*`. - -For axum `0.5.*` use version `4.0.15` of this crate. diff --git a/integrations/axum/src/extract.rs b/integrations/axum/src/extract.rs deleted file mode 100644 index 7976e5bb9..000000000 --- a/integrations/axum/src/extract.rs +++ /dev/null @@ -1,132 +0,0 @@ -use std::marker::PhantomData; - -use async_graphql::{ParseRequestError, futures_util::TryStreamExt, http::MultipartOptions}; -use axum::{ - extract::{FromRequest, Request}, - http::{self, Method}, - response::IntoResponse, -}; -use tokio_util::compat::TokioAsyncReadCompatExt; - -/// Extractor for GraphQL request. -pub struct GraphQLRequest( - pub async_graphql::Request, - PhantomData, -); - -impl GraphQLRequest { - /// Unwraps the value to `async_graphql::Request`. - #[must_use] - pub fn into_inner(self) -> async_graphql::Request { - self.0 - } -} - -/// Rejection response types. -pub mod rejection { - use async_graphql::ParseRequestError; - use axum::{ - body::Body, - http, - http::StatusCode, - response::{IntoResponse, Response}, - }; - - /// Rejection used for [`GraphQLRequest`](GraphQLRequest). - pub struct GraphQLRejection(pub ParseRequestError); - - impl IntoResponse for GraphQLRejection { - fn into_response(self) -> Response { - match self.0 { - ParseRequestError::PayloadTooLarge => http::Response::builder() - .status(StatusCode::PAYLOAD_TOO_LARGE) - .body(Body::empty()) - .unwrap(), - bad_request => http::Response::builder() - .status(StatusCode::BAD_REQUEST) - .body(Body::from(format!("{:?}", bad_request))) - .unwrap(), - } - } - } - - impl From for GraphQLRejection { - fn from(err: ParseRequestError) -> Self { - GraphQLRejection(err) - } - } -} - -impl FromRequest for GraphQLRequest -where - S: Send + Sync, - R: IntoResponse + From, -{ - type Rejection = R; - - async fn from_request(req: Request, state: &S) -> Result { - Ok(GraphQLRequest( - GraphQLBatchRequest::::from_request(req, state) - .await? - .0 - .into_single()?, - PhantomData, - )) - } -} - -/// Extractor for GraphQL batch request. -pub struct GraphQLBatchRequest( - pub async_graphql::BatchRequest, - PhantomData, -); - -impl GraphQLBatchRequest { - /// Unwraps the value to `async_graphql::BatchRequest`. - #[must_use] - pub fn into_inner(self) -> async_graphql::BatchRequest { - self.0 - } -} - -impl FromRequest for GraphQLBatchRequest -where - S: Send + Sync, - R: IntoResponse + From, -{ - type Rejection = R; - - async fn from_request(req: Request, _state: &S) -> Result { - if req.method() == Method::GET { - let uri = req.uri(); - let res = async_graphql::http::parse_query_string(uri.query().unwrap_or_default()) - .map_err(|err| { - ParseRequestError::Io(std::io::Error::other(format!( - "failed to parse graphql request from uri query: {}", - err - ))) - }); - Ok(Self(async_graphql::BatchRequest::Single(res?), PhantomData)) - } else { - let content_type = req - .headers() - .get(http::header::CONTENT_TYPE) - .and_then(|value| value.to_str().ok()) - .map(ToString::to_string); - let body_stream = req - .into_body() - .into_data_stream() - .map_err(|err| std::io::Error::other(err.to_string())); - let body_reader = tokio_util::io::StreamReader::new(body_stream).compat(); - Ok(Self( - async_graphql::http::receive_batch_body( - content_type, - body_reader, - MultipartOptions::default(), - ) - .await?, - PhantomData, - )) - } - } -} diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs deleted file mode 100644 index 5d87913f9..000000000 --- a/integrations/axum/src/lib.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! Async-graphql integration with Axum -#![forbid(unsafe_code)] -#![allow(clippy::uninlined_format_args)] -#![warn(missing_docs)] - -mod extract; -mod query; -mod response; -#[cfg(not(target_arch = "wasm32"))] -mod subscription; - -pub use extract::{GraphQLBatchRequest, GraphQLRequest, rejection}; -pub use query::GraphQL; -pub use response::GraphQLResponse; -#[cfg(not(target_arch = "wasm32"))] -pub use subscription::{GraphQLProtocol, GraphQLSubscription, GraphQLWebSocket}; diff --git a/integrations/axum/src/query.rs b/integrations/axum/src/query.rs deleted file mode 100644 index 3d2a5de46..000000000 --- a/integrations/axum/src/query.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::{ - convert::Infallible, - task::{Context, Poll}, - time::Duration, -}; - -use async_graphql::{ - Executor, - http::{create_multipart_mixed_stream, is_accept_multipart_mixed}, -}; -use axum::{ - BoxError, - body::{Body, HttpBody}, - extract::FromRequest, - http::{Request as HttpRequest, Response as HttpResponse}, - response::IntoResponse, -}; -use bytes::Bytes; -use futures_util::{StreamExt, future::BoxFuture}; -use tower_service::Service; - -use crate::{ - GraphQLBatchRequest, GraphQLRequest, GraphQLResponse, extract::rejection::GraphQLRejection, -}; - -/// A GraphQL service. -#[derive(Clone)] -pub struct GraphQL { - executor: E, -} - -impl GraphQL { - /// Create a GraphQL handler. - pub fn new(executor: E) -> Self { - Self { executor } - } -} - -impl Service> for GraphQL -where - B: HttpBody + Send + 'static, - B::Data: Into, - B::Error: Into, - E: Executor, -{ - type Response = HttpResponse; - type Error = Infallible; - type Future = BoxFuture<'static, Result>; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: HttpRequest) -> Self::Future { - let executor = self.executor.clone(); - let req = req.map(Body::new); - Box::pin(async move { - let is_accept_multipart_mixed = req - .headers() - .get("accept") - .and_then(|value| value.to_str().ok()) - .map(is_accept_multipart_mixed) - .unwrap_or_default(); - - if is_accept_multipart_mixed { - let req = match GraphQLRequest::::from_request(req, &()).await { - Ok(req) => req, - Err(err) => return Ok(err.into_response()), - }; - let stream = executor.execute_stream(req.0, None); - let body = Body::from_stream( - create_multipart_mixed_stream(stream, Duration::from_secs(30)) - .map(Ok::<_, std::io::Error>), - ); - Ok(HttpResponse::builder() - .header("content-type", "multipart/mixed; boundary=graphql") - .body(body) - .expect("BUG: invalid response")) - } else { - let req = - match GraphQLBatchRequest::::from_request(req, &()).await { - Ok(req) => req, - Err(err) => return Ok(err.into_response()), - }; - Ok(GraphQLResponse(executor.execute_batch(req.0).await).into_response()) - } - }) - } -} diff --git a/integrations/axum/src/response.rs b/integrations/axum/src/response.rs deleted file mode 100644 index fade3c478..000000000 --- a/integrations/axum/src/response.rs +++ /dev/null @@ -1,46 +0,0 @@ -use axum::{ - body::Body, - http, - http::HeaderValue, - response::{IntoResponse, Response}, -}; - -/// Responder for a GraphQL response. -/// -/// This contains a batch response, but since regular responses are a type of -/// batch response it works for both. -pub struct GraphQLResponse(pub async_graphql::BatchResponse); - -impl From for GraphQLResponse { - fn from(resp: async_graphql::Response) -> Self { - Self(resp.into()) - } -} - -impl From for GraphQLResponse { - fn from(resp: async_graphql::BatchResponse) -> Self { - Self(resp) - } -} - -impl IntoResponse for GraphQLResponse { - fn into_response(self) -> Response { - let body: Body = serde_json::to_string(&self.0).unwrap().into(); - let mut resp = Response::new(body); - resp.headers_mut().insert( - http::header::CONTENT_TYPE, - HeaderValue::from_static("application/graphql-response+json"), - ); - if self.0.is_ok() { - if let Some(cache_control) = self.0.cache_control().value() { - if let Ok(value) = HeaderValue::from_str(&cache_control) { - resp.headers_mut() - .insert(http::header::CACHE_CONTROL, value); - } - } - } - - resp.headers_mut().extend(self.0.http_headers()); - resp - } -} diff --git a/integrations/axum/src/subscription.rs b/integrations/axum/src/subscription.rs deleted file mode 100644 index 2f5fd869c..000000000 --- a/integrations/axum/src/subscription.rs +++ /dev/null @@ -1,301 +0,0 @@ -use std::{convert::Infallible, future::Future, str::FromStr, time::Duration}; - -use async_graphql::{ - Data, Executor, Result, - futures_util::task::{Context, Poll}, - http::{ - ALL_WEBSOCKET_PROTOCOLS, DefaultOnConnInitType, DefaultOnPingType, WebSocketProtocols, - WsMessage, default_on_connection_init, default_on_ping, - }, -}; -use axum::{ - Error, - body::{Body, HttpBody}, - extract::{ - FromRequestParts, WebSocketUpgrade, - ws::{CloseFrame, Message}, - }, - http::{self, Request, Response, StatusCode, request::Parts}, - response::IntoResponse, -}; -use futures_util::{ - Sink, SinkExt, Stream, StreamExt, future, - future::BoxFuture, - stream::{SplitSink, SplitStream}, -}; -use tower_service::Service; - -/// A GraphQL protocol extractor. -/// -/// It extract GraphQL protocol from `SEC_WEBSOCKET_PROTOCOL` header. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct GraphQLProtocol(WebSocketProtocols); - -impl FromRequestParts for GraphQLProtocol -where - S: Send + Sync, -{ - type Rejection = StatusCode; - - async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { - parts - .headers - .get(http::header::SEC_WEBSOCKET_PROTOCOL) - .and_then(|value| value.to_str().ok()) - .and_then(|protocols| { - protocols - .split(',') - .find_map(|p| WebSocketProtocols::from_str(p.trim()).ok()) - }) - .map(Self) - .ok_or(StatusCode::BAD_REQUEST) - } -} - -/// A GraphQL subscription service. -pub struct GraphQLSubscription { - executor: E, -} - -impl Clone for GraphQLSubscription -where - E: Executor, -{ - fn clone(&self) -> Self { - Self { - executor: self.executor.clone(), - } - } -} - -impl GraphQLSubscription -where - E: Executor, -{ - /// Create a GraphQL subscription service. - pub fn new(executor: E) -> Self { - Self { executor } - } -} - -impl Service> for GraphQLSubscription -where - B: HttpBody + Send + 'static, - E: Executor, -{ - type Response = Response; - type Error = Infallible; - type Future = BoxFuture<'static, Result>; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: Request) -> Self::Future { - let executor = self.executor.clone(); - - Box::pin(async move { - let (mut parts, _body) = req.into_parts(); - - let protocol = match GraphQLProtocol::from_request_parts(&mut parts, &()).await { - Ok(protocol) => protocol, - Err(err) => return Ok(err.into_response()), - }; - let upgrade = match WebSocketUpgrade::from_request_parts(&mut parts, &()).await { - Ok(protocol) => protocol, - Err(err) => return Ok(err.into_response()), - }; - - let executor = executor.clone(); - - let resp = upgrade - .protocols(ALL_WEBSOCKET_PROTOCOLS) - .on_upgrade(move |stream| { - GraphQLWebSocket::new(stream, executor, protocol).serve() - }); - Ok(resp.into_response()) - }) - } -} - -/// A Websocket connection for GraphQL subscription. -pub struct GraphQLWebSocket { - sink: Sink, - stream: Stream, - executor: E, - data: Data, - on_connection_init: OnConnInit, - on_ping: OnPing, - protocol: GraphQLProtocol, - keepalive_timeout: Option, -} - -impl - GraphQLWebSocket< - SplitSink, - SplitStream, - E, - DefaultOnConnInitType, - DefaultOnPingType, - > -where - S: Stream> + Sink, - E: Executor, -{ - /// Create a [`GraphQLWebSocket`] object. - pub fn new(stream: S, executor: E, protocol: GraphQLProtocol) -> Self { - let (sink, stream) = stream.split(); - GraphQLWebSocket::new_with_pair(sink, stream, executor, protocol) - } -} - -impl GraphQLWebSocket -where - Sink: futures_util::sink::Sink, - Stream: futures_util::stream::Stream>, - E: Executor, -{ - /// Create a [`GraphQLWebSocket`] object with sink and stream objects. - pub fn new_with_pair( - sink: Sink, - stream: Stream, - executor: E, - protocol: GraphQLProtocol, - ) -> Self { - GraphQLWebSocket { - sink, - stream, - executor, - data: Data::default(), - on_connection_init: default_on_connection_init, - on_ping: default_on_ping, - protocol, - keepalive_timeout: None, - } - } -} - -impl - GraphQLWebSocket -where - Sink: futures_util::sink::Sink, - Stream: futures_util::stream::Stream>, - E: Executor, - OnConnInit: FnOnce(serde_json::Value) -> OnConnInitFut + Send + 'static, - OnConnInitFut: Future> + Send + 'static, - OnPing: FnOnce(Option<&Data>, Option) -> OnPingFut + Clone + Send + 'static, - OnPingFut: Future>> + Send + 'static, -{ - /// Specify the initial subscription context data, usually you can get - /// something from the incoming request to create it. - #[must_use] - pub fn with_data(self, data: Data) -> Self { - Self { data, ..self } - } - - /// Specify a callback function to be called when the connection is - /// initialized. - /// - /// You can get something from the payload of [`GQL_CONNECTION_INIT` message](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init) to create [`Data`]. - /// The data returned by this callback function will be merged with the data - /// specified by [`with_data`]. - #[must_use] - pub fn on_connection_init( - self, - callback: F, - ) -> GraphQLWebSocket - where - F: FnOnce(serde_json::Value) -> R + Send + 'static, - R: Future> + Send + 'static, - { - GraphQLWebSocket { - sink: self.sink, - stream: self.stream, - executor: self.executor, - data: self.data, - on_connection_init: callback, - on_ping: self.on_ping, - protocol: self.protocol, - keepalive_timeout: self.keepalive_timeout, - } - } - - /// Specify a ping callback function. - /// - /// This function if present, will be called with the data sent by the - /// client in the [`Ping` message](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#ping). - /// - /// The function should return the data to be sent in the [`Pong` message](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#pong). - /// - /// NOTE: Only used for the `graphql-ws` protocol. - #[must_use] - pub fn on_ping(self, callback: F) -> GraphQLWebSocket - where - F: FnOnce(Option<&Data>, Option) -> R + Clone + Send + 'static, - R: Future>> + Send + 'static, - { - GraphQLWebSocket { - sink: self.sink, - stream: self.stream, - executor: self.executor, - data: self.data, - on_connection_init: self.on_connection_init, - on_ping: callback, - protocol: self.protocol, - keepalive_timeout: self.keepalive_timeout, - } - } - - /// Sets a timeout for receiving an acknowledgement of the keep-alive ping. - /// - /// If the ping is not acknowledged within the timeout, the connection will - /// be closed. - /// - /// NOTE: Only used for the `graphql-ws` protocol. - #[must_use] - pub fn keepalive_timeout(self, timeout: impl Into>) -> Self { - Self { - keepalive_timeout: timeout.into(), - ..self - } - } - - /// Processing subscription requests. - pub async fn serve(self) { - let input = self - .stream - .take_while(|res| future::ready(res.is_ok())) - .map(Result::unwrap) - .filter_map(|msg| { - if let Message::Text(_) | Message::Binary(_) = msg { - future::ready(Some(msg)) - } else { - future::ready(None) - } - }) - .map(Message::into_data); - - let stream = - async_graphql::http::WebSocket::new(self.executor.clone(), input, self.protocol.0) - .connection_data(self.data) - .on_connection_init(self.on_connection_init) - .on_ping(self.on_ping.clone()) - .keepalive_timeout(self.keepalive_timeout) - .map(|msg| match msg { - WsMessage::Text(text) => Message::Text(text.into()), - WsMessage::Close(code, status) => Message::Close(Some(CloseFrame { - code, - reason: status.into(), - })), - }); - - let sink = self.sink; - futures_util::pin_mut!(stream, sink); - - while let Some(item) = stream.next().await { - if sink.send(item).await.is_err() { - break; - } - } - } -} diff --git a/integrations/poem/Cargo.toml b/integrations/poem/Cargo.toml deleted file mode 100644 index 2fd3df2d8..000000000 --- a/integrations/poem/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -authors = ["sunli "] -categories = ["network-programming", "asynchronous"] -description = "async-graphql for poem" -documentation = "https://docs.rs/async-graphql-poem/" -edition = "2024" -homepage = "https://github.com/async-graphql/async-graphql" -keywords = ["futures", "async", "graphql", "poem"] -license = "MIT OR Apache-2.0" -name = "async-graphql-poem" -repository = "https://github.com/async-graphql/async-graphql" -version = "7.0.17" - -[dependencies] -async-graphql.workspace = true - -futures-util = { workspace = true, default-features = false } -poem = { version = "3.1.10", features = ["websocket"] } -serde_json.workspace = true -tokio-util = { workspace = true, default-features = false, features = [ - "compat", -] } -mime = "0.3.17" -tokio = { version = "1.36.0", features = ["time"] } -tokio-stream = "0.1.15" -http = "1.1.0" diff --git a/integrations/poem/LICENSE-APACHE b/integrations/poem/LICENSE-APACHE deleted file mode 100644 index f8e5e5ea0..000000000 --- a/integrations/poem/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file diff --git a/integrations/poem/LICENSE-MIT b/integrations/poem/LICENSE-MIT deleted file mode 100644 index 468cd79a8..000000000 --- a/integrations/poem/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -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. \ No newline at end of file diff --git a/integrations/poem/src/extractor.rs b/integrations/poem/src/extractor.rs deleted file mode 100644 index 291049147..000000000 --- a/integrations/poem/src/extractor.rs +++ /dev/null @@ -1,87 +0,0 @@ -use async_graphql::http::MultipartOptions; -use poem::{ - FromRequest, Request, RequestBody, Result, - error::BadRequest, - http::{Method, header}, -}; -use tokio_util::compat::TokioAsyncReadCompatExt; - -/// An extractor for GraphQL request. -/// -/// You can just use the extractor as in the example below, but I would -/// recommend using the [`GraphQL`](crate::GraphQL) endpoint because it is -/// easier to integrate. -/// -/// # Example -/// -/// ``` -/// use async_graphql::{EmptyMutation, EmptySubscription, Object, Schema}; -/// use async_graphql_poem::GraphQLRequest; -/// use poem::{ -/// EndpointExt, Route, handler, -/// middleware::AddData, -/// post, -/// web::{Data, Json}, -/// }; -/// -/// struct Query; -/// -/// #[Object] -/// impl Query { -/// async fn value(&self) -> i32 { -/// 100 -/// } -/// } -/// -/// type MySchema = Schema; -/// -/// #[handler] -/// async fn index(req: GraphQLRequest, schema: Data<&MySchema>) -> Json { -/// Json(schema.execute(req.0).await) -/// } -/// -/// let schema = Schema::new(Query, EmptyMutation, EmptySubscription); -/// let app = Route::new().at("/", post(index.with(AddData::new(schema)))); -/// ``` -pub struct GraphQLRequest(pub async_graphql::Request); - -impl<'a> FromRequest<'a> for GraphQLRequest { - async fn from_request(req: &'a Request, body: &mut RequestBody) -> Result { - Ok(GraphQLRequest( - GraphQLBatchRequest::from_request(req, body) - .await? - .0 - .into_single() - .map_err(BadRequest)?, - )) - } -} - -/// An extractor for GraphQL batch request. -pub struct GraphQLBatchRequest(pub async_graphql::BatchRequest); - -impl<'a> FromRequest<'a> for GraphQLBatchRequest { - async fn from_request(req: &'a Request, body: &mut RequestBody) -> Result { - if req.method() == Method::GET { - let req = - async_graphql::http::parse_query_string(req.uri().query().unwrap_or_default()) - .map_err(BadRequest)?; - Ok(Self(async_graphql::BatchRequest::Single(req))) - } else { - let content_type = req - .headers() - .get(header::CONTENT_TYPE) - .and_then(|value| value.to_str().ok()) - .map(ToString::to_string); - Ok(Self( - async_graphql::http::receive_batch_body( - content_type, - body.take()?.into_async_read().compat(), - MultipartOptions::default(), - ) - .await - .map_err(BadRequest)?, - )) - } - } -} diff --git a/integrations/poem/src/lib.rs b/integrations/poem/src/lib.rs deleted file mode 100644 index 67094ba89..000000000 --- a/integrations/poem/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Async-graphql integration with Poem -#![forbid(unsafe_code)] -#![warn(missing_docs)] - -mod extractor; -mod query; -mod response; -mod subscription; - -pub use extractor::{GraphQLBatchRequest, GraphQLRequest}; -pub use query::GraphQL; -pub use response::{GraphQLBatchResponse, GraphQLResponse}; -pub use subscription::{GraphQLProtocol, GraphQLSubscription, GraphQLWebSocket}; diff --git a/integrations/poem/src/query.rs b/integrations/poem/src/query.rs deleted file mode 100644 index 68807c9d9..000000000 --- a/integrations/poem/src/query.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::time::Duration; - -use async_graphql::{ - Executor, - http::{create_multipart_mixed_stream, is_accept_multipart_mixed}, -}; -use futures_util::StreamExt; -use poem::{Body, Endpoint, FromRequest, IntoResponse, Request, Response, Result}; - -use crate::{GraphQLBatchRequest, GraphQLBatchResponse, GraphQLRequest}; - -/// A GraphQL query endpoint. -/// -/// # Example -/// -/// ``` -/// use async_graphql::{EmptyMutation, EmptySubscription, Object, Schema}; -/// use async_graphql_poem::GraphQL; -/// use poem::{Route, post}; -/// -/// struct Query; -/// -/// #[Object] -/// impl Query { -/// async fn value(&self) -> i32 { -/// 100 -/// } -/// } -/// -/// type MySchema = Schema; -/// -/// let schema = Schema::new(Query, EmptyMutation, EmptySubscription); -/// let app = Route::new().at("/", post(GraphQL::new(schema))); -/// ``` -pub struct GraphQL { - executor: E, -} - -impl GraphQL { - /// Create a GraphQL endpoint. - pub fn new(executor: E) -> Self { - Self { executor } - } -} - -impl Endpoint for GraphQL -where - E: Executor, -{ - type Output = Response; - - async fn call(&self, req: Request) -> Result { - let is_accept_multipart_mixed = req - .header("accept") - .map(is_accept_multipart_mixed) - .unwrap_or_default(); - - if is_accept_multipart_mixed { - let (req, mut body) = req.split(); - let req = GraphQLRequest::from_request(&req, &mut body).await?; - let stream = self.executor.execute_stream(req.0, None); - Ok(Response::builder() - .header("content-type", "multipart/mixed; boundary=graphql") - .body(Body::from_bytes_stream( - create_multipart_mixed_stream(stream, Duration::from_secs(30)) - .map(Ok::<_, std::io::Error>), - ))) - } else { - let (req, mut body) = req.split(); - let req = GraphQLBatchRequest::from_request(&req, &mut body).await?; - Ok(GraphQLBatchResponse(self.executor.execute_batch(req.0).await).into_response()) - } - } -} diff --git a/integrations/poem/src/response.rs b/integrations/poem/src/response.rs deleted file mode 100644 index d731f59b0..000000000 --- a/integrations/poem/src/response.rs +++ /dev/null @@ -1,50 +0,0 @@ -use std::str::FromStr; - -use http::{HeaderName, HeaderValue}; -use poem::{IntoResponse, Response, web::Json}; - -/// Response for `async_graphql::Request`. -pub struct GraphQLResponse(pub async_graphql::Response); - -impl From for GraphQLResponse { - fn from(resp: async_graphql::Response) -> Self { - Self(resp) - } -} - -impl IntoResponse for GraphQLResponse { - fn into_response(self) -> Response { - GraphQLBatchResponse(self.0.into()).into_response() - } -} - -/// Response for `async_graphql::BatchRequest`. -pub struct GraphQLBatchResponse(pub async_graphql::BatchResponse); - -impl From for GraphQLBatchResponse { - fn from(resp: async_graphql::BatchResponse) -> Self { - Self(resp) - } -} - -impl IntoResponse for GraphQLBatchResponse { - fn into_response(self) -> Response { - let mut resp = Json(&self.0).into_response(); - - if self.0.is_ok() { - if let Some(cache_control) = self.0.cache_control().value() { - if let Ok(value) = cache_control.try_into() { - resp.headers_mut().insert("cache-control", value); - } - } - } - - resp.headers_mut() - .extend(self.0.http_headers().iter().filter_map(|(name, value)| { - HeaderName::from_str(name.as_str()) - .ok() - .zip(HeaderValue::from_bytes(value.as_bytes()).ok()) - })); - resp - } -} diff --git a/integrations/poem/src/subscription.rs b/integrations/poem/src/subscription.rs deleted file mode 100644 index c2ef7f01f..000000000 --- a/integrations/poem/src/subscription.rs +++ /dev/null @@ -1,283 +0,0 @@ -use std::{io::Error as IoError, str::FromStr, time::Duration}; - -use async_graphql::{ - Data, Executor, - http::{ - ALL_WEBSOCKET_PROTOCOLS, DefaultOnConnInitType, DefaultOnPingType, WebSocketProtocols, - WsMessage, default_on_connection_init, default_on_ping, - }, -}; -use futures_util::{ - Future, Sink, SinkExt, Stream, StreamExt, - future::{self}, - stream::{SplitSink, SplitStream}, -}; -use poem::{ - Endpoint, Error, FromRequest, IntoResponse, Request, RequestBody, Response, Result, - http::StatusCode, - web::websocket::{Message, WebSocket}, -}; - -/// A GraphQL protocol extractor. -/// -/// It extract GraphQL protocol from `SEC_WEBSOCKET_PROTOCOL` header. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct GraphQLProtocol(pub WebSocketProtocols); - -impl<'a> FromRequest<'a> for GraphQLProtocol { - async fn from_request(req: &'a Request, _body: &mut RequestBody) -> Result { - req.headers() - .get(http::header::SEC_WEBSOCKET_PROTOCOL) - .and_then(|value| value.to_str().ok()) - .and_then(|protocols| { - protocols - .split(',') - .find_map(|p| WebSocketProtocols::from_str(p.trim()).ok()) - }) - .map(Self) - .ok_or_else(|| Error::from_status(StatusCode::BAD_REQUEST)) - } -} - -/// A GraphQL subscription endpoint. -/// -/// # Example -/// -/// ``` -/// use async_graphql::{EmptyMutation, Object, Schema, Subscription}; -/// use async_graphql_poem::GraphQLSubscription; -/// use futures_util::{Stream, stream}; -/// use poem::{Route, get}; -/// -/// struct Query; -/// -/// #[Object] -/// impl Query { -/// async fn value(&self) -> i32 { -/// 100 -/// } -/// } -/// -/// struct Subscription; -/// -/// #[Subscription] -/// impl Subscription { -/// async fn values(&self) -> impl Stream { -/// stream::iter(vec![1, 2, 3, 4, 5]) -/// } -/// } -/// -/// type MySchema = Schema; -/// -/// let schema = Schema::new(Query, EmptyMutation, Subscription); -/// let app = Route::new().at("/ws", get(GraphQLSubscription::new(schema))); -/// ``` -pub struct GraphQLSubscription { - executor: E, -} - -impl GraphQLSubscription { - /// Create a GraphQL subscription endpoint. - pub fn new(executor: E) -> Self { - Self { executor } - } -} - -impl Endpoint for GraphQLSubscription -where - E: Executor, -{ - type Output = Response; - - async fn call(&self, req: Request) -> Result { - let (req, mut body) = req.split(); - let websocket = WebSocket::from_request(&req, &mut body).await?; - let protocol = GraphQLProtocol::from_request(&req, &mut body).await?; - let executor = self.executor.clone(); - - let resp = websocket - .protocols(ALL_WEBSOCKET_PROTOCOLS) - .on_upgrade(move |stream| GraphQLWebSocket::new(stream, executor, protocol).serve()) - .into_response(); - Ok(resp) - } -} - -/// A Websocket connection for GraphQL subscription. -pub struct GraphQLWebSocket { - sink: Sink, - stream: Stream, - executor: E, - data: Data, - on_connection_init: OnConnInit, - on_ping: OnPing, - protocol: GraphQLProtocol, - keepalive_timeout: Option, -} - -impl - GraphQLWebSocket< - SplitSink, - SplitStream, - E, - DefaultOnConnInitType, - DefaultOnPingType, - > -where - S: Stream> + Sink, - E: Executor, -{ - /// Create a [`GraphQLWebSocket`] object. - pub fn new(stream: S, executor: E, protocol: GraphQLProtocol) -> Self { - let (sink, stream) = stream.split(); - GraphQLWebSocket::new_with_pair(sink, stream, executor, protocol) - } -} - -impl GraphQLWebSocket -where - Sink: futures_util::sink::Sink, - Stream: futures_util::stream::Stream>, - E: Executor, -{ - /// Create a [`GraphQLWebSocket`] object with sink and stream objects. - pub fn new_with_pair( - sink: Sink, - stream: Stream, - executor: E, - protocol: GraphQLProtocol, - ) -> Self { - GraphQLWebSocket { - sink, - stream, - executor, - data: Data::default(), - on_connection_init: default_on_connection_init, - on_ping: default_on_ping, - protocol, - keepalive_timeout: None, - } - } -} - -impl - GraphQLWebSocket -where - Sink: futures_util::sink::Sink, - Stream: futures_util::stream::Stream>, - E: Executor, - OnConnInit: FnOnce(serde_json::Value) -> OnConnInitFut + Send + 'static, - OnConnInitFut: Future> + Send + 'static, - OnPing: FnOnce(Option<&Data>, Option) -> OnPingFut + Clone + Send + 'static, - OnPingFut: Future>> + Send + 'static, -{ - /// Specify the initial subscription context data, usually you can get - /// something from the incoming request to create it. - #[must_use] - pub fn with_data(self, data: Data) -> Self { - Self { data, ..self } - } - - /// Specify a callback function to be called when the connection is - /// initialized. - /// - /// You can get something from the payload of [`GQL_CONNECTION_INIT` message](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init) to create [`Data`]. - /// The data returned by this callback function will be merged with the data - /// specified by [`with_data`]. - #[must_use] - pub fn on_connection_init( - self, - callback: F, - ) -> GraphQLWebSocket - where - F: FnOnce(serde_json::Value) -> R + Send + 'static, - R: Future> + Send + 'static, - { - GraphQLWebSocket { - sink: self.sink, - stream: self.stream, - executor: self.executor, - data: self.data, - on_connection_init: callback, - on_ping: self.on_ping, - protocol: self.protocol, - keepalive_timeout: self.keepalive_timeout, - } - } - - /// Specify a ping callback function. - /// - /// This function if present, will be called with the data sent by the - /// client in the [`Ping` message](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#ping). - /// - /// The function should return the data to be sent in the [`Pong` message](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#pong). - /// - /// NOTE: Only used for the `graphql-ws` protocol. - #[must_use] - pub fn on_ping(self, callback: F) -> GraphQLWebSocket - where - F: FnOnce(Option<&Data>, Option) -> R + Clone + Send + 'static, - R: Future>> + Send + 'static, - { - GraphQLWebSocket { - sink: self.sink, - stream: self.stream, - executor: self.executor, - data: self.data, - on_connection_init: self.on_connection_init, - on_ping: callback, - protocol: self.protocol, - keepalive_timeout: self.keepalive_timeout, - } - } - - /// Sets a timeout for receiving an acknowledgement of the keep-alive ping. - /// - /// If the ping is not acknowledged within the timeout, the connection will - /// be closed. - /// - /// NOTE: Only used for the `graphql-ws` protocol. - #[must_use] - pub fn keepalive_timeout(self, timeout: impl Into>) -> Self { - Self { - keepalive_timeout: timeout.into(), - ..self - } - } - - /// Processing subscription requests. - pub async fn serve(self) { - let stream = self - .stream - .take_while(|res| future::ready(res.is_ok())) - .map(Result::unwrap) - .filter_map(|msg| { - if msg.is_text() || msg.is_binary() { - future::ready(Some(msg)) - } else { - future::ready(None) - } - }) - .map(Message::into_bytes); - - let stream = - async_graphql::http::WebSocket::new(self.executor.clone(), stream, self.protocol.0) - .connection_data(self.data) - .on_connection_init(self.on_connection_init) - .on_ping(self.on_ping.clone()) - .keepalive_timeout(self.keepalive_timeout) - .map(|msg| match msg { - WsMessage::Text(text) => Message::text(text), - WsMessage::Close(code, status) => Message::close_with(code, status), - }); - - let sink = self.sink; - futures_util::pin_mut!(stream, sink); - - while let Some(item) = stream.next().await { - if sink.send(item).await.is_err() { - break; - } - } - } -} diff --git a/integrations/rocket/Cargo.toml b/integrations/rocket/Cargo.toml deleted file mode 100644 index b89bf34b0..000000000 --- a/integrations/rocket/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -authors = ["Daniel Wiesenberg "] -categories = ["network-programming", "asynchronous"] -description = "async-graphql for Rocket.rs" -documentation = "https://docs.rs/async-graphql/" -edition = "2024" -homepage = "https://github.com/async-graphql/async-graphql" -keywords = ["futures", "async", "graphql", "rocket"] -license = "MIT OR Apache-2.0" -name = "async-graphql-rocket" -repository = "https://github.com/async-graphql/async-graphql" -version = "7.0.17" - -[dependencies] -async-graphql.workspace = true - -rocket = { version = "0.5.0", default-features = false } -serde.workspace = true -serde_json.workspace = true -tokio-util = { workspace = true, default-features = false, features = [ - "compat", -] } diff --git a/integrations/rocket/LICENSE-APACHE b/integrations/rocket/LICENSE-APACHE deleted file mode 100644 index f8e5e5ea0..000000000 --- a/integrations/rocket/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file diff --git a/integrations/rocket/LICENSE-MIT b/integrations/rocket/LICENSE-MIT deleted file mode 100644 index 468cd79a8..000000000 --- a/integrations/rocket/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -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. \ No newline at end of file diff --git a/integrations/rocket/src/lib.rs b/integrations/rocket/src/lib.rs deleted file mode 100644 index d2a690807..000000000 --- a/integrations/rocket/src/lib.rs +++ /dev/null @@ -1,216 +0,0 @@ -//! Async-graphql integration with Rocket. -//! -//! Note: This integrates with the unreleased version 0.5 of Rocket, and so -//! breaking changes in both this library and Rocket are to be expected. -//! -//! To configure options for sending and receiving multipart requests, add your -//! instance of `MultipartOptions` to the state managed by Rocket -//! (`.manage(your_multipart_options)`). -//! -//! **[Full Example]()** - -#![warn(missing_docs)] -#![forbid(unsafe_code)] -#![allow(clippy::blocks_in_conditions)] - -use core::any::Any; -use std::io::Cursor; - -use async_graphql::{Executor, ParseRequestError, http::MultipartOptions}; -use rocket::{ - data::{self, Data, FromData, ToByteUnit}, - form::FromForm, - http::{ContentType, Header, Status}, - response::{self, Responder}, -}; -use tokio_util::compat::TokioAsyncReadCompatExt; - -/// A batch request which can be extracted from a request's body. -/// -/// # Examples -/// -/// ```ignore -/// #[rocket::post("/graphql", data = "", format = "application/json", rank = 1)] -/// async fn graphql_request(schema: State<'_, ExampleSchema>, request: BatchRequest) -> Response { -/// request.execute(&schema).await -/// } -/// ``` -#[derive(Debug)] -pub struct GraphQLBatchRequest(pub async_graphql::BatchRequest); - -impl GraphQLBatchRequest { - /// Shortcut method to execute the request on the executor. - pub async fn execute(self, executor: &E) -> GraphQLResponse - where - E: Executor, - { - GraphQLResponse(executor.execute_batch(self.0).await) - } -} - -#[rocket::async_trait] -impl<'r> FromData<'r> for GraphQLBatchRequest { - type Error = ParseRequestError; - - async fn from_data(req: &'r rocket::Request<'_>, data: Data<'r>) -> data::Outcome<'r, Self> { - let opts: MultipartOptions = req.rocket().state().copied().unwrap_or_default(); - - let request = async_graphql::http::receive_batch_body( - req.headers().get_one("Content-Type"), - data.open( - req.limits() - .get("graphql") - .unwrap_or_else(|| 128.kibibytes()), - ) - .compat(), - opts, - ) - .await; - - match request { - Ok(request) => data::Outcome::Success(Self(request)), - Err(e) => data::Outcome::Error(( - match e { - ParseRequestError::PayloadTooLarge => Status::PayloadTooLarge, - _ => Status::BadRequest, - }, - e, - )), - } - } -} - -/// A GraphQL request which can be extracted from the request's body. -/// -/// # Examples -/// -/// ```ignore -/// #[rocket::post("/graphql", data = "", format = "application/json", rank = 2)] -/// async fn graphql_request(schema: State<'_, ExampleSchema>, request: Request) -> Result { -/// request.execute(&schema).await -/// } -/// ``` -#[derive(Debug)] -pub struct GraphQLRequest(pub async_graphql::Request); - -impl GraphQLRequest { - /// Shortcut method to execute the request on the schema. - pub async fn execute(self, executor: &E) -> GraphQLResponse - where - E: Executor, - { - GraphQLResponse(executor.execute(self.0).await.into()) - } - - /// Insert some data for this request. - #[must_use] - pub fn data(mut self, data: D) -> Self { - self.0.data.insert(data); - self - } -} - -impl From for GraphQLRequest { - fn from(query: GraphQLQuery) -> Self { - let mut request = async_graphql::Request::new(query.query); - - if let Some(operation_name) = query.operation_name { - request = request.operation_name(operation_name); - } - - if let Some(variables) = query.variables { - let value = serde_json::from_str(&variables).unwrap_or_default(); - let variables = async_graphql::Variables::from_json(value); - request = request.variables(variables); - } - - GraphQLRequest(request) - } -} - -/// A GraphQL request which can be extracted from a query string. -/// -/// # Examples -/// -/// ```ignore -/// #[rocket::get("/graphql?")] -/// async fn graphql_query(schema: State<'_, ExampleSchema>, query: Query) -> Result { -/// query.execute(&schema).await -/// } -/// ``` -#[derive(FromForm, Debug)] -pub struct GraphQLQuery { - query: String, - #[field(name = "operationName")] - operation_name: Option, - variables: Option, -} - -impl GraphQLQuery { - /// Shortcut method to execute the request on the schema. - pub async fn execute(self, executor: &E) -> GraphQLResponse - where - E: Executor, - { - let request: GraphQLRequest = self.into(); - request.execute(executor).await - } -} - -#[rocket::async_trait] -impl<'r> FromData<'r> for GraphQLRequest { - type Error = ParseRequestError; - - async fn from_data(req: &'r rocket::Request<'_>, data: Data<'r>) -> data::Outcome<'r, Self> { - GraphQLBatchRequest::from_data(req, data) - .await - .and_then(|request| match request.0.into_single() { - Ok(single) => data::Outcome::Success(Self(single)), - Err(e) => data::Outcome::Error((Status::BadRequest, e)), - }) - } -} - -/// Wrapper around `async-graphql::Response` that is a Rocket responder so it -/// can be returned from a routing function in Rocket. -/// -/// It contains a `BatchResponse` but since a response is a type of batch -/// response it works for both. -#[derive(Debug)] -pub struct GraphQLResponse(pub async_graphql::BatchResponse); - -impl From for GraphQLResponse { - fn from(batch: async_graphql::BatchResponse) -> Self { - Self(batch) - } -} -impl From for GraphQLResponse { - fn from(res: async_graphql::Response) -> Self { - Self(res.into()) - } -} - -impl<'r> Responder<'r, 'static> for GraphQLResponse { - fn respond_to(self, _: &'r rocket::Request<'_>) -> response::Result<'static> { - let body = serde_json::to_string(&self.0).unwrap(); - - let mut response = rocket::Response::new(); - response.set_header(ContentType::new("application", "json")); - - if self.0.is_ok() { - if let Some(cache_control) = self.0.cache_control().value() { - response.set_header(Header::new("cache-control", cache_control)); - } - } - - for (name, value) in self.0.http_headers_iter() { - if let Ok(value) = value.to_str() { - response.adjoin_header(Header::new(name.as_str().to_string(), value.to_string())); - } - } - - response.set_sized_body(body.len(), Cursor::new(body)); - - Ok(response) - } -} diff --git a/integrations/tide/Cargo.toml b/integrations/tide/Cargo.toml deleted file mode 100644 index c432a0f6a..000000000 --- a/integrations/tide/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -authors = ["vkill ", "sunli "] -categories = ["network-programming", "asynchronous"] -description = "async-graphql for tide" -documentation = "https://docs.rs/async-graphql-tide/" -edition = "2024" -homepage = "https://github.com/async-graphql/async-graphql" -keywords = ["futures", "async", "graphql"] -license = "MIT OR Apache-2.0" -name = "async-graphql-tide" -repository = "https://github.com/async-graphql/async-graphql" -version = "7.0.17" - -[features] -default = ["websocket"] -websocket = ["tide-websockets"] - -[dependencies] -async-graphql = { workspace = true } - -async-trait.workspace = true -futures-util.workspace = true -serde_json.workspace = true - -tide = { version = "0.16.0", default-features = false, features = [ - "h1-server", -] } -tide-websockets = { version = "0.4.0", optional = true } - -[dev-dependencies] -# Surf lacks multipart support -async-std = { version = "1.12.0", features = ["attributes", "tokio1"] } -reqwest = { version = "0.12.1", default-features = false, features = [ - "json", - "multipart", -] } -serde_json.workspace = true diff --git a/integrations/tide/LICENSE-APACHE b/integrations/tide/LICENSE-APACHE deleted file mode 100644 index f8e5e5ea0..000000000 --- a/integrations/tide/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file diff --git a/integrations/tide/LICENSE-MIT b/integrations/tide/LICENSE-MIT deleted file mode 100644 index 468cd79a8..000000000 --- a/integrations/tide/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -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. \ No newline at end of file diff --git a/integrations/tide/src/lib.rs b/integrations/tide/src/lib.rs deleted file mode 100644 index 6b244ac4a..000000000 --- a/integrations/tide/src/lib.rs +++ /dev/null @@ -1,176 +0,0 @@ -//! Async-graphql integration with Tide -//! -//! # Examples -//! *[Full Example]()* - -#![warn(missing_docs)] -#![allow(clippy::type_complexity)] -#![allow(clippy::needless_doctest_main)] -#![forbid(unsafe_code)] - -#[cfg(feature = "websocket")] -mod subscription; - -use async_graphql::{Executor, ParseRequestError, http::MultipartOptions}; -#[cfg(feature = "websocket")] -pub use subscription::GraphQLSubscription; -use tide::{ - Body, Request, Response, StatusCode, - http::{ - Method, - headers::{self, HeaderValue}, - }, - utils::async_trait, -}; - -/// Create a new GraphQL endpoint with the executor. -/// -/// Default multipart options are used and batch operations are supported. -pub fn graphql(executor: E) -> GraphQLEndpoint { - GraphQLEndpoint { - executor, - opts: MultipartOptions::default(), - batch: true, - } -} - -/// A GraphQL endpoint. -/// -/// This is created with the [`endpoint`](fn.endpoint.html) function. -#[non_exhaustive] -pub struct GraphQLEndpoint { - /// The graphql executor - pub executor: E, - /// The multipart options of the endpoint. - pub opts: MultipartOptions, - /// Whether to support batch requests in the endpoint. - pub batch: bool, -} - -impl GraphQLEndpoint { - /// Set the multipart options of the endpoint. - #[must_use] - pub fn multipart_opts(self, opts: MultipartOptions) -> Self { - Self { opts, ..self } - } - /// Set whether batch requests are supported in the endpoint. - #[must_use] - pub fn batch(self, batch: bool) -> Self { - Self { batch, ..self } - } -} - -// Manual impl to remove bounds on generics -impl Clone for GraphQLEndpoint { - fn clone(&self) -> Self { - Self { - executor: self.executor.clone(), - opts: self.opts, - batch: self.batch, - } - } -} - -#[async_trait] -impl tide::Endpoint for GraphQLEndpoint -where - E: Executor, - TideState: Clone + Send + Sync + 'static, -{ - async fn call(&self, request: Request) -> tide::Result { - respond( - self.executor - .execute_batch(if self.batch { - receive_batch_request_opts(request, self.opts).await - } else { - receive_request_opts(request, self.opts) - .await - .map(Into::into) - }?) - .await, - ) - } -} - -/// Convert a Tide request to a GraphQL request. -pub async fn receive_request( - request: Request, -) -> tide::Result { - receive_request_opts(request, Default::default()).await -} - -/// Convert a Tide request to a GraphQL request with options on how to receive -/// multipart. -pub async fn receive_request_opts( - request: Request, - opts: MultipartOptions, -) -> tide::Result { - receive_batch_request_opts(request, opts) - .await? - .into_single() - .map_err(|e| tide::Error::new(StatusCode::BadRequest, e)) -} - -/// Convert a Tide request to a GraphQL batch request. -pub async fn receive_batch_request( - request: Request, -) -> tide::Result { - receive_batch_request_opts(request, Default::default()).await -} - -/// Convert a Tide request to a GraphQL batch request with options on how to -/// receive multipart. -pub async fn receive_batch_request_opts( - mut request: Request, - opts: MultipartOptions, -) -> tide::Result { - if request.method() == Method::Get { - async_graphql::http::parse_query_string(request.url().query().unwrap_or_default()) - .map(Into::into) - .map_err(|err| tide::Error::new(StatusCode::BadRequest, err)) - } else if request.method() == Method::Post { - let body = request.take_body(); - let content_type = request - .header(headers::CONTENT_TYPE) - .and_then(|values| values.get(0)) - .map(HeaderValue::as_str); - - async_graphql::http::receive_batch_body(content_type, body, opts) - .await - .map_err(|e| { - tide::Error::new( - match &e { - ParseRequestError::PayloadTooLarge => StatusCode::PayloadTooLarge, - _ => StatusCode::BadRequest, - }, - e, - ) - }) - } else { - Err(tide::Error::from_str( - StatusCode::MethodNotAllowed, - "GraphQL only supports GET and POST requests", - )) - } -} - -/// Convert a GraphQL response to a Tide response. -pub fn respond(resp: impl Into) -> tide::Result { - let resp = resp.into(); - - let mut response = Response::new(StatusCode::Ok); - if resp.is_ok() { - if let Some(cache_control) = resp.cache_control().value() { - response.insert_header(headers::CACHE_CONTROL, cache_control); - } - } - - for (name, value) in resp.http_headers_iter() { - if let Ok(value) = value.to_str() { - response.append_header(name.as_str(), value); - } - } - - response.set_body(Body::from_json(&resp)?); - Ok(response) -} diff --git a/integrations/tide/src/subscription.rs b/integrations/tide/src/subscription.rs deleted file mode 100644 index 78051a642..000000000 --- a/integrations/tide/src/subscription.rs +++ /dev/null @@ -1,169 +0,0 @@ -use std::{future::Future, str::FromStr, time::Duration}; - -use async_graphql::{ - Data, Executor, Result, - http::{ - ALL_WEBSOCKET_PROTOCOLS, DefaultOnConnInitType, DefaultOnPingType, - WebSocket as AGWebSocket, WebSocketProtocols, WsMessage, default_on_connection_init, - default_on_ping, - }, -}; -use futures_util::{StreamExt, future}; -use tide::Endpoint; -use tide_websockets::{Message, tungstenite::protocol::CloseFrame}; - -/// A GraphQL subscription endpoint builder. -#[cfg_attr(docsrs, doc(cfg(feature = "websocket")))] -pub struct GraphQLSubscription { - executor: E, - on_connection_init: OnConnInit, - on_ping: OnPing, - keepalive_timeout: Option, -} - -impl GraphQLSubscription -where - E: Executor, -{ - /// Create a [`GraphQLSubscription`] object. - pub fn new(executor: E) -> Self { - GraphQLSubscription { - executor, - on_connection_init: default_on_connection_init, - on_ping: default_on_ping, - keepalive_timeout: None, - } - } -} - -impl GraphQLSubscription -where - E: Executor, - OnConnInit: Fn(serde_json::Value) -> OnConnInitFut + Clone + Send + Sync + 'static, - OnConnInitFut: Future> + Send + 'static, - OnPing: FnOnce(Option<&Data>, Option) -> OnPingFut - + Clone - + Send - + Sync - + 'static, - OnPingFut: Future>> + Send + 'static, -{ - /// Specify a callback function to be called when the connection is - /// initialized. - /// - /// You can get something from the payload of [`GQL_CONNECTION_INIT` message](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init) to create [`Data`]. - /// The data returned by this callback function will be merged with the data - /// specified by [`with_data`]. - #[must_use] - pub fn on_connection_init(self, callback: F) -> GraphQLSubscription - where - F: Fn(serde_json::Value) -> R + Clone + Send + Sync + 'static, - R: Future> + Send + 'static, - { - GraphQLSubscription { - executor: self.executor, - on_connection_init: callback, - on_ping: self.on_ping, - keepalive_timeout: self.keepalive_timeout, - } - } - - /// Specify a ping callback function. - /// - /// This function if present, will be called with the data sent by the - /// client in the [`Ping` message](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#ping). - /// - /// The function should return the data to be sent in the [`Pong` message](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#pong). - /// - /// NOTE: Only used for the `graphql-ws` protocol. - #[must_use] - pub fn on_ping(self, callback: F) -> GraphQLSubscription - where - F: FnOnce(Option<&Data>, Option) -> R + Send + Sync + Clone + 'static, - R: Future>> + Send + 'static, - { - GraphQLSubscription { - executor: self.executor, - on_connection_init: self.on_connection_init, - on_ping: callback, - keepalive_timeout: self.keepalive_timeout, - } - } - - /// Sets a timeout for receiving an acknowledgement of the keep-alive ping. - /// - /// If the ping is not acknowledged within the timeout, the connection will - /// be closed. - /// - /// NOTE: Only used for the `graphql-ws` protocol. - #[must_use] - pub fn keepalive_timeout(self, timeout: impl Into>) -> Self { - Self { - keepalive_timeout: timeout.into(), - ..self - } - } - - /// Consumes this builder to create a tide endpoint. - pub fn build(self) -> impl Endpoint - where - S: Send + Sync + Clone + 'static, - { - tide_websockets::WebSocket::::new(move |request, connection| { - let executor = self.executor.clone(); - let on_connection_init = self.on_connection_init.clone(); - let on_ping = self.on_ping.clone(); - async move { - let protocol = match request - .header("sec-websocket-protocol") - .map(|value| value.as_str()) - .and_then(|protocols| { - protocols - .split(',') - .find_map(|p| WebSocketProtocols::from_str(p.trim()).ok()) - }) { - Some(protocol) => protocol, - None => { - // default to the prior standard - WebSocketProtocols::SubscriptionsTransportWS - } - }; - - let sink = connection.clone(); - let mut stream = AGWebSocket::new( - executor.clone(), - connection - .take_while(|msg| future::ready(msg.is_ok())) - .map(Result::unwrap) - .map(Message::into_data), - protocol, - ) - .on_connection_init(on_connection_init) - .on_ping(on_ping) - .keepalive_timeout(self.keepalive_timeout); - - while let Some(data) = stream.next().await { - match data { - WsMessage::Text(text) => { - if sink.send_string(text).await.is_err() { - break; - } - } - WsMessage::Close(code, msg) => { - let _ = sink - .send(Message::Close(Some(CloseFrame { - code: code.into(), - reason: msg.into(), - }))) - .await; - break; - } - } - } - - Ok(()) - } - }) - .with_protocols(&ALL_WEBSOCKET_PROTOCOLS) - } -} diff --git a/integrations/tide/tests/graphql.rs b/integrations/tide/tests/graphql.rs deleted file mode 100644 index 73b7923b1..000000000 --- a/integrations/tide/tests/graphql.rs +++ /dev/null @@ -1,219 +0,0 @@ -#![allow(clippy::uninlined_format_args)] - -mod test_utils; - -use std::io::Read; - -use async_graphql::*; -use reqwest::{StatusCode, header}; -use serde_json::json; - -type Result = std::result::Result>; - -#[async_std::test] -async fn quickstart() -> Result<()> { - let listen_addr = "127.0.0.1:8081"; - - async_std::task::spawn(async move { - struct QueryRoot; - #[Object] - impl QueryRoot { - /// Returns the sum of a and b - async fn add(&self, a: i32, b: i32) -> i32 { - a + b - } - } - - let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription).finish(); - - let mut app = tide::new(); - let endpoint = async_graphql_tide::graphql(schema); - app.at("/").post(endpoint.clone()).get(endpoint); - app.listen(listen_addr).await - }); - - test_utils::wait_server_ready().await; - - let client = test_utils::client(); - - let resp = client - .post(format!("http://{}", listen_addr)) - .json(&json!({"query":"{ add(a: 10, b: 20) }"})) - .send() - .await?; - - assert_eq!(resp.status(), StatusCode::OK); - let string = resp.text().await?; - println!("via post {}", string); - - assert_eq!(string, json!({"data": {"add": 30}}).to_string()); - - let resp = client - .get(format!("http://{}", listen_addr)) - .query(&[("query", "{ add(a: 10, b: 20) }")]) - .send() - .await?; - - assert_eq!(resp.status(), StatusCode::OK); - let string = resp.text().await?; - println!("via get {}", string); - - assert_eq!(string, json!({"data": {"add": 30}}).to_string()); - - Ok(()) -} - -#[async_std::test] -async fn hello() -> Result<()> { - let listen_addr = "127.0.0.1:8082"; - - async_std::task::spawn(async move { - struct Hello(String); - struct QueryRoot; - #[Object] - impl QueryRoot { - /// Returns hello - async fn hello<'a>(&self, ctx: &'a Context<'_>) -> String { - let name = ctx.data_opt::().map(|hello| hello.0.as_str()); - format!("Hello, {}!", name.unwrap_or("world")) - } - } - - let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription).finish(); - - let mut app = tide::new(); - - app.at("/").post(move |req: tide::Request<()>| { - let schema = schema.clone(); - async move { - let name = req - .header("name") - .and_then(|values| values.get(0)) - .map(ToString::to_string); - let mut req = async_graphql_tide::receive_request(req).await?; - if let Some(name) = name { - req = req.data(Hello(name)); - } - async_graphql_tide::respond(schema.execute(req).await) - } - }); - app.listen(listen_addr).await - }); - - test_utils::wait_server_ready().await; - - let client = test_utils::client(); - - let resp = client - .post(format!("http://{}", listen_addr)) - .json(&json!({"query":"{ hello }"})) - .header("Name", "Foo") - .send() - .await?; - - assert_eq!(resp.status(), StatusCode::OK); - let string = resp.text().await?; - println!("{}", string); - - assert_eq!(string, json!({"data":{"hello":"Hello, Foo!"}}).to_string()); - - let resp = client - .post(format!("http://{}", listen_addr)) - .json(&json!({"query":"{ hello }"})) - .header(header::CONTENT_TYPE, "application/json") - .send() - .await?; - - assert_eq!(resp.status(), StatusCode::OK); - let string = resp.text().await?; - println!("{}", string); - - assert_eq!( - string, - json!({"data":{"hello":"Hello, world!"}}).to_string() - ); - - Ok(()) -} - -#[async_std::test] -async fn upload() -> Result<()> { - let listen_addr = "127.0.0.1:8083"; - - async_std::task::spawn(async move { - struct QueryRoot; - - #[Object] - impl QueryRoot { - async fn value(&self) -> i32 { - 10 - } - } - - #[derive(Clone, SimpleObject)] - pub struct FileInfo { - filename: String, - mime_type: Option, - } - - struct MutationRoot; - #[Object] - impl MutationRoot { - async fn single_upload(&self, ctx: &Context<'_>, file: Upload) -> FileInfo { - let upload_value = file.value(ctx).unwrap(); - println!("single_upload: filename={}", upload_value.filename); - println!( - "single_upload: content_type={:?}", - upload_value.content_type - ); - - let file_info = FileInfo { - filename: upload_value.filename.clone(), - mime_type: upload_value.content_type.clone(), - }; - - let mut content = String::new(); - upload_value - .into_read() - .read_to_string(&mut content) - .unwrap(); - assert_eq!(content, "test".to_owned()); - - file_info - } - } - - let schema = Schema::build(QueryRoot, MutationRoot, EmptySubscription).finish(); - - let mut app = tide::new(); - app.at("/").post(async_graphql_tide::graphql(schema)); - app.listen(listen_addr).await - }); - - test_utils::wait_server_ready().await; - - let client = test_utils::client(); - - let form = reqwest::multipart::Form::new() - .text("operations", r#"{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { filename, mimeType } }", "variables": { "file": null } }"#) - .text("map", r#"{ "0": ["variables.file"] }"#) - .part("0", reqwest::multipart::Part::stream("test").file_name("test.txt").mime_str("text/plain")?); - - let resp = client - .post(format!("http://{}", listen_addr)) - .multipart(form) - .send() - .await?; - - assert_eq!(resp.status(), StatusCode::OK); - let string = resp.text().await?; - println!("{}", string); - - assert_eq!( - string, - json!({"data": {"singleUpload": {"filename": "test.txt", "mimeType": "text/plain"}}}) - .to_string() - ); - - Ok(()) -} diff --git a/integrations/tide/tests/test_utils.rs b/integrations/tide/tests/test_utils.rs deleted file mode 100644 index 2cf2bf145..000000000 --- a/integrations/tide/tests/test_utils.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::time::Duration; - -use reqwest::Client; - -pub fn client() -> Client { - Client::builder().no_proxy().build().unwrap() -} - -pub async fn wait_server_ready() { - async_std::task::sleep(Duration::from_secs(1)).await; -} diff --git a/integrations/warp/Cargo.toml b/integrations/warp/Cargo.toml deleted file mode 100644 index 3b06a9545..000000000 --- a/integrations/warp/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -authors = ["sunli ", "Koxiaet"] -categories = ["network-programming", "asynchronous"] -description = "async-graphql for warp" -documentation = "https://docs.rs/async-graphql-warp/" -edition = "2024" -homepage = "https://github.com/async-graphql/async-graphql" -keywords = ["futures", "async", "graphql"] -license = "MIT OR Apache-2.0" -name = "async-graphql-warp" -repository = "https://github.com/async-graphql/async-graphql" -version = "7.0.17" - -[dependencies] -async-graphql.workspace = true - -futures-util = { workspace = true, default-features = false, features = [ - "sink", -] } -serde_json.workspace = true -warp = { version = "0.3.6", default-features = false, features = ["websocket"] } - -[dev-dependencies] -async-stream = "0.3.5" -tokio = { version = "1.36.0", default-features = false, features = [ - "macros", - "rt-multi-thread", - "time", -] } diff --git a/integrations/warp/LICENSE-APACHE b/integrations/warp/LICENSE-APACHE deleted file mode 100644 index f8e5e5ea0..000000000 --- a/integrations/warp/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file diff --git a/integrations/warp/LICENSE-MIT b/integrations/warp/LICENSE-MIT deleted file mode 100644 index 468cd79a8..000000000 --- a/integrations/warp/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -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. \ No newline at end of file diff --git a/integrations/warp/src/batch_request.rs b/integrations/warp/src/batch_request.rs deleted file mode 100644 index 15ac34414..000000000 --- a/integrations/warp/src/batch_request.rs +++ /dev/null @@ -1,99 +0,0 @@ -use std::{io, str::FromStr}; - -use async_graphql::{BatchRequest, Executor, http::MultipartOptions}; -use futures_util::TryStreamExt; -use warp::{ - Buf, Filter, Rejection, Reply, - http::{HeaderName, HeaderValue}, - reply::Response as WarpResponse, -}; - -use crate::GraphQLBadRequest; - -/// GraphQL batch request filter -/// -/// It outputs a tuple containing the `async_graphql::Executor` and -/// `async_graphql::BatchRequest`. -pub fn graphql_batch( - executor: E, -) -> impl Filter + Clone -where - E: Executor, -{ - graphql_batch_opts(executor, Default::default()) -} - -/// Similar to graphql_batch, but you can set the options with -/// :`async_graphql::MultipartOptions`. -pub fn graphql_batch_opts( - executor: E, - opts: MultipartOptions, -) -> impl Filter + Clone -where - E: Executor, -{ - warp::any() - .and(warp::get().and(warp::filters::query::raw()).and_then( - |query_string: String| async move { - async_graphql::http::parse_query_string(&query_string) - .map(Into::into) - .map_err(|e| warp::reject::custom(GraphQLBadRequest(e))) - }, - )) - .or(warp::post() - .and(warp::header::optional::("content-type")) - .and(warp::body::stream()) - .and_then(move |content_type, body| async move { - async_graphql::http::receive_batch_body( - content_type, - TryStreamExt::map_err(body, io::Error::other) - .map_ok(|mut buf| { - let remaining = Buf::remaining(&buf); - Buf::copy_to_bytes(&mut buf, remaining) - }) - .into_async_read(), - opts, - ) - .await - .map_err(|e| warp::reject::custom(GraphQLBadRequest(e))) - })) - .unify() - .map(move |res| (executor.clone(), res)) -} - -/// Reply for `async_graphql::BatchRequest`. -#[derive(Debug)] -pub struct GraphQLBatchResponse(pub async_graphql::BatchResponse); - -impl From for GraphQLBatchResponse { - fn from(resp: async_graphql::BatchResponse) -> Self { - GraphQLBatchResponse(resp) - } -} - -impl Reply for GraphQLBatchResponse { - fn into_response(self) -> WarpResponse { - let mut resp = warp::reply::with_header( - warp::reply::json(&self.0), - "content-type", - "application/json", - ) - .into_response(); - - if self.0.is_ok() { - if let Some(cache_control) = self.0.cache_control().value() { - if let Ok(value) = cache_control.try_into() { - resp.headers_mut().insert("cache-control", value); - } - } - } - - resp.headers_mut() - .extend(self.0.http_headers().iter().filter_map(|(name, value)| { - HeaderName::from_str(name.as_str()) - .ok() - .zip(HeaderValue::from_bytes(value.as_bytes()).ok()) - })); - resp - } -} diff --git a/integrations/warp/src/error.rs b/integrations/warp/src/error.rs deleted file mode 100644 index 590d0c7e7..000000000 --- a/integrations/warp/src/error.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::{ - error::Error, - fmt::{self, Display, Formatter}, -}; - -use async_graphql::ParseRequestError; -use warp::{ - Reply, - http::{Response, StatusCode}, - hyper::Body, - reject::Reject, -}; - -/// Bad request error. -/// -/// It's a wrapper of `async_graphql::ParseRequestError`. It is also a `Reply` - -/// by default it just returns a response containing the error message in plain -/// text. -#[derive(Debug)] -pub struct GraphQLBadRequest(pub ParseRequestError); - -impl GraphQLBadRequest { - /// Get the appropriate status code of the error. - #[must_use] - pub fn status(&self) -> StatusCode { - match self.0 { - ParseRequestError::PayloadTooLarge => StatusCode::PAYLOAD_TOO_LARGE, - _ => StatusCode::BAD_REQUEST, - } - } -} - -impl Display for GraphQLBadRequest { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl Error for GraphQLBadRequest { - fn source(&self) -> Option<&(dyn Error + 'static)> { - Some(&self.0) - } -} - -impl Reject for GraphQLBadRequest {} - -impl Reply for GraphQLBadRequest { - fn into_response(self) -> Response { - Response::builder() - .status(self.status()) - .body(Body::from(self.0.to_string())) - .unwrap() - } -} - -impl From for GraphQLBadRequest { - fn from(e: ParseRequestError) -> Self { - Self(e) - } -} - -impl From for ParseRequestError { - fn from(e: GraphQLBadRequest) -> Self { - e.0 - } -} diff --git a/integrations/warp/src/lib.rs b/integrations/warp/src/lib.rs deleted file mode 100644 index edf5f3abe..000000000 --- a/integrations/warp/src/lib.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! Async-graphql integration with Warp - -#![allow(clippy::type_complexity)] -#![forbid(unsafe_code)] -#![warn(missing_docs)] - -mod batch_request; -mod error; -mod request; -mod subscription; - -pub use batch_request::{GraphQLBatchResponse, graphql_batch, graphql_batch_opts}; -pub use error::GraphQLBadRequest; -pub use request::{GraphQLResponse, graphql, graphql_opts}; -pub use subscription::{GraphQLWebSocket, graphql_protocol, graphql_subscription}; diff --git a/integrations/warp/src/request.rs b/integrations/warp/src/request.rs deleted file mode 100644 index c10934e63..000000000 --- a/integrations/warp/src/request.rs +++ /dev/null @@ -1,87 +0,0 @@ -use async_graphql::{BatchRequest, Executor, Request, http::MultipartOptions}; -use warp::{Filter, Rejection, Reply, reply::Response as WarpResponse}; - -use crate::{GraphQLBadRequest, GraphQLBatchResponse, graphql_batch_opts}; - -/// GraphQL request filter -/// -/// It outputs a tuple containing the `async_graphql::Schema` and -/// `async_graphql::Request`. -/// -/// # Examples -/// -/// *[Full Example]()* -/// -/// ```no_run -/// use std::convert::Infallible; -/// -/// use async_graphql::*; -/// use async_graphql_warp::*; -/// use warp::Filter; -/// -/// struct QueryRoot; -/// -/// #[Object] -/// impl QueryRoot { -/// async fn value(&self, ctx: &Context<'_>) -> i32 { -/// unimplemented!() -/// } -/// } -/// -/// type MySchema = Schema; -/// -/// # tokio::runtime::Runtime::new().unwrap().block_on(async { -/// let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription); -/// let filter = async_graphql_warp::graphql(schema).and_then( -/// |(schema, request): (MySchema, async_graphql::Request)| async move { -/// Ok::<_, Infallible>(async_graphql_warp::GraphQLResponse::from( -/// schema.execute(request).await, -/// )) -/// }, -/// ); -/// warp::serve(filter).run(([0, 0, 0, 0], 8000)).await; -/// # }); -/// ``` -pub fn graphql( - executor: E, -) -> impl Filter + Clone -where - E: Executor, -{ - graphql_opts(executor, Default::default()) -} - -/// Similar to graphql, but you can set the options -/// `async_graphql::MultipartOptions`. -pub fn graphql_opts( - executor: E, - opts: MultipartOptions, -) -> impl Filter + Clone -where - E: Executor, -{ - graphql_batch_opts(executor, opts).and_then(|(schema, batch): (_, BatchRequest)| async move { - >::Ok(( - schema, - batch - .into_single() - .map_err(|e| warp::reject::custom(GraphQLBadRequest(e)))?, - )) - }) -} - -/// Reply for `async_graphql::Request`. -#[derive(Debug)] -pub struct GraphQLResponse(pub async_graphql::Response); - -impl From for GraphQLResponse { - fn from(resp: async_graphql::Response) -> Self { - GraphQLResponse(resp) - } -} - -impl Reply for GraphQLResponse { - fn into_response(self) -> WarpResponse { - GraphQLBatchResponse(self.0.into()).into_response() - } -} diff --git a/integrations/warp/src/subscription.rs b/integrations/warp/src/subscription.rs deleted file mode 100644 index 10b3ff953..000000000 --- a/integrations/warp/src/subscription.rs +++ /dev/null @@ -1,319 +0,0 @@ -use std::{future::Future, str::FromStr, time::Duration}; - -use async_graphql::{ - Data, Executor, Result, - http::{ - DefaultOnConnInitType, DefaultOnPingType, WebSocketProtocols, WsMessage, - default_on_connection_init, default_on_ping, - }, -}; -use futures_util::{ - Sink, Stream, StreamExt, future, - stream::{SplitSink, SplitStream}, -}; -use warp::{Error, Filter, Rejection, Reply, filters::ws, ws::Message}; - -/// GraphQL subscription filter -/// -/// # Examples -/// -/// ```no_run -/// use std::time::Duration; -/// -/// use async_graphql::*; -/// use async_graphql_warp::*; -/// use futures_util::stream::{Stream, StreamExt}; -/// use warp::Filter; -/// -/// struct QueryRoot; -/// -/// #[Object] -/// impl QueryRoot { -/// async fn value(&self) -> i32 { -/// // A GraphQL Object type must define one or more fields. -/// 100 -/// } -/// } -/// -/// struct SubscriptionRoot; -/// -/// #[Subscription] -/// impl SubscriptionRoot { -/// async fn tick(&self) -> impl Stream { -/// async_stream::stream! { -/// let mut interval = tokio::time::interval(Duration::from_secs(1)); -/// loop { -/// let n = interval.tick().await; -/// yield format!("{}", n.elapsed().as_secs_f32()); -/// } -/// } -/// } -/// } -/// -/// # tokio::runtime::Runtime::new().unwrap().block_on(async { -/// let schema = Schema::new(QueryRoot, EmptyMutation, SubscriptionRoot); -/// let filter = -/// async_graphql_warp::graphql_subscription(schema).or(warp::any().map(|| "Hello, World!")); -/// warp::serve(filter).run(([0, 0, 0, 0], 8000)).await; -/// # }); -/// ``` -pub fn graphql_subscription( - executor: E, -) -> impl Filter + Clone -where - E: Executor, -{ - warp::ws() - .and(graphql_protocol()) - .map(move |ws: ws::Ws, protocol| { - let executor = executor.clone(); - - let reply = ws.on_upgrade(move |socket| { - GraphQLWebSocket::new(socket, executor, protocol).serve() - }); - - warp::reply::with_header( - reply, - "Sec-WebSocket-Protocol", - protocol.sec_websocket_protocol(), - ) - }) -} - -/// Create a `Filter` that parse [WebSocketProtocols] from -/// `sec-websocket-protocol` header. -pub fn graphql_protocol() -> impl Filter + Clone -{ - warp::header::optional::("sec-websocket-protocol").map(|protocols: Option| { - protocols - .and_then(|protocols| { - protocols - .split(',') - .find_map(|p| WebSocketProtocols::from_str(p.trim()).ok()) - }) - .unwrap_or(WebSocketProtocols::SubscriptionsTransportWS) - }) -} - -/// A Websocket connection for GraphQL subscription. -/// -/// # Examples -/// -/// ```no_run -/// use std::time::Duration; -/// -/// use async_graphql::*; -/// use async_graphql_warp::*; -/// use futures_util::stream::{Stream, StreamExt}; -/// use warp::{Filter, ws}; -/// -/// struct QueryRoot; -/// -/// #[Object] -/// impl QueryRoot { -/// async fn value(&self) -> i32 { -/// // A GraphQL Object type must define one or more fields. -/// 100 -/// } -/// } -/// -/// struct SubscriptionRoot; -/// -/// #[Subscription] -/// impl SubscriptionRoot { -/// async fn tick(&self) -> impl Stream { -/// async_stream::stream! { -/// let mut interval = tokio::time::interval(Duration::from_secs(1)); -/// loop { -/// let n = interval.tick().await; -/// yield format!("{}", n.elapsed().as_secs_f32()); -/// } -/// } -/// } -/// } -/// -/// # tokio::runtime::Runtime::new().unwrap().block_on(async { -/// let schema = Schema::new(QueryRoot, EmptyMutation, SubscriptionRoot); -/// -/// let filter = warp::ws() -/// .and(graphql_protocol()) -/// .map(move |ws: ws::Ws, protocol| { -/// let schema = schema.clone(); -/// -/// let reply = ws -/// .on_upgrade(move |socket| GraphQLWebSocket::new(socket, schema, protocol).serve()); -/// -/// warp::reply::with_header( -/// reply, -/// "Sec-WebSocket-Protocol", -/// protocol.sec_websocket_protocol(), -/// ) -/// }); -/// -/// warp::serve(filter).run(([0, 0, 0, 0], 8000)).await; -/// # }); -/// ``` -pub struct GraphQLWebSocket { - sink: Sink, - stream: Stream, - protocol: WebSocketProtocols, - executor: E, - data: Data, - on_init: OnInit, - on_ping: OnPing, - keepalive_timeout: Option, -} - -impl - GraphQLWebSocket< - SplitSink, - SplitStream, - E, - DefaultOnConnInitType, - DefaultOnPingType, - > -where - S: Stream> + Sink, - E: Executor, -{ - /// Create a [`GraphQLWebSocket`] object. - pub fn new(socket: S, executor: E, protocol: WebSocketProtocols) -> Self { - let (sink, stream) = socket.split(); - GraphQLWebSocket::new_with_pair(sink, stream, executor, protocol) - } -} - -impl GraphQLWebSocket -where - Sink: futures_util::sink::Sink, - Stream: futures_util::stream::Stream>, - E: Executor, -{ - /// Create a [`GraphQLWebSocket`] object with sink and stream objects. - pub fn new_with_pair( - sink: Sink, - stream: Stream, - executor: E, - protocol: WebSocketProtocols, - ) -> Self { - GraphQLWebSocket { - sink, - stream, - protocol, - executor, - data: Data::default(), - on_init: default_on_connection_init, - on_ping: default_on_ping, - keepalive_timeout: None, - } - } -} - -impl - GraphQLWebSocket -where - Sink: futures_util::sink::Sink, - Stream: futures_util::stream::Stream>, - E: Executor, - OnConnInit: FnOnce(serde_json::Value) -> OnConnInitFut + Send + 'static, - OnConnInitFut: Future> + Send + 'static, - OnPing: FnOnce(Option<&Data>, Option) -> OnPingFut + Clone + Send + 'static, - OnPingFut: Future>> + Send + 'static, -{ - /// Specify the initial subscription context data, usually you can get - /// something from the incoming request to create it. - #[must_use] - pub fn with_data(self, data: Data) -> Self { - Self { data, ..self } - } - - /// Specify a callback function to be called when the connection is - /// initialized. - /// - /// You can get something from the payload of [`GQL_CONNECTION_INIT` message](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init) to create [`Data`]. - /// The data returned by this callback function will be merged with the data - /// specified by [`with_data`]. - #[must_use] - pub fn on_connection_init( - self, - callback: F, - ) -> GraphQLWebSocket - where - F: FnOnce(serde_json::Value) -> R + Send + 'static, - R: Future> + Send + 'static, - { - GraphQLWebSocket { - sink: self.sink, - stream: self.stream, - executor: self.executor, - data: self.data, - on_init: callback, - on_ping: self.on_ping, - protocol: self.protocol, - keepalive_timeout: self.keepalive_timeout, - } - } - - /// Specify a ping callback function. - /// - /// This function if present, will be called with the data sent by the - /// client in the [`Ping` message](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#ping). - /// - /// The function should return the data to be sent in the [`Pong` message](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#pong). - /// - /// NOTE: Only used for the `graphql-ws` protocol. - #[must_use] - pub fn on_ping(self, callback: F) -> GraphQLWebSocket - where - F: FnOnce(Option<&Data>, Option) -> R + Send + Clone + 'static, - R: Future>> + Send + 'static, - { - GraphQLWebSocket { - sink: self.sink, - stream: self.stream, - executor: self.executor, - data: self.data, - on_init: self.on_init, - on_ping: callback, - protocol: self.protocol, - keepalive_timeout: self.keepalive_timeout, - } - } - - /// Sets a timeout for receiving an acknowledgement of the keep-alive ping. - /// - /// If the ping is not acknowledged within the timeout, the connection will - /// be closed. - /// - /// NOTE: Only used for the `graphql-ws` protocol. - #[must_use] - pub fn keepalive_timeout(self, timeout: impl Into>) -> Self { - Self { - keepalive_timeout: timeout.into(), - ..self - } - } - - /// Processing subscription requests. - pub async fn serve(self) { - let stream = self - .stream - .take_while(|msg| future::ready(msg.is_ok())) - .map(Result::unwrap) - .filter(|msg| future::ready(msg.is_text() || msg.is_binary())) - .map(ws::Message::into_bytes); - - let _ = async_graphql::http::WebSocket::new(self.executor.clone(), stream, self.protocol) - .connection_data(self.data) - .on_connection_init(self.on_init) - .on_ping(self.on_ping) - .keepalive_timeout(self.keepalive_timeout) - .map(|msg| match msg { - WsMessage::Text(text) => ws::Message::text(text), - WsMessage::Close(code, status) => ws::Message::close_with(code, status), - }) - .map(Ok) - .forward(self.sink) - .await; - } -} diff --git a/parser/Cargo.toml b/parser/Cargo.toml deleted file mode 100644 index 6eef7a776..000000000 --- a/parser/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -authors = ["sunli ", "Koxiaet"] -categories = ["network-programming", "asynchronous"] -description = "GraphQL query parser for async-graphql" -documentation = "https://docs.rs/async-graphql/" -edition = "2024" -homepage = "https://github.com/async-graphql/async-graphql" -keywords = ["futures", "async", "graphql"] -license = "MIT OR Apache-2.0" -name = "async-graphql-parser" -repository = "https://github.com/async-graphql/async-graphql" -version = "7.0.17" - -[dependencies] -async-graphql-value.workspace = true -pest = "2.7.11" -serde.workspace = true -serde_json.workspace = true - -[dev-dependencies] -pest_generator = "2.7.8" -proc-macro2 = "1.0.79" diff --git a/parser/LICENSE-APACHE b/parser/LICENSE-APACHE deleted file mode 100644 index f8e5e5ea0..000000000 --- a/parser/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file diff --git a/parser/LICENSE-MIT b/parser/LICENSE-MIT deleted file mode 100644 index 468cd79a8..000000000 --- a/parser/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -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. \ No newline at end of file diff --git a/parser/fuzz/.gitignore b/parser/fuzz/.gitignore deleted file mode 100644 index a0925114d..000000000 --- a/parser/fuzz/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -target -corpus -artifacts diff --git a/parser/fuzz/Cargo.toml b/parser/fuzz/Cargo.toml deleted file mode 100644 index bbe6f7668..000000000 --- a/parser/fuzz/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "async-graphql-parser-fuzz" -version = "0.0.0" -authors = ["Automatically generated"] -publish = false -edition = "2018" - -[package.metadata] -cargo-fuzz = true - -[dependencies] -libfuzzer-sys = "0.4" - -[dependencies.async-graphql-parser] -path = ".." - -# Prevent this from interfering with workspaces -[workspace] -members = ["."] - -[[bin]] -name = "parse_query" -path = "fuzz_targets/parse_query.rs" -test = false -doc = false diff --git a/parser/fuzz/fuzz_targets/parse_query.rs b/parser/fuzz/fuzz_targets/parse_query.rs deleted file mode 100644 index c0345fa7d..000000000 --- a/parser/fuzz/fuzz_targets/parse_query.rs +++ /dev/null @@ -1,6 +0,0 @@ -#![no_main] -use libfuzzer_sys::fuzz_target; - -fuzz_target!(|data: &str| { - let _ = async_graphql_parser::parse_query(data); -}); diff --git a/parser/src/graphql.pest b/parser/src/graphql.pest deleted file mode 100644 index f405c9670..000000000 --- a/parser/src/graphql.pest +++ /dev/null @@ -1,170 +0,0 @@ -WHITESPACE = _{ " " | "," | "\t" | "\u{feff}" | line_terminator } -COMMENT = _{ "#" ~ (!line_terminator ~ ANY)* } -line_terminator = @{ "\r\n" | "\r" | "\n" } - -// Executable // - -executable_document = { SOI ~ executable_definition+ ~ EOI } -executable_definition = { operation_definition | fragment_definition } - -operation_definition = { named_operation_definition | selection_set } -named_operation_definition = { operation_type ~ name? ~ variable_definitions? ~ directives? ~ selection_set } -variable_definitions = { "(" ~ variable_definition* ~ ")" } -variable_definition = { variable ~ ":" ~ type_ ~ directives? ~ default_value? } - -selection_set = { "{" ~ selection+ ~ "}" } -selection = { field | inline_fragment | fragment_spread } -field = { alias? ~ name ~ arguments? ~ directives? ~ selection_set? } -alias = { name ~ ":" } -fragment_spread = { "..." ~ !type_condition ~ name ~ directives? } -inline_fragment = { "..." ~ type_condition? ~ directives? ~ selection_set } - -fragment_definition = { "fragment" ~ name ~ type_condition ~ directives? ~ selection_set } -type_condition = ${ "on" ~ WHITESPACE+ ~ name } - -// Service // - -service_document = { SOI ~ type_system_definition+ ~ EOI } -type_system_definition = { schema_definition | type_definition | directive_definition } - -schema_definition = { - "schema" ~ const_directives? ~ "{" ~ operation_type_definition+ ~ "}" - | extend ~ "schema" ~ (const_directives? ~ "{" ~ operation_type_definition+ ~ "}" | const_directives) -} -operation_type_definition = { operation_type ~ ":" ~ name } - -type_definition = { scalar_type | object_type | interface_type | union_type | enum_type | input_object_type } - -scalar_type = { - string? ~ "scalar" ~ name ~ const_directives? - | extend ~ "scalar" ~ name ~ const_directives -} - -object_type = { - string? ~ "type" ~ name ~ implements_interfaces? ~ const_directives? ~ fields_definition? - | extend ~ "type" ~ name ~ (implements_interfaces? ~ (const_directives? ~ fields_definition | const_directives) | implements_interfaces) -} -implements_interfaces = { "implements" ~ "&"? ~ name ~ ("&" ~ name)* } - -interface_type = { - string? ~ "interface" ~ name ~ implements_interfaces? ~ const_directives? ~ fields_definition? - | extend ~ "interface" ~ name ~ implements_interfaces? ~ (const_directives? ~ fields_definition | const_directives) -} - -fields_definition = { "{" ~ field_definition+ ~ "}" } -field_definition = { string? ~ name ~ arguments_definition? ~ ":" ~ type_ ~ const_directives? } - -union_type = { - string? ~ "union" ~ name ~ const_directives? ~ union_member_types? - | extend ~ "union" ~ name ~ (const_directives? ~ union_member_types | const_directives) -} -union_member_types = { "=" ~ "|"? ~ name ~ ("|" ~ name)* } - -enum_type = { - string? ~ "enum" ~ name ~ const_directives? ~ enum_values? - | extend ~ "enum" ~ name ~ (const_directives? ~ enum_values | const_directives) -} -enum_values = { "{" ~ enum_value_definition+ ~ "}" } -enum_value_definition = { string? ~ enum_value ~ const_directives? } - -input_object_type = { - string? ~ "input" ~ name ~ const_directives? ~ input_fields_definition? - | extend ~ "input" ~ name ~ (const_directives? ~ input_fields_definition | const_directives) -} -input_fields_definition = { "{" ~ input_value_definition+ ~ "}" } - -extend = { "extend" } - -directive_definition = { string? ~ "directive" ~ "@" ~ name ~ arguments_definition? ~ repeatable ~ "on" ~ directive_locations } -repeatable = { "repeatable"? } -directive_locations = { "|"? ~ directive_location ~ ("|" ~ directive_location)* } -directive_location = { - "QUERY" - | "MUTATION" - | "SUBSCRIPTION" - | "FIELD_DEFINITION" - | "FIELD" - | "FRAGMENT_DEFINITION" - | "FRAGMENT_SPREAD" - | "INLINE_FRAGMENT" - | "VARIABLE_DEFINITION" - | "SCHEMA" - | "SCALAR" - | "OBJECT" - | "ARGUMENT_DEFINITION" - | "INTERFACE" - | "UNION" - | "ENUM_VALUE" - | "ENUM" - | "INPUT_OBJECT" - | "INPUT_FIELD_DEFINITION" -} - -arguments_definition = { "(" ~ input_value_definition+ ~ ")" } - -input_value_definition = { string? ~ name ~ ":" ~ type_ ~ default_value? ~ const_directives? } - -// Common // - -operation_type = { "query" | "mutation" | "subscription" } - -default_value = { "=" ~ const_value } - -type_ = @{ (name | "[" ~ type_ ~ "]") ~ "!"? } - -const_value = { number | string | boolean | null | enum_value | const_list | const_object } -value = { variable | number | string | boolean | null | enum_value | list | object } - -variable = { "$" ~ name } - -number = @{ (float | int) ~ !name_start } -float = { int ~ ((fractional ~ exponent) | fractional | exponent) } -fractional = { "." ~ ASCII_DIGIT+ } -exponent = { ("E" | "e") ~ ("+" | "-")? ~ ASCII_DIGIT+ } -int = { "-"? ~ ("0" | (ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*)) } - -string = ${ ("\"\"\"" ~ block_string_content ~ "\"\"\"") | ("\"" ~ string_content ~ "\"") } -block_string_content = @{ block_string_character* } -block_string_character = { - (!("\"\"\"" | "\\\"\"\"") ~ ANY) - | "\\\"\"\"" -} -string_content = @{ string_character* } -string_character = { - (!("\"" | "\\" | line_terminator) ~ ANY) - | ("\\" ~ ("\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t")) - | ("\\u" ~ unicode_scalar_value_hex) -} -// Spec inconsistency: -// In GraphQL, strings can contain any Unicode code point. However in Rust strings can only contain -// Unicode Scalar Values. To avoid having to use Vec everywhere we deviate from the spec -// slightly and disallow non-scalar value code points at the parsing level. -unicode_scalar_value_hex = { !(^"d" ~ ('8'..'9' | 'a'..'f' | 'A'..'F')) ~ ASCII_HEX_DIGIT{4} } - -boolean = { "true" | "false" } - -null = { "null" } - -enum_value = ${ !(boolean | null) ~ name } - -const_list = { "[" ~ const_value* ~ "]" } -list = { "[" ~ value* ~ "]" } - -const_object = { "{" ~ const_object_field* ~ "}" } -object = { "{" ~ object_field* ~ "}" } -const_object_field = { name ~ ":" ~ const_value } -object_field = { name ~ ":" ~ value } - - -const_directives = { const_directive+ } -directives = { directive+ } -const_directive = { "@" ~ name ~ const_arguments? } -directive = { "@" ~ name ~ arguments? } - -const_arguments = { "(" ~ const_argument+ ~ ")" } -arguments = { "(" ~ argument+ ~ ")" } -const_argument = { name ~ ":" ~ const_value } -argument = { name ~ ":" ~ value } - -name_start = @{ (ASCII_ALPHA | "_") } -name = @{ name_start ~ (ASCII_ALPHA | ASCII_DIGIT | "_")* } diff --git a/parser/src/lib.rs b/parser/src/lib.rs deleted file mode 100644 index d2a29601c..000000000 --- a/parser/src/lib.rs +++ /dev/null @@ -1,238 +0,0 @@ -//! A parser for GraphQL. Used in the [`async-graphql`](https://crates.io/crates/async-graphql) -//! crate. -//! -//! It uses the [pest](https://crates.io/crates/pest) crate to parse the input and then transforms -//! it into Rust types. -#![warn(missing_docs)] -#![allow(clippy::unnecessary_wraps)] -#![allow(clippy::upper_case_acronyms)] -#![allow(clippy::needless_question_mark)] -#![allow(clippy::uninlined_format_args)] -#![forbid(unsafe_code)] - -use std::fmt::{self, Display, Formatter}; - -use async_graphql_value::Name; -pub use parse::{parse_query, parse_schema}; -use pest::{RuleType, error::LineColLocation}; -pub use pos::{Pos, Positioned}; -use serde::{Serialize, Serializer}; - -use crate::types::OperationType; - -pub mod types; - -mod parse; -mod pos; - -/// Parser error. -#[derive(Debug, Clone, PartialEq, Eq)] -#[non_exhaustive] -pub enum Error { - /// A syntax error occurred. - Syntax { - /// The message of the error, nicely formatted with newlines. - message: String, - /// The start position of the error. - start: Pos, - /// The end position of the error, if present. - end: Option, - }, - /// The schema contained multiple query, mutation or subscription roots. - MultipleRoots { - /// The type of root that was duplicated. - root: OperationType, - /// The position of the schema. - schema: Pos, - /// The position of the second root. - pos: Pos, - }, - /// The schema contained no query root. - MissingQueryRoot { - /// The position of the schema. - pos: Pos, - }, - /// Multiple operations were found in a document with an anonymous one. - MultipleOperations { - /// The position of the anonymous operation. - anonymous: Pos, - /// The position of the other operation. - operation: Pos, - }, - /// An operation is defined multiple times in a document. - OperationDuplicated { - /// The name of the operation. - operation: Name, - /// The position of the first definition. - first: Pos, - /// The position of the second definition. - second: Pos, - }, - /// A fragment is defined multiple times in a document. - FragmentDuplicated { - /// The name of the fragment. - fragment: Name, - /// The position of the first definition. - first: Pos, - /// The position of the second definition. - second: Pos, - }, - /// The document does not contain any operation. - MissingOperation, - /// Recursion limit exceeded. - RecursionLimitExceeded, -} - -impl Error { - /// Get an iterator over the positions of the error. - /// - /// The iterator is ordered from most important to least important position. - #[must_use] - pub fn positions(&self) -> ErrorPositions { - match self { - Self::Syntax { - start, - end: Some(end), - .. - } => ErrorPositions::new_2(*start, *end), - Self::Syntax { start, .. } => ErrorPositions::new_1(*start), - Self::MultipleRoots { schema, pos, .. } => ErrorPositions::new_2(*pos, *schema), - Self::MissingQueryRoot { pos } => ErrorPositions::new_1(*pos), - Self::MultipleOperations { - anonymous, - operation, - } => ErrorPositions::new_2(*anonymous, *operation), - Self::OperationDuplicated { first, second, .. } => { - ErrorPositions::new_2(*second, *first) - } - Self::FragmentDuplicated { first, second, .. } => { - ErrorPositions::new_2(*second, *first) - } - Self::MissingOperation => ErrorPositions::new_0(), - Self::RecursionLimitExceeded => ErrorPositions::new_0(), - } - } -} - -impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Self::Syntax { message, .. } => f.write_str(message), - Self::MissingQueryRoot { .. } => f.write_str("schema definition is missing query root"), - Self::MultipleRoots { root, .. } => { - write!(f, "multiple {} roots in schema definition", root) - } - Self::MultipleOperations { .. } => f.write_str("document contains multiple operations"), - Self::OperationDuplicated { operation, .. } => { - write!(f, "operation {} is defined twice", operation) - } - Self::FragmentDuplicated { fragment, .. } => { - write!(f, "fragment {} is defined twice", fragment) - } - Self::MissingOperation => f.write_str("document does not contain an operation"), - Self::RecursionLimitExceeded => f.write_str("recursion limit exceeded."), - } - } -} - -impl std::error::Error for Error {} - -impl From> for Error { - fn from(err: pest::error::Error) -> Self { - let (start, end) = match err.line_col { - LineColLocation::Pos(at) => (at, None), - LineColLocation::Span(start, end) => (start, Some(end)), - }; - - Error::Syntax { - message: err.to_string(), - start: Pos::from(start), - end: end.map(Pos::from), - } - } -} - -/// An alias for `Result`. -pub type Result = std::result::Result; - -/// An iterator over the positions inside an error. -/// -/// Constructed from the `Error::positions` function. -#[derive(Debug, Clone)] -pub struct ErrorPositions(ErrorPositionsInner); - -impl ErrorPositions { - fn new_0() -> Self { - Self(ErrorPositionsInner::None) - } - fn new_1(a: Pos) -> Self { - Self(ErrorPositionsInner::One(a)) - } - fn new_2(a: Pos, b: Pos) -> Self { - Self(ErrorPositionsInner::Two(a, b)) - } -} - -impl Iterator for ErrorPositions { - type Item = Pos; - - fn next(&mut self) -> Option { - match self.0 { - ErrorPositionsInner::Two(a, b) => { - self.0 = ErrorPositionsInner::One(b); - Some(a) - } - ErrorPositionsInner::One(a) => { - self.0 = ErrorPositionsInner::None; - Some(a) - } - ErrorPositionsInner::None => None, - } - } - - fn size_hint(&self) -> (usize, Option) { - let len = self.len(); - (len, Some(len)) - } -} - -impl DoubleEndedIterator for ErrorPositions { - fn next_back(&mut self) -> Option { - match self.0 { - ErrorPositionsInner::Two(a, b) => { - self.0 = ErrorPositionsInner::One(a); - Some(b) - } - ErrorPositionsInner::One(a) => { - self.0 = ErrorPositionsInner::None; - Some(a) - } - ErrorPositionsInner::None => None, - } - } -} - -impl std::iter::FusedIterator for ErrorPositions {} - -impl ExactSizeIterator for ErrorPositions { - fn len(&self) -> usize { - match self.0 { - ErrorPositionsInner::Two(_, _) => 2, - ErrorPositionsInner::One(_) => 1, - ErrorPositionsInner::None => 0, - } - } -} - -impl Serialize for ErrorPositions { - fn serialize(&self, serializer: S) -> std::result::Result { - serializer.collect_seq(self.clone()) - } -} - -#[derive(Debug, Clone, Copy)] -enum ErrorPositionsInner { - Two(Pos, Pos), - One(Pos), - None, -} diff --git a/parser/src/parse/executable.rs b/parser/src/parse/executable.rs deleted file mode 100644 index 9ca7cb9b3..000000000 --- a/parser/src/parse/executable.rs +++ /dev/null @@ -1,454 +0,0 @@ -use super::*; - -const MAX_RECURSION_DEPTH: usize = 64; - -macro_rules! recursion_depth { - ($remaining_depth:ident) => {{ - if $remaining_depth == 0 { - return Err(Error::RecursionLimitExceeded); - } - $remaining_depth - 1 - }}; -} - -/// Parse a GraphQL query document. -/// -/// # Errors -/// -/// Fails if the query is not a valid GraphQL document. -pub fn parse_query>(input: T) -> Result { - let mut pc = PositionCalculator::new(input.as_ref()); - - let pairs = GraphQLParser::parse(Rule::executable_document, input.as_ref())?; - let items = parse_definition_items(exactly_one(pairs), &mut pc)?; - - let mut operations = None; - let mut fragments: HashMap<_, Positioned> = HashMap::new(); - - for item in items { - match item { - DefinitionItem::Operation(item) => { - if let Some(name) = item.node.name { - let operations = operations - .get_or_insert_with(|| DocumentOperations::Multiple(HashMap::new())); - let operations = match operations { - DocumentOperations::Single(anonymous) => { - return Err(Error::MultipleOperations { - anonymous: anonymous.pos, - operation: item.pos, - }); - } - DocumentOperations::Multiple(operations) => operations, - }; - - match operations.entry(name.node) { - hash_map::Entry::Occupied(entry) => { - let (name, first) = entry.remove_entry(); - return Err(Error::OperationDuplicated { - operation: name, - first: first.pos, - second: item.pos, - }); - } - hash_map::Entry::Vacant(entry) => { - entry.insert(Positioned::new(item.node.definition, item.pos)); - } - } - } else { - match operations { - Some(operations) => { - return Err(Error::MultipleOperations { - anonymous: item.pos, - operation: match operations { - DocumentOperations::Single(single) => single.pos, - DocumentOperations::Multiple(map) => { - map.values().next().unwrap().pos - } - }, - }); - } - None => { - operations = Some(DocumentOperations::Single(Positioned::new( - item.node.definition, - item.pos, - ))); - } - } - } - } - DefinitionItem::Fragment(item) => match fragments.entry(item.node.name.node) { - hash_map::Entry::Occupied(entry) => { - let (name, first) = entry.remove_entry(); - return Err(Error::FragmentDuplicated { - fragment: name, - first: first.pos, - second: item.pos, - }); - } - hash_map::Entry::Vacant(entry) => { - entry.insert(Positioned::new(item.node.definition, item.pos)); - } - }, - } - } - - Ok(ExecutableDocument { - operations: operations.ok_or(Error::MissingOperation)?, - fragments, - }) -} - -fn parse_definition_items( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::executable_document); - - Ok(pair - .into_inner() - .filter(|pair| pair.as_rule() != Rule::EOI) - .map(|pair| parse_definition_item(pair, pc)) - .collect::>()?) -} - -enum DefinitionItem { - Operation(Positioned), - Fragment(Positioned), -} - -fn parse_definition_item(pair: Pair, pc: &mut PositionCalculator) -> Result { - debug_assert_eq!(pair.as_rule(), Rule::executable_definition); - - let pair = exactly_one(pair.into_inner()); - Ok(match pair.as_rule() { - Rule::operation_definition => { - DefinitionItem::Operation(parse_operation_definition_item(pair, pc)?) - } - Rule::fragment_definition => { - DefinitionItem::Fragment(parse_fragment_definition_item(pair, pc)?) - } - _ => unreachable!(), - }) -} - -struct OperationDefinitionItem { - name: Option>, - definition: OperationDefinition, -} - -fn parse_operation_definition_item( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::operation_definition); - - let pos = pc.step(&pair); - let pair = exactly_one(pair.into_inner()); - Ok(Positioned::new( - match pair.as_rule() { - Rule::named_operation_definition => parse_named_operation_definition(pair, pc)?, - Rule::selection_set => OperationDefinitionItem { - name: None, - definition: OperationDefinition { - ty: OperationType::Query, - variable_definitions: Vec::new(), - directives: Vec::new(), - selection_set: parse_selection_set(pair, pc, MAX_RECURSION_DEPTH)?, - }, - }, - _ => unreachable!(), - }, - pos, - )) -} - -fn parse_named_operation_definition( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result { - debug_assert_eq!(pair.as_rule(), Rule::named_operation_definition); - - let mut pairs = pair.into_inner(); - - let ty = parse_operation_type(pairs.next().unwrap(), pc)?; - let name = parse_if_rule(&mut pairs, Rule::name, |pair| parse_name(pair, pc))?; - let variable_definitions = parse_if_rule(&mut pairs, Rule::variable_definitions, |pair| { - parse_variable_definitions(pair, pc) - })?; - let directives = parse_opt_directives(&mut pairs, pc)?; - let selection_set = parse_selection_set(pairs.next().unwrap(), pc, MAX_RECURSION_DEPTH)?; - - debug_assert_eq!(pairs.next(), None); - - Ok(OperationDefinitionItem { - name, - definition: OperationDefinition { - ty: ty.node, - variable_definitions: variable_definitions.unwrap_or_default(), - directives, - selection_set, - }, - }) -} - -fn parse_variable_definitions( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result>> { - debug_assert_eq!(pair.as_rule(), Rule::variable_definitions); - - pair.into_inner() - .map(|pair| parse_variable_definition(pair, pc)) - .collect() -} - -fn parse_variable_definition( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::variable_definition); - - let pos = pc.step(&pair); - let mut pairs = pair.into_inner(); - - let variable = parse_variable(pairs.next().unwrap(), pc)?; - let var_type = parse_type(pairs.next().unwrap(), pc)?; - - let directives = parse_opt_directives(&mut pairs, pc)?; - let default_value = parse_if_rule(&mut pairs, Rule::default_value, |pair| { - parse_default_value(pair, pc) - })?; - - debug_assert_eq!(pairs.next(), None); - - Ok(Positioned::new( - VariableDefinition { - name: variable, - var_type, - directives, - default_value, - }, - pos, - )) -} - -fn parse_selection_set( - pair: Pair, - pc: &mut PositionCalculator, - remaining_depth: usize, -) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::selection_set); - - let pos = pc.step(&pair); - - Ok(Positioned::new( - SelectionSet { - items: pair - .into_inner() - .map(|pair| parse_selection(pair, pc, remaining_depth)) - .collect::>()?, - }, - pos, - )) -} - -fn parse_selection( - pair: Pair, - pc: &mut PositionCalculator, - remaining_depth: usize, -) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::selection); - - let pos = pc.step(&pair); - let pair = exactly_one(pair.into_inner()); - - Ok(Positioned::new( - match pair.as_rule() { - Rule::field => Selection::Field(parse_field(pair, pc, remaining_depth)?), - Rule::fragment_spread => Selection::FragmentSpread(parse_fragment_spread(pair, pc)?), - Rule::inline_fragment => { - Selection::InlineFragment(parse_inline_fragment(pair, pc, remaining_depth)?) - } - _ => unreachable!(), - }, - pos, - )) -} - -fn parse_field( - pair: Pair, - pc: &mut PositionCalculator, - remaining_depth: usize, -) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::field); - - let pos = pc.step(&pair); - let mut pairs = pair.into_inner(); - - let alias = parse_if_rule(&mut pairs, Rule::alias, |pair| parse_alias(pair, pc))?; - let name = parse_name(pairs.next().unwrap(), pc)?; - let arguments = parse_if_rule(&mut pairs, Rule::arguments, |pair| { - parse_arguments(pair, pc) - })?; - let directives = parse_opt_directives(&mut pairs, pc)?; - let selection_set = parse_if_rule(&mut pairs, Rule::selection_set, |pair| { - parse_selection_set(pair, pc, recursion_depth!(remaining_depth)) - })?; - - debug_assert_eq!(pairs.next(), None); - - Ok(Positioned::new( - Field { - alias, - name, - arguments: arguments.unwrap_or_default(), - directives, - selection_set: selection_set.unwrap_or_default(), - }, - pos, - )) -} - -fn parse_alias(pair: Pair, pc: &mut PositionCalculator) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::alias); - parse_name(exactly_one(pair.into_inner()), pc) -} - -fn parse_fragment_spread( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::fragment_spread); - - let pos = pc.step(&pair); - let mut pairs = pair.into_inner(); - - let fragment_name = parse_name(pairs.next().unwrap(), pc)?; - let directives = parse_opt_directives(&mut pairs, pc)?; - - debug_assert_eq!(pairs.next(), None); - - Ok(Positioned::new( - FragmentSpread { - fragment_name, - directives, - }, - pos, - )) -} - -fn parse_inline_fragment( - pair: Pair, - pc: &mut PositionCalculator, - remaining_depth: usize, -) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::inline_fragment); - - let pos = pc.step(&pair); - let mut pairs = pair.into_inner(); - - let type_condition = parse_if_rule(&mut pairs, Rule::type_condition, |pair| { - parse_type_condition(pair, pc) - })?; - let directives = parse_opt_directives(&mut pairs, pc)?; - let selection_set = - parse_selection_set(pairs.next().unwrap(), pc, recursion_depth!(remaining_depth))?; - - debug_assert_eq!(pairs.next(), None); - - Ok(Positioned::new( - InlineFragment { - type_condition, - directives, - selection_set, - }, - pos, - )) -} - -struct FragmentDefinitionItem { - name: Positioned, - definition: FragmentDefinition, -} - -fn parse_fragment_definition_item( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::fragment_definition); - - let pos = pc.step(&pair); - let mut pairs = pair.into_inner(); - - let name = parse_name(pairs.next().unwrap(), pc)?; - let type_condition = parse_type_condition(pairs.next().unwrap(), pc)?; - let directives = parse_opt_directives(&mut pairs, pc)?; - let selection_set = parse_selection_set(pairs.next().unwrap(), pc, MAX_RECURSION_DEPTH)?; - - debug_assert_eq!(pairs.next(), None); - - Ok(Positioned::new( - FragmentDefinitionItem { - name, - definition: FragmentDefinition { - type_condition, - directives, - selection_set, - }, - }, - pos, - )) -} - -fn parse_type_condition( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::type_condition); - - let pos = pc.step(&pair); - Ok(Positioned::new( - TypeCondition { - on: parse_name(exactly_one(pair.into_inner()), pc)?, - }, - pos, - )) -} - -#[cfg(test)] -mod tests { - use std::fs; - - use super::*; - - #[test] - fn test_parser() { - for entry in fs::read_dir("tests/executables").unwrap() { - let entry = entry.unwrap(); - eprintln!("Parsing file {}", entry.path().display()); - - GraphQLParser::parse( - Rule::executable_document, - &fs::read_to_string(entry.path()).unwrap(), - ) - .unwrap(); - } - } - - #[test] - fn test_parser_ast() { - for entry in fs::read_dir("tests/executables").unwrap() { - let entry = entry.unwrap(); - eprintln!("Parsing and transforming file {}", entry.path().display()); - parse_query(fs::read_to_string(entry.path()).unwrap()).unwrap(); - } - } - - #[test] - fn test_parse_overflowing_int() { - let query_ok = format!("mutation {{ add(big: {}) }} ", i32::MAX); - let query_overflow = format!("mutation {{ add(big: {}0000) }} ", i32::MAX); - assert!(parse_query(query_ok).is_ok()); - assert!(parse_query(query_overflow).is_ok()); - } -} diff --git a/parser/src/parse/generated.rs b/parser/src/parse/generated.rs deleted file mode 100644 index 9be99d480..000000000 --- a/parser/src/parse/generated.rs +++ /dev/null @@ -1,7 +0,0 @@ - -//! This is @generated code, do not edit by hand. -//! See `graphql.pest` and `tests/codegen.rs`. -#![allow(unused_attributes)] -use super::GraphQLParser; - -# [allow (dead_code , non_camel_case_types , clippy :: upper_case_acronyms)] # [derive (Clone , Copy , Debug , Eq , Hash , Ord , PartialEq , PartialOrd)] pub enum Rule { # [doc = "End-of-input"] EOI , r#WHITESPACE , r#COMMENT , r#line_terminator , r#executable_document , r#executable_definition , r#operation_definition , r#named_operation_definition , r#variable_definitions , r#variable_definition , r#selection_set , r#selection , r#field , r#alias , r#fragment_spread , r#inline_fragment , r#fragment_definition , r#type_condition , r#service_document , r#type_system_definition , r#schema_definition , r#operation_type_definition , r#type_definition , r#scalar_type , r#object_type , r#implements_interfaces , r#interface_type , r#fields_definition , r#field_definition , r#union_type , r#union_member_types , r#enum_type , r#enum_values , r#enum_value_definition , r#input_object_type , r#input_fields_definition , r#extend , r#directive_definition , r#repeatable , r#directive_locations , r#directive_location , r#arguments_definition , r#input_value_definition , r#operation_type , r#default_value , r#type_ , r#const_value , r#value , r#variable , r#number , r#float , r#fractional , r#exponent , r#int , r#string , r#block_string_content , r#block_string_character , r#string_content , r#string_character , r#unicode_scalar_value_hex , r#boolean , r#null , r#enum_value , r#const_list , r#list , r#const_object , r#object , r#const_object_field , r#object_field , r#const_directives , r#directives , r#const_directive , r#directive , r#const_arguments , r#arguments , r#const_argument , r#argument , r#name_start , r#name } impl Rule { pub fn all_rules () -> & 'static [Rule] { & [Rule :: r#WHITESPACE , Rule :: r#COMMENT , Rule :: r#line_terminator , Rule :: r#executable_document , Rule :: r#executable_definition , Rule :: r#operation_definition , Rule :: r#named_operation_definition , Rule :: r#variable_definitions , Rule :: r#variable_definition , Rule :: r#selection_set , Rule :: r#selection , Rule :: r#field , Rule :: r#alias , Rule :: r#fragment_spread , Rule :: r#inline_fragment , Rule :: r#fragment_definition , Rule :: r#type_condition , Rule :: r#service_document , Rule :: r#type_system_definition , Rule :: r#schema_definition , Rule :: r#operation_type_definition , Rule :: r#type_definition , Rule :: r#scalar_type , Rule :: r#object_type , Rule :: r#implements_interfaces , Rule :: r#interface_type , Rule :: r#fields_definition , Rule :: r#field_definition , Rule :: r#union_type , Rule :: r#union_member_types , Rule :: r#enum_type , Rule :: r#enum_values , Rule :: r#enum_value_definition , Rule :: r#input_object_type , Rule :: r#input_fields_definition , Rule :: r#extend , Rule :: r#directive_definition , Rule :: r#repeatable , Rule :: r#directive_locations , Rule :: r#directive_location , Rule :: r#arguments_definition , Rule :: r#input_value_definition , Rule :: r#operation_type , Rule :: r#default_value , Rule :: r#type_ , Rule :: r#const_value , Rule :: r#value , Rule :: r#variable , Rule :: r#number , Rule :: r#float , Rule :: r#fractional , Rule :: r#exponent , Rule :: r#int , Rule :: r#string , Rule :: r#block_string_content , Rule :: r#block_string_character , Rule :: r#string_content , Rule :: r#string_character , Rule :: r#unicode_scalar_value_hex , Rule :: r#boolean , Rule :: r#null , Rule :: r#enum_value , Rule :: r#const_list , Rule :: r#list , Rule :: r#const_object , Rule :: r#object , Rule :: r#const_object_field , Rule :: r#object_field , Rule :: r#const_directives , Rule :: r#directives , Rule :: r#const_directive , Rule :: r#directive , Rule :: r#const_arguments , Rule :: r#arguments , Rule :: r#const_argument , Rule :: r#argument , Rule :: r#name_start , Rule :: r#name] } } # [allow (clippy :: all)] impl :: pest :: Parser < Rule > for GraphQLParser { fn parse < 'i > (rule : Rule , input : & 'i str) -> :: std :: result :: Result < :: pest :: iterators :: Pairs < 'i , Rule > , :: pest :: error :: Error < Rule > > { mod rules { # ! [allow (clippy :: upper_case_acronyms)] pub mod hidden { use super :: super :: Rule ; # [inline] # [allow (dead_code , non_snake_case , unused_variables)] pub fn skip (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { if state . atomicity () == :: pest :: Atomicity :: NonAtomic { state . sequence (| state | { state . repeat (| state | super :: visible :: WHITESPACE (state)) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: visible :: COMMENT (state) . and_then (| state | { state . repeat (| state | super :: visible :: WHITESPACE (state)) }) }) }) }) }) } else { Ok (state) } } } pub mod visible { use super :: super :: Rule ; # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#WHITESPACE (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . atomic (:: pest :: Atomicity :: Atomic , | state | { state . match_string (" ") . or_else (| state | { state . match_string (",") }) . or_else (| state | { state . match_string ("\t") }) . or_else (| state | { state . match_string ("\u{feff}") }) . or_else (| state | { self :: r#line_terminator (state) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#COMMENT (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . atomic (:: pest :: Atomicity :: Atomic , | state | { state . sequence (| state | { state . match_string ("#") . and_then (| state | { state . repeat (| state | { state . sequence (| state | { state . lookahead (false , | state | { self :: r#line_terminator (state) }) . and_then (| state | { self :: r#ANY (state) }) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#line_terminator (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#line_terminator , | state | { state . atomic (:: pest :: Atomicity :: Atomic , | state | { state . match_string ("\r\n") . or_else (| state | { state . match_string ("\r") }) . or_else (| state | { state . match_string ("\n") }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#executable_document (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#executable_document , | state | { state . sequence (| state | { self :: r#SOI (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { self :: r#executable_definition (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#executable_definition (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#executable_definition (state) }) }) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#EOI (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#executable_definition (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#executable_definition , | state | { self :: r#operation_definition (state) . or_else (| state | { self :: r#fragment_definition (state) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#operation_definition (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#operation_definition , | state | { self :: r#named_operation_definition (state) . or_else (| state | { self :: r#selection_set (state) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#named_operation_definition (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#named_operation_definition , | state | { state . sequence (| state | { self :: r#operation_type (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#name (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#variable_definitions (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#directives (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#selection_set (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#variable_definitions (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#variable_definitions , | state | { state . sequence (| state | { state . match_string ("(") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#variable_definition (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#variable_definition (state) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string (")") }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#variable_definition (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#variable_definition , | state | { state . sequence (| state | { self :: r#variable (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string (":") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#type_ (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#directives (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#default_value (state) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#selection_set (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#selection_set , | state | { state . sequence (| state | { state . match_string ("{") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { self :: r#selection (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#selection (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#selection (state) }) }) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("}") }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#selection (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#selection , | state | { self :: r#field (state) . or_else (| state | { self :: r#inline_fragment (state) }) . or_else (| state | { self :: r#fragment_spread (state) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#field (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#field , | state | { state . sequence (| state | { state . optional (| state | { self :: r#alias (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#arguments (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#directives (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#selection_set (state) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#alias (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#alias , | state | { state . sequence (| state | { self :: r#name (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string (":") }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#fragment_spread (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#fragment_spread , | state | { state . sequence (| state | { state . match_string ("...") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . lookahead (false , | state | { self :: r#type_condition (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#directives (state) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#inline_fragment (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#inline_fragment , | state | { state . sequence (| state | { state . match_string ("...") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#type_condition (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#directives (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#selection_set (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#fragment_definition (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#fragment_definition , | state | { state . sequence (| state | { state . match_string ("fragment") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#type_condition (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#directives (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#selection_set (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#type_condition (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . atomic (:: pest :: Atomicity :: CompoundAtomic , | state | { state . rule (Rule :: r#type_condition , | state | { state . sequence (| state | { state . match_string ("on") . and_then (| state | { state . sequence (| state | { self :: r#WHITESPACE (state) . and_then (| state | { state . repeat (| state | { self :: r#WHITESPACE (state) }) }) }) }) . and_then (| state | { self :: r#name (state) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#service_document (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#service_document , | state | { state . sequence (| state | { self :: r#SOI (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { self :: r#type_system_definition (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#type_system_definition (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#type_system_definition (state) }) }) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#EOI (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#type_system_definition (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#type_system_definition , | state | { self :: r#schema_definition (state) . or_else (| state | { self :: r#type_definition (state) }) . or_else (| state | { self :: r#directive_definition (state) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#schema_definition (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#schema_definition , | state | { state . sequence (| state | { state . match_string ("schema") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#const_directives (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("{") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { self :: r#operation_type_definition (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#operation_type_definition (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#operation_type_definition (state) }) }) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("}") }) }) . or_else (| state | { state . sequence (| state | { self :: r#extend (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("schema") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#const_directives (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("{") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { self :: r#operation_type_definition (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#operation_type_definition (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#operation_type_definition (state) }) }) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("}") }) }) . or_else (| state | { self :: r#const_directives (state) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#operation_type_definition (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#operation_type_definition , | state | { state . sequence (| state | { self :: r#operation_type (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string (":") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#type_definition (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#type_definition , | state | { self :: r#scalar_type (state) . or_else (| state | { self :: r#object_type (state) }) . or_else (| state | { self :: r#interface_type (state) }) . or_else (| state | { self :: r#union_type (state) }) . or_else (| state | { self :: r#enum_type (state) }) . or_else (| state | { self :: r#input_object_type (state) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#scalar_type (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#scalar_type , | state | { state . sequence (| state | { state . optional (| state | { self :: r#string (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("scalar") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#const_directives (state) }) }) }) . or_else (| state | { state . sequence (| state | { self :: r#extend (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("scalar") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#const_directives (state) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#object_type (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#object_type , | state | { state . sequence (| state | { state . optional (| state | { self :: r#string (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("type") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#implements_interfaces (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#const_directives (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#fields_definition (state) }) }) }) . or_else (| state | { state . sequence (| state | { self :: r#extend (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("type") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#implements_interfaces (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#const_directives (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#fields_definition (state) }) }) . or_else (| state | { self :: r#const_directives (state) }) }) }) . or_else (| state | { self :: r#implements_interfaces (state) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#implements_interfaces (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#implements_interfaces , | state | { state . sequence (| state | { state . match_string ("implements") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { state . match_string ("&") }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { state . sequence (| state | { state . match_string ("&") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) }) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { state . sequence (| state | { state . match_string ("&") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) }) }) }) }) }) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#interface_type (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#interface_type , | state | { state . sequence (| state | { state . optional (| state | { self :: r#string (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("interface") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#implements_interfaces (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#const_directives (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#fields_definition (state) }) }) }) . or_else (| state | { state . sequence (| state | { self :: r#extend (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("interface") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#implements_interfaces (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#const_directives (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#fields_definition (state) }) }) . or_else (| state | { self :: r#const_directives (state) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#fields_definition (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#fields_definition , | state | { state . sequence (| state | { state . match_string ("{") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { self :: r#field_definition (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#field_definition (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#field_definition (state) }) }) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("}") }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#field_definition (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#field_definition , | state | { state . sequence (| state | { state . optional (| state | { self :: r#string (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#arguments_definition (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string (":") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#type_ (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#const_directives (state) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#union_type (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#union_type , | state | { state . sequence (| state | { state . optional (| state | { self :: r#string (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("union") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#const_directives (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#union_member_types (state) }) }) }) . or_else (| state | { state . sequence (| state | { self :: r#extend (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("union") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#const_directives (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#union_member_types (state) }) }) . or_else (| state | { self :: r#const_directives (state) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#union_member_types (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#union_member_types , | state | { state . sequence (| state | { state . match_string ("=") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { state . match_string ("|") }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { state . sequence (| state | { state . match_string ("|") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) }) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { state . sequence (| state | { state . match_string ("|") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) }) }) }) }) }) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#enum_type (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#enum_type , | state | { state . sequence (| state | { state . optional (| state | { self :: r#string (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("enum") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#const_directives (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#enum_values (state) }) }) }) . or_else (| state | { state . sequence (| state | { self :: r#extend (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("enum") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#const_directives (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#enum_values (state) }) }) . or_else (| state | { self :: r#const_directives (state) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#enum_values (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#enum_values , | state | { state . sequence (| state | { state . match_string ("{") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { self :: r#enum_value_definition (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#enum_value_definition (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#enum_value_definition (state) }) }) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("}") }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#enum_value_definition (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#enum_value_definition , | state | { state . sequence (| state | { state . optional (| state | { self :: r#string (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#enum_value (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#const_directives (state) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#input_object_type (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#input_object_type , | state | { state . sequence (| state | { state . optional (| state | { self :: r#string (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("input") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#const_directives (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#input_fields_definition (state) }) }) }) . or_else (| state | { state . sequence (| state | { self :: r#extend (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("input") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#const_directives (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#input_fields_definition (state) }) }) . or_else (| state | { self :: r#const_directives (state) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#input_fields_definition (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#input_fields_definition , | state | { state . sequence (| state | { state . match_string ("{") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { self :: r#input_value_definition (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#input_value_definition (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#input_value_definition (state) }) }) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("}") }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#extend (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#extend , | state | { state . match_string ("extend") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#directive_definition (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#directive_definition , | state | { state . sequence (| state | { state . optional (| state | { self :: r#string (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("directive") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("@") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#arguments_definition (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#repeatable (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("on") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#directive_locations (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#repeatable (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#repeatable , | state | { state . optional (| state | { state . match_string ("repeatable") }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#directive_locations (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#directive_locations , | state | { state . sequence (| state | { state . optional (| state | { state . match_string ("|") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#directive_location (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { state . sequence (| state | { state . match_string ("|") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#directive_location (state) }) }) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { state . sequence (| state | { state . match_string ("|") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#directive_location (state) }) }) }) }) }) }) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#directive_location (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#directive_location , | state | { state . match_string ("QUERY") . or_else (| state | { state . match_string ("MUTATION") }) . or_else (| state | { state . match_string ("SUBSCRIPTION") }) . or_else (| state | { state . match_string ("FIELD_DEFINITION") }) . or_else (| state | { state . match_string ("FIELD") }) . or_else (| state | { state . match_string ("FRAGMENT_DEFINITION") }) . or_else (| state | { state . match_string ("FRAGMENT_SPREAD") }) . or_else (| state | { state . match_string ("INLINE_FRAGMENT") }) . or_else (| state | { state . match_string ("VARIABLE_DEFINITION") }) . or_else (| state | { state . match_string ("SCHEMA") }) . or_else (| state | { state . match_string ("SCALAR") }) . or_else (| state | { state . match_string ("OBJECT") }) . or_else (| state | { state . match_string ("ARGUMENT_DEFINITION") }) . or_else (| state | { state . match_string ("INTERFACE") }) . or_else (| state | { state . match_string ("UNION") }) . or_else (| state | { state . match_string ("ENUM_VALUE") }) . or_else (| state | { state . match_string ("ENUM") }) . or_else (| state | { state . match_string ("INPUT_OBJECT") }) . or_else (| state | { state . match_string ("INPUT_FIELD_DEFINITION") }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#arguments_definition (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#arguments_definition , | state | { state . sequence (| state | { state . match_string ("(") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { self :: r#input_value_definition (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#input_value_definition (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#input_value_definition (state) }) }) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string (")") }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#input_value_definition (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#input_value_definition , | state | { state . sequence (| state | { state . optional (| state | { self :: r#string (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string (":") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#type_ (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#default_value (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#const_directives (state) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#operation_type (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#operation_type , | state | { state . match_string ("query") . or_else (| state | { state . match_string ("mutation") }) . or_else (| state | { state . match_string ("subscription") }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#default_value (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#default_value , | state | { state . sequence (| state | { state . match_string ("=") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#const_value (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#type_ (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#type_ , | state | { state . atomic (:: pest :: Atomicity :: Atomic , | state | { state . sequence (| state | { self :: r#name (state) . or_else (| state | { state . sequence (| state | { state . match_string ("[") . and_then (| state | { self :: r#type_ (state) }) . and_then (| state | { state . match_string ("]") }) }) }) . and_then (| state | { state . optional (| state | { state . match_string ("!") }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#const_value (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#const_value , | state | { self :: r#number (state) . or_else (| state | { self :: r#string (state) }) . or_else (| state | { self :: r#boolean (state) }) . or_else (| state | { self :: r#null (state) }) . or_else (| state | { self :: r#enum_value (state) }) . or_else (| state | { self :: r#const_list (state) }) . or_else (| state | { self :: r#const_object (state) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#value (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#value , | state | { self :: r#variable (state) . or_else (| state | { self :: r#number (state) }) . or_else (| state | { self :: r#string (state) }) . or_else (| state | { self :: r#boolean (state) }) . or_else (| state | { self :: r#null (state) }) . or_else (| state | { self :: r#enum_value (state) }) . or_else (| state | { self :: r#list (state) }) . or_else (| state | { self :: r#object (state) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#variable (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#variable , | state | { state . sequence (| state | { state . match_string ("$") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#number (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#number , | state | { state . atomic (:: pest :: Atomicity :: Atomic , | state | { state . sequence (| state | { self :: r#float (state) . or_else (| state | { self :: r#int (state) }) . and_then (| state | { state . lookahead (false , | state | { self :: r#name_start (state) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#float (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#float , | state | { state . sequence (| state | { self :: r#int (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { self :: r#fractional (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#exponent (state) }) }) . or_else (| state | { self :: r#fractional (state) }) . or_else (| state | { self :: r#exponent (state) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#fractional (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#fractional , | state | { state . sequence (| state | { state . match_string (".") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#ASCII_DIGIT (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#ASCII_DIGIT (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#ASCII_DIGIT (state) }) }) }) }) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#exponent (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#exponent , | state | { state . sequence (| state | { state . match_string ("E") . or_else (| state | { state . match_string ("e") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { state . match_string ("+") . or_else (| state | { state . match_string ("-") }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#ASCII_DIGIT (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#ASCII_DIGIT (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#ASCII_DIGIT (state) }) }) }) }) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#int (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#int , | state | { state . sequence (| state | { state . optional (| state | { state . match_string ("-") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("0") . or_else (| state | { state . sequence (| state | { self :: r#ASCII_NONZERO_DIGIT (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#ASCII_DIGIT (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#ASCII_DIGIT (state) }) }) }) }) }) }) }) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#string (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . atomic (:: pest :: Atomicity :: CompoundAtomic , | state | { state . rule (Rule :: r#string , | state | { state . sequence (| state | { state . match_string ("\"\"\"") . and_then (| state | { self :: r#block_string_content (state) }) . and_then (| state | { state . match_string ("\"\"\"") }) }) . or_else (| state | { state . sequence (| state | { state . match_string ("\"") . and_then (| state | { self :: r#string_content (state) }) . and_then (| state | { state . match_string ("\"") }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#block_string_content (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#block_string_content , | state | { state . atomic (:: pest :: Atomicity :: Atomic , | state | { state . repeat (| state | { self :: r#block_string_character (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#block_string_character (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#block_string_character , | state | { state . sequence (| state | { state . lookahead (false , | state | { state . match_string ("\"\"\"") . or_else (| state | { state . match_string ("\\\"\"\"") }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#ANY (state) }) }) . or_else (| state | { state . match_string ("\\\"\"\"") }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#string_content (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#string_content , | state | { state . atomic (:: pest :: Atomicity :: Atomic , | state | { state . repeat (| state | { self :: r#string_character (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#string_character (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#string_character , | state | { state . sequence (| state | { state . lookahead (false , | state | { state . match_string ("\"") . or_else (| state | { state . match_string ("\\") }) . or_else (| state | { self :: r#line_terminator (state) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#ANY (state) }) }) . or_else (| state | { state . sequence (| state | { state . match_string ("\\") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("\"") . or_else (| state | { state . match_string ("\\") }) . or_else (| state | { state . match_string ("/") }) . or_else (| state | { state . match_string ("b") }) . or_else (| state | { state . match_string ("f") }) . or_else (| state | { state . match_string ("n") }) . or_else (| state | { state . match_string ("r") }) . or_else (| state | { state . match_string ("t") }) }) }) }) . or_else (| state | { state . sequence (| state | { state . match_string ("\\u") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#unicode_scalar_value_hex (state) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#unicode_scalar_value_hex (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#unicode_scalar_value_hex , | state | { state . sequence (| state | { state . lookahead (false , | state | { state . sequence (| state | { state . match_insensitive ("d") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_range ('8' .. '9') . or_else (| state | { state . match_range ('a' .. 'f') }) . or_else (| state | { state . match_range ('A' .. 'F') }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#ASCII_HEX_DIGIT (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#ASCII_HEX_DIGIT (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#ASCII_HEX_DIGIT (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#ASCII_HEX_DIGIT (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#boolean (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#boolean , | state | { state . match_string ("true") . or_else (| state | { state . match_string ("false") }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#null (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#null , | state | { state . match_string ("null") }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#enum_value (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . atomic (:: pest :: Atomicity :: CompoundAtomic , | state | { state . rule (Rule :: r#enum_value , | state | { state . sequence (| state | { state . lookahead (false , | state | { self :: r#boolean (state) . or_else (| state | { self :: r#null (state) }) }) . and_then (| state | { self :: r#name (state) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#const_list (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#const_list , | state | { state . sequence (| state | { state . match_string ("[") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#const_value (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#const_value (state) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("]") }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#list (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#list , | state | { state . sequence (| state | { state . match_string ("[") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#value (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#value (state) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("]") }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#const_object (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#const_object , | state | { state . sequence (| state | { state . match_string ("{") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#const_object_field (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#const_object_field (state) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("}") }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#object (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#object , | state | { state . sequence (| state | { state . match_string ("{") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#object_field (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#object_field (state) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string ("}") }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#const_object_field (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#const_object_field , | state | { state . sequence (| state | { self :: r#name (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string (":") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#const_value (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#object_field (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#object_field , | state | { state . sequence (| state | { self :: r#name (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string (":") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#value (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#const_directives (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#const_directives , | state | { state . sequence (| state | { self :: r#const_directive (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#const_directive (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#const_directive (state) }) }) }) }) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#directives (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#directives , | state | { state . sequence (| state | { self :: r#directive (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#directive (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#directive (state) }) }) }) }) }) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#const_directive (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#const_directive , | state | { state . sequence (| state | { state . match_string ("@") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#const_arguments (state) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#directive (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#directive , | state | { state . sequence (| state | { state . match_string ("@") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#name (state) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . optional (| state | { self :: r#arguments (state) }) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#const_arguments (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#const_arguments , | state | { state . sequence (| state | { state . match_string ("(") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { self :: r#const_argument (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#const_argument (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#const_argument (state) }) }) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string (")") }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#arguments (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#arguments , | state | { state . sequence (| state | { state . match_string ("(") . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { self :: r#argument (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . sequence (| state | { state . optional (| state | { self :: r#argument (state) . and_then (| state | { state . repeat (| state | { state . sequence (| state | { super :: hidden :: skip (state) . and_then (| state | { self :: r#argument (state) }) }) }) }) }) }) }) }) }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string (")") }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#const_argument (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#const_argument , | state | { state . sequence (| state | { self :: r#name (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string (":") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#const_value (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#argument (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#argument , | state | { state . sequence (| state | { self :: r#name (state) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { state . match_string (":") }) . and_then (| state | { super :: hidden :: skip (state) }) . and_then (| state | { self :: r#value (state) }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#name_start (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#name_start , | state | { state . atomic (:: pest :: Atomicity :: Atomic , | state | { self :: r#ASCII_ALPHA (state) . or_else (| state | { state . match_string ("_") }) }) }) } # [inline] # [allow (non_snake_case , unused_variables)] pub fn r#name (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: r#name , | state | { state . atomic (:: pest :: Atomicity :: Atomic , | state | { state . sequence (| state | { self :: r#name_start (state) . and_then (| state | { state . repeat (| state | { self :: r#ASCII_ALPHA (state) . or_else (| state | { self :: r#ASCII_DIGIT (state) }) . or_else (| state | { state . match_string ("_") }) }) }) }) }) }) } # [inline] # [allow (dead_code , non_snake_case , unused_variables)] pub fn ANY (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . skip (1) } # [inline] # [allow (dead_code , non_snake_case , unused_variables)] pub fn EOI (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . rule (Rule :: EOI , | state | state . end_of_input ()) } # [inline] # [allow (dead_code , non_snake_case , unused_variables)] pub fn SOI (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . start_of_input () } # [inline] # [allow (dead_code , non_snake_case , unused_variables)] pub fn ASCII_DIGIT (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . match_range ('0' ..'9') } # [inline] # [allow (dead_code , non_snake_case , unused_variables)] pub fn ASCII_NONZERO_DIGIT (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . match_range ('1' ..'9') } # [inline] # [allow (dead_code , non_snake_case , unused_variables)] pub fn ASCII_HEX_DIGIT (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . match_range ('0' ..'9') . or_else (| state | state . match_range ('a' ..'f')) . or_else (| state | state . match_range ('A' ..'F')) } # [inline] # [allow (dead_code , non_snake_case , unused_variables)] pub fn ASCII_ALPHA (state : :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >>) -> :: pest :: ParseResult < :: std :: boxed :: Box < :: pest :: ParserState < '_ , Rule >> > { state . match_range ('a' ..'z') . or_else (| state | state . match_range ('A' ..'Z')) } } pub use self :: visible :: * ; } :: pest :: state (input , | state | { match rule { Rule :: r#WHITESPACE => rules :: r#WHITESPACE (state) , Rule :: r#COMMENT => rules :: r#COMMENT (state) , Rule :: r#line_terminator => rules :: r#line_terminator (state) , Rule :: r#executable_document => rules :: r#executable_document (state) , Rule :: r#executable_definition => rules :: r#executable_definition (state) , Rule :: r#operation_definition => rules :: r#operation_definition (state) , Rule :: r#named_operation_definition => rules :: r#named_operation_definition (state) , Rule :: r#variable_definitions => rules :: r#variable_definitions (state) , Rule :: r#variable_definition => rules :: r#variable_definition (state) , Rule :: r#selection_set => rules :: r#selection_set (state) , Rule :: r#selection => rules :: r#selection (state) , Rule :: r#field => rules :: r#field (state) , Rule :: r#alias => rules :: r#alias (state) , Rule :: r#fragment_spread => rules :: r#fragment_spread (state) , Rule :: r#inline_fragment => rules :: r#inline_fragment (state) , Rule :: r#fragment_definition => rules :: r#fragment_definition (state) , Rule :: r#type_condition => rules :: r#type_condition (state) , Rule :: r#service_document => rules :: r#service_document (state) , Rule :: r#type_system_definition => rules :: r#type_system_definition (state) , Rule :: r#schema_definition => rules :: r#schema_definition (state) , Rule :: r#operation_type_definition => rules :: r#operation_type_definition (state) , Rule :: r#type_definition => rules :: r#type_definition (state) , Rule :: r#scalar_type => rules :: r#scalar_type (state) , Rule :: r#object_type => rules :: r#object_type (state) , Rule :: r#implements_interfaces => rules :: r#implements_interfaces (state) , Rule :: r#interface_type => rules :: r#interface_type (state) , Rule :: r#fields_definition => rules :: r#fields_definition (state) , Rule :: r#field_definition => rules :: r#field_definition (state) , Rule :: r#union_type => rules :: r#union_type (state) , Rule :: r#union_member_types => rules :: r#union_member_types (state) , Rule :: r#enum_type => rules :: r#enum_type (state) , Rule :: r#enum_values => rules :: r#enum_values (state) , Rule :: r#enum_value_definition => rules :: r#enum_value_definition (state) , Rule :: r#input_object_type => rules :: r#input_object_type (state) , Rule :: r#input_fields_definition => rules :: r#input_fields_definition (state) , Rule :: r#extend => rules :: r#extend (state) , Rule :: r#directive_definition => rules :: r#directive_definition (state) , Rule :: r#repeatable => rules :: r#repeatable (state) , Rule :: r#directive_locations => rules :: r#directive_locations (state) , Rule :: r#directive_location => rules :: r#directive_location (state) , Rule :: r#arguments_definition => rules :: r#arguments_definition (state) , Rule :: r#input_value_definition => rules :: r#input_value_definition (state) , Rule :: r#operation_type => rules :: r#operation_type (state) , Rule :: r#default_value => rules :: r#default_value (state) , Rule :: r#type_ => rules :: r#type_ (state) , Rule :: r#const_value => rules :: r#const_value (state) , Rule :: r#value => rules :: r#value (state) , Rule :: r#variable => rules :: r#variable (state) , Rule :: r#number => rules :: r#number (state) , Rule :: r#float => rules :: r#float (state) , Rule :: r#fractional => rules :: r#fractional (state) , Rule :: r#exponent => rules :: r#exponent (state) , Rule :: r#int => rules :: r#int (state) , Rule :: r#string => rules :: r#string (state) , Rule :: r#block_string_content => rules :: r#block_string_content (state) , Rule :: r#block_string_character => rules :: r#block_string_character (state) , Rule :: r#string_content => rules :: r#string_content (state) , Rule :: r#string_character => rules :: r#string_character (state) , Rule :: r#unicode_scalar_value_hex => rules :: r#unicode_scalar_value_hex (state) , Rule :: r#boolean => rules :: r#boolean (state) , Rule :: r#null => rules :: r#null (state) , Rule :: r#enum_value => rules :: r#enum_value (state) , Rule :: r#const_list => rules :: r#const_list (state) , Rule :: r#list => rules :: r#list (state) , Rule :: r#const_object => rules :: r#const_object (state) , Rule :: r#object => rules :: r#object (state) , Rule :: r#const_object_field => rules :: r#const_object_field (state) , Rule :: r#object_field => rules :: r#object_field (state) , Rule :: r#const_directives => rules :: r#const_directives (state) , Rule :: r#directives => rules :: r#directives (state) , Rule :: r#const_directive => rules :: r#const_directive (state) , Rule :: r#directive => rules :: r#directive (state) , Rule :: r#const_arguments => rules :: r#const_arguments (state) , Rule :: r#arguments => rules :: r#arguments (state) , Rule :: r#const_argument => rules :: r#const_argument (state) , Rule :: r#argument => rules :: r#argument (state) , Rule :: r#name_start => rules :: r#name_start (state) , Rule :: r#name => rules :: r#name (state) , Rule :: EOI => rules :: EOI (state) } }) } } diff --git a/parser/src/parse/mod.rs b/parser/src/parse/mod.rs deleted file mode 100644 index 6ac2631af..000000000 --- a/parser/src/parse/mod.rs +++ /dev/null @@ -1,344 +0,0 @@ -//! Parsing module. -//! -//! This module's structure mirrors `types`. - -use std::collections::{HashMap, hash_map}; - -use pest::{ - Parser, - iterators::{Pair, Pairs}, -}; -use utils::*; - -use crate::{ - Error, Result, - pos::{PositionCalculator, Positioned}, - types::*, -}; - -mod executable; -#[allow(clippy::redundant_static_lifetimes)] -#[rustfmt::skip] -#[allow(dead_code)] -mod generated; -mod service; -mod utils; - -use async_graphql_value::{ConstValue, Name, Number, Value}; -pub use executable::parse_query; -use generated::Rule; -pub use service::parse_schema; - -struct GraphQLParser; - -fn parse_operation_type( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::operation_type); - - let pos = pc.step(&pair); - - Ok(Positioned::new( - match pair.as_str() { - "query" => OperationType::Query, - "mutation" => OperationType::Mutation, - "subscription" => OperationType::Subscription, - _ => unreachable!(), - }, - pos, - )) -} - -fn parse_default_value( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::default_value); - - parse_const_value(exactly_one(pair.into_inner()), pc) -} - -fn parse_type(pair: Pair, pc: &mut PositionCalculator) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::type_); - - Ok(Positioned::new( - Type::new(pair.as_str()).unwrap(), - pc.step(&pair), - )) -} - -fn parse_const_value( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::const_value); - - let pos = pc.step(&pair); - let pair = exactly_one(pair.into_inner()); - - Ok(Positioned::new( - match pair.as_rule() { - Rule::number => ConstValue::Number(parse_number(pair, pc)?.node), - Rule::string => ConstValue::String(parse_string(pair, pc)?.node), - Rule::boolean => ConstValue::Boolean(parse_boolean(pair, pc)?.node), - Rule::null => ConstValue::Null, - Rule::enum_value => ConstValue::Enum(parse_enum_value(pair, pc)?.node), - Rule::const_list => ConstValue::List( - pair.into_inner() - .map(|pair| Ok(parse_const_value(pair, pc)?.node)) - .collect::>()?, - ), - Rule::const_object => ConstValue::Object( - pair.into_inner() - .map(|pair| { - debug_assert_eq!(pair.as_rule(), Rule::const_object_field); - - let mut pairs = pair.into_inner(); - - let name = parse_name(pairs.next().unwrap(), pc)?; - let value = parse_const_value(pairs.next().unwrap(), pc)?; - - debug_assert_eq!(pairs.next(), None); - - Ok((name.node, value.node)) - }) - .collect::>()?, - ), - _ => unreachable!(), - }, - pos, - )) -} -fn parse_value(pair: Pair, pc: &mut PositionCalculator) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::value); - - let pos = pc.step(&pair); - let pair = exactly_one(pair.into_inner()); - - Ok(Positioned::new( - match pair.as_rule() { - Rule::variable => Value::Variable(parse_variable(pair, pc)?.node), - Rule::number => Value::Number(parse_number(pair, pc)?.node), - Rule::string => Value::String(parse_string(pair, pc)?.node), - Rule::boolean => Value::Boolean(parse_boolean(pair, pc)?.node), - Rule::null => Value::Null, - Rule::enum_value => Value::Enum(parse_enum_value(pair, pc)?.node), - Rule::list => Value::List( - pair.into_inner() - .map(|pair| Ok(parse_value(pair, pc)?.node)) - .collect::>()?, - ), - Rule::object => Value::Object( - pair.into_inner() - .map(|pair| { - debug_assert_eq!(pair.as_rule(), Rule::object_field); - let mut pairs = pair.into_inner(); - - let name = parse_name(pairs.next().unwrap(), pc)?; - let value = parse_value(pairs.next().unwrap(), pc)?; - - debug_assert_eq!(pairs.next(), None); - - Ok((name.node, value.node)) - }) - .collect::>()?, - ), - _ => unreachable!(), - }, - pos, - )) -} - -fn parse_variable(pair: Pair, pc: &mut PositionCalculator) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::variable); - parse_name(exactly_one(pair.into_inner()), pc) -} -fn parse_number(pair: Pair, pc: &mut PositionCalculator) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::number); - let pos = pc.step(&pair); - Ok(Positioned::new( - pair.as_str().parse().map_err(|err| Error::Syntax { - message: format!("invalid number: {}", err), - start: pos, - end: None, - })?, - pos, - )) -} -fn parse_string(pair: Pair, pc: &mut PositionCalculator) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::string); - let pos = pc.step(&pair); - let pair = exactly_one(pair.into_inner()); - Ok(Positioned::new( - match pair.as_rule() { - Rule::block_string_content => block_string_value(pair.as_str()), - Rule::string_content => string_value(pair.as_str()), - _ => unreachable!(), - }, - pos, - )) -} -fn parse_boolean(pair: Pair, pc: &mut PositionCalculator) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::boolean); - let pos = pc.step(&pair); - Ok(Positioned::new( - match pair.as_str() { - "true" => true, - "false" => false, - _ => unreachable!(), - }, - pos, - )) -} -fn parse_enum_value(pair: Pair, pc: &mut PositionCalculator) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::enum_value); - parse_name(exactly_one(pair.into_inner()), pc) -} - -fn parse_opt_const_directives( - pairs: &mut Pairs<'_, Rule>, - pc: &mut PositionCalculator, -) -> Result>> { - Ok(parse_if_rule(pairs, Rule::const_directives, |pair| { - parse_const_directives(pair, pc) - })? - .unwrap_or_default()) -} -fn parse_opt_directives( - pairs: &mut Pairs<'_, Rule>, - pc: &mut PositionCalculator, -) -> Result>> { - Ok( - parse_if_rule(pairs, Rule::directives, |pair| parse_directives(pair, pc))? - .unwrap_or_default(), - ) -} -fn parse_const_directives( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result>> { - debug_assert_eq!(pair.as_rule(), Rule::const_directives); - - pair.into_inner() - .map(|pair| parse_const_directive(pair, pc)) - .collect() -} -fn parse_directives( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result>> { - debug_assert_eq!(pair.as_rule(), Rule::directives); - - pair.into_inner() - .map(|pair| parse_directive(pair, pc)) - .collect() -} - -fn parse_const_directive( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::const_directive); - - let pos = pc.step(&pair); - let mut pairs = pair.into_inner(); - - let name = parse_name(pairs.next().unwrap(), pc)?; - let arguments = parse_if_rule(&mut pairs, Rule::const_arguments, |pair| { - parse_const_arguments(pair, pc) - })?; - - debug_assert_eq!(pairs.next(), None); - - Ok(Positioned::new( - ConstDirective { - name, - arguments: arguments.unwrap_or_default(), - }, - pos, - )) -} -fn parse_directive(pair: Pair, pc: &mut PositionCalculator) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::directive); - - let pos = pc.step(&pair); - let mut pairs = pair.into_inner(); - - let name = parse_name(pairs.next().unwrap(), pc)?; - let arguments = parse_if_rule(&mut pairs, Rule::arguments, |pair| { - parse_arguments(pair, pc) - })?; - - debug_assert_eq!(pairs.next(), None); - - Ok(Positioned::new( - Directive { - name, - arguments: arguments.unwrap_or_default(), - }, - pos, - )) -} - -fn parse_const_arguments( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result, Positioned)>> { - debug_assert_eq!(pair.as_rule(), Rule::const_arguments); - pair.into_inner() - .map(|pair| { - debug_assert_eq!(pair.as_rule(), Rule::const_argument); - let mut pairs = pair.into_inner(); - - let name = parse_name(pairs.next().unwrap(), pc)?; - let value = parse_const_value(pairs.next().unwrap(), pc)?; - - debug_assert_eq!(pairs.next(), None); - - Ok((name, value)) - }) - .collect() -} -fn parse_arguments( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result, Positioned)>> { - debug_assert_eq!(pair.as_rule(), Rule::arguments); - pair.into_inner() - .map(|pair| { - debug_assert_eq!(pair.as_rule(), Rule::argument); - let mut pairs = pair.into_inner(); - - let name = parse_name(pairs.next().unwrap(), pc)?; - let value = parse_value(pairs.next().unwrap(), pc)?; - - debug_assert_eq!(pairs.next(), None); - - Ok((name, value)) - }) - .collect() -} - -fn parse_name(pair: Pair, pc: &mut PositionCalculator) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::name); - Ok(Positioned::new(Name::new(pair.as_str()), pc.step(&pair))) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_number_lookahead_restrictions() { - GraphQLParser::parse(Rule::const_list, "[123 abc]").unwrap(); - GraphQLParser::parse(Rule::const_list, "[123.0123 abc]").unwrap(); - GraphQLParser::parse(Rule::const_list, "[123.0123e7 abc]").unwrap(); - GraphQLParser::parse(Rule::const_list, "[123.0123e77 abc]").unwrap(); - - assert!(GraphQLParser::parse(Rule::const_list, "[123abc]").is_err()); - assert!(GraphQLParser::parse(Rule::const_list, "[123.0123abc]").is_err()); - assert!(GraphQLParser::parse(Rule::const_list, "[123.0123e7abc]").is_err()); - assert!(GraphQLParser::parse(Rule::const_list, "[123.0123e77abc]").is_err()); - } -} diff --git a/parser/src/parse/service.rs b/parser/src/parse/service.rs deleted file mode 100644 index 186a386f3..000000000 --- a/parser/src/parse/service.rs +++ /dev/null @@ -1,432 +0,0 @@ -use super::*; - -/// Parse a GraphQL schema document. -/// -/// # Errors -/// -/// Fails if the schema is not a valid GraphQL document. -pub fn parse_schema>(input: T) -> Result { - let mut pc = PositionCalculator::new(input.as_ref()); - Ok(parse_service_document( - exactly_one(GraphQLParser::parse( - Rule::service_document, - input.as_ref(), - )?), - &mut pc, - )?) -} - -fn parse_service_document( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result { - debug_assert_eq!(pair.as_rule(), Rule::service_document); - - Ok(ServiceDocument { - definitions: pair - .into_inner() - .filter(|pair| pair.as_rule() != Rule::EOI) - .map(|pair| parse_type_system_definition(pair, pc)) - .collect::>()?, - }) -} - -fn parse_type_system_definition( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result { - debug_assert_eq!(pair.as_rule(), Rule::type_system_definition); - - let pair = exactly_one(pair.into_inner()); - Ok(match pair.as_rule() { - Rule::schema_definition => TypeSystemDefinition::Schema(parse_schema_definition(pair, pc)?), - Rule::type_definition => TypeSystemDefinition::Type(parse_type_definition(pair, pc)?), - Rule::directive_definition => { - TypeSystemDefinition::Directive(parse_directive_definition(pair, pc)?) - } - _ => unreachable!(), - }) -} - -fn parse_schema_definition( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::schema_definition); - - let pos = pc.step(&pair); - let mut pairs = pair.into_inner(); - - let extend = next_if_rule(&mut pairs, Rule::extend).is_some(); - let directives = parse_opt_const_directives(&mut pairs, pc)?; - - let mut query = None; - let mut mutation = None; - let mut subscription = None; - - for pair in pairs { - debug_assert_eq!(pair.as_rule(), Rule::operation_type_definition); - - let mut pairs = pair.into_inner(); - - let operation_type = parse_operation_type(pairs.next().unwrap(), pc)?; - let name = parse_name(pairs.next().unwrap(), pc)?; - - match operation_type.node { - OperationType::Query if query.is_none() => query = Some(name), - OperationType::Mutation if mutation.is_none() => mutation = Some(name), - OperationType::Subscription if subscription.is_none() => subscription = Some(name), - _ => { - return Err(Error::MultipleRoots { - root: operation_type.node, - schema: pos, - pos: operation_type.pos, - }); - } - } - - debug_assert_eq!(pairs.next(), None); - } - - if !extend && query.is_none() { - return Err(Error::MissingQueryRoot { pos }); - } - - Ok(Positioned::new( - SchemaDefinition { - extend, - directives, - query, - mutation, - subscription, - }, - pos, - )) -} - -fn parse_type_definition( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::type_definition); - - let pos = pc.step(&pair); - let pair = exactly_one(pair.into_inner()); - let rule = pair.as_rule(); - let mut pairs = pair.into_inner(); - - let description = parse_if_rule(&mut pairs, Rule::string, |pair| parse_string(pair, pc))?; - let extend = next_if_rule(&mut pairs, Rule::extend).is_some(); - let name = parse_name(pairs.next().unwrap(), pc)?; - - let (directives, kind) = match rule { - Rule::scalar_type => { - let directives = parse_opt_const_directives(&mut pairs, pc)?; - (directives, TypeKind::Scalar) - } - Rule::object_type => { - let implements = parse_if_rule(&mut pairs, Rule::implements_interfaces, |pair| { - debug_assert_eq!(pair.as_rule(), Rule::implements_interfaces); - - pair.into_inner() - .map(|pair| parse_name(pair, pc)) - .collect::>() - })?; - - let directives = parse_opt_const_directives(&mut pairs, pc)?; - - let fields = parse_if_rule(&mut pairs, Rule::fields_definition, |pair| { - parse_fields_definition(pair, pc) - })? - .unwrap_or_default(); - - ( - directives, - TypeKind::Object(ObjectType { - implements: implements.unwrap_or_default(), - fields, - }), - ) - } - Rule::interface_type => { - let implements = parse_if_rule(&mut pairs, Rule::implements_interfaces, |pair| { - debug_assert_eq!(pair.as_rule(), Rule::implements_interfaces); - - pair.into_inner() - .map(|pair| parse_name(pair, pc)) - .collect::>() - })?; - - let directives = parse_opt_const_directives(&mut pairs, pc)?; - let fields = parse_if_rule(&mut pairs, Rule::fields_definition, |pair| { - parse_fields_definition(pair, pc) - })? - .unwrap_or_default(); - ( - directives, - TypeKind::Interface(InterfaceType { - implements: implements.unwrap_or_default(), - fields, - }), - ) - } - Rule::union_type => { - let directives = parse_opt_const_directives(&mut pairs, pc)?; - let members = parse_if_rule(&mut pairs, Rule::union_member_types, |pair| { - debug_assert_eq!(pair.as_rule(), Rule::union_member_types); - - pair.into_inner().map(|pair| parse_name(pair, pc)).collect() - })? - .unwrap_or_default(); - (directives, TypeKind::Union(UnionType { members })) - } - Rule::enum_type => { - let directives = parse_opt_const_directives(&mut pairs, pc)?; - let values = parse_if_rule(&mut pairs, Rule::enum_values, |pair| { - debug_assert_eq!(pair.as_rule(), Rule::enum_values); - - pair.into_inner() - .map(|pair| { - debug_assert_eq!(pair.as_rule(), Rule::enum_value_definition); - - let pos = pc.step(&pair); - let mut pairs = pair.into_inner(); - - let description = - parse_if_rule(&mut pairs, Rule::string, |pair| parse_string(pair, pc))?; - let value = parse_enum_value(pairs.next().unwrap(), pc)?; - let directives = parse_opt_const_directives(&mut pairs, pc)?; - - debug_assert_eq!(pairs.next(), None); - - Ok(Positioned::new( - EnumValueDefinition { - description, - value, - directives, - }, - pos, - )) - }) - .collect() - })? - .unwrap_or_default(); - (directives, TypeKind::Enum(EnumType { values })) - } - Rule::input_object_type => { - let directives = parse_opt_const_directives(&mut pairs, pc)?; - let fields = parse_if_rule(&mut pairs, Rule::input_fields_definition, |pair| { - debug_assert_eq!(pair.as_rule(), Rule::input_fields_definition); - - pair.into_inner() - .map(|pair| parse_input_value_definition(pair, pc)) - .collect() - })? - .unwrap_or_default(); - - ( - directives, - TypeKind::InputObject(InputObjectType { fields }), - ) - } - _ => unreachable!(), - }; - - debug_assert_eq!(pairs.next(), None); - - Ok(Positioned::new( - TypeDefinition { - extend, - description, - name, - directives, - kind, - }, - pos, - )) -} - -fn parse_fields_definition( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result>> { - debug_assert_eq!(pair.as_rule(), Rule::fields_definition); - - pair.into_inner() - .map(|pair| parse_field_definition(pair, pc)) - .collect() -} - -fn parse_field_definition( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::field_definition); - - let pos = pc.step(&pair); - let mut pairs = pair.into_inner(); - - let description = parse_if_rule(&mut pairs, Rule::string, |pair| parse_string(pair, pc))?; - let name = parse_name(pairs.next().unwrap(), pc)?; - let arguments = parse_if_rule(&mut pairs, Rule::arguments_definition, |pair| { - parse_arguments_definition(pair, pc) - })? - .unwrap_or_default(); - let ty = parse_type(pairs.next().unwrap(), pc)?; - let directives = parse_opt_const_directives(&mut pairs, pc)?; - - debug_assert_eq!(pairs.next(), None); - - Ok(Positioned::new( - FieldDefinition { - description, - name, - arguments, - ty, - directives, - }, - pos, - )) -} - -fn parse_directive_definition( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::directive_definition); - - let pos = pc.step(&pair); - let mut pairs = pair.into_inner(); - - let description = parse_if_rule(&mut pairs, Rule::string, |pair| parse_string(pair, pc))?; - let name = parse_name(pairs.next().unwrap(), pc)?; - let arguments = parse_if_rule(&mut pairs, Rule::arguments_definition, |pair| { - debug_assert_eq!(pair.as_rule(), Rule::arguments_definition); - pair.into_inner() - .map(|pair| parse_input_value_definition(pair, pc)) - .collect() - })? - .unwrap_or_default(); - let is_repeatable = parse_if_rule(&mut pairs, Rule::repeatable, |pair| { - debug_assert_eq!(pair.as_rule(), Rule::repeatable); - Ok(()) - }) - .unwrap_or_default() - .is_some(); - let locations = { - let pair = pairs.next().unwrap(); - debug_assert_eq!(pair.as_rule(), Rule::directive_locations); - pair.into_inner() - .map(|pair| { - let pos = pc.step(&pair); - debug_assert_eq!(pair.as_rule(), Rule::directive_location); - Positioned::new( - match pair.as_str() { - "QUERY" => DirectiveLocation::Query, - "MUTATION" => DirectiveLocation::Mutation, - "SUBSCRIPTION" => DirectiveLocation::Subscription, - "FIELD" => DirectiveLocation::Field, - "FRAGMENT_DEFINITION" => DirectiveLocation::FragmentDefinition, - "FRAGMENT_SPREAD" => DirectiveLocation::FragmentSpread, - "INLINE_FRAGMENT" => DirectiveLocation::InlineFragment, - "VARIABLE_DEFINITION" => DirectiveLocation::VariableDefinition, - "SCHEMA" => DirectiveLocation::Schema, - "SCALAR" => DirectiveLocation::Scalar, - "OBJECT" => DirectiveLocation::Object, - "FIELD_DEFINITION" => DirectiveLocation::FieldDefinition, - "ARGUMENT_DEFINITION" => DirectiveLocation::ArgumentDefinition, - "INTERFACE" => DirectiveLocation::Interface, - "UNION" => DirectiveLocation::Union, - "ENUM" => DirectiveLocation::Enum, - "ENUM_VALUE" => DirectiveLocation::EnumValue, - "INPUT_OBJECT" => DirectiveLocation::InputObject, - "INPUT_FIELD_DEFINITION" => DirectiveLocation::InputFieldDefinition, - _ => unreachable!(), - }, - pos, - ) - }) - .collect() - }; - - debug_assert_eq!(pairs.next(), None); - - Ok(Positioned::new( - DirectiveDefinition { - description, - name, - arguments, - is_repeatable, - locations, - }, - pos, - )) -} - -fn parse_arguments_definition( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result>> { - debug_assert_eq!(pair.as_rule(), Rule::arguments_definition); - - pair.into_inner() - .map(|pair| parse_input_value_definition(pair, pc)) - .collect() -} - -fn parse_input_value_definition( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - debug_assert_eq!(pair.as_rule(), Rule::input_value_definition); - - let pos = pc.step(&pair); - let mut pairs = pair.into_inner(); - - let description = parse_if_rule(&mut pairs, Rule::string, |pair| parse_string(pair, pc))?; - let name = parse_name(pairs.next().unwrap(), pc)?; - let ty = parse_type(pairs.next().unwrap(), pc)?; - let default_value = parse_if_rule(&mut pairs, Rule::default_value, |pair| { - parse_default_value(pair, pc) - })?; - let directives = parse_opt_const_directives(&mut pairs, pc)?; - - Ok(Positioned::new( - InputValueDefinition { - description, - name, - ty, - default_value, - directives, - }, - pos, - )) -} - -#[cfg(test)] -mod tests { - use std::fs; - - use super::*; - - #[test] - fn test_parser() { - for entry in fs::read_dir("tests/services").unwrap() { - let entry = entry.unwrap(); - eprintln!("Parsing file {}", entry.path().display()); - GraphQLParser::parse( - Rule::service_document, - &fs::read_to_string(entry.path()).unwrap(), - ) - .unwrap(); - } - } - - #[test] - fn test_parser_ast() { - for entry in fs::read_dir("tests/services").unwrap() { - let entry = entry.unwrap(); - parse_schema(fs::read_to_string(entry.path()).unwrap()).unwrap(); - } - } -} diff --git a/parser/src/parse/utils.rs b/parser/src/parse/utils.rs deleted file mode 100644 index 9769ed8ec..000000000 --- a/parser/src/parse/utils.rs +++ /dev/null @@ -1,140 +0,0 @@ -use pest::iterators::{Pair, Pairs}; - -use super::Rule; -use crate::Result; - -pub(super) fn next_if_rule<'a>(pairs: &mut Pairs<'a, Rule>, rule: Rule) -> Option> { - if pairs.peek().is_some_and(|pair| pair.as_rule() == rule) { - Some(pairs.next().unwrap()) - } else { - None - } -} -pub(super) fn parse_if_rule( - pairs: &mut Pairs, - rule: Rule, - f: impl FnOnce(Pair) -> Result, -) -> Result> { - next_if_rule(pairs, rule).map(f).transpose() -} - -pub(super) fn exactly_one(iter: impl IntoIterator) -> T { - let mut iter = iter.into_iter(); - let res = iter.next().unwrap(); - debug_assert!(iter.next().is_none()); - res -} - -pub(super) fn block_string_value(raw: &str) -> String { - // Split the string by either \r\n, \r or \n - let lines: Vec<_> = raw - .split("\r\n") - .flat_map(|s| s.split(['\r', '\n'].as_ref())) - .collect(); - - // Find the common indent - let common_indent = lines - .iter() - .skip(1) - .copied() - .filter_map(|line| line.find(|c| c != '\t' && c != ' ')) - .min() - .unwrap_or(0); - - let line_has_content = |line: &str| line.as_bytes().iter().any(|&c| c != b'\t' && c != b' '); - - let first_contentful_line = lines - .iter() - .copied() - .position(line_has_content) - .unwrap_or(lines.len()); - let ending_lines_start = lines - .iter() - .copied() - .rposition(line_has_content) - .map_or(0, |i| i + 1); - - lines - .iter() - .copied() - .enumerate() - .take(ending_lines_start) - .skip(first_contentful_line) - // Remove the common indent, but not on the first line - .map(|(i, line)| { - if i != 0 && line.len() >= common_indent { - &line[common_indent..] - } else { - line - } - }) - // Put a newline between each line - .enumerate() - .flat_map(|(i, line)| { - if i == 0 { [].as_ref() } else { ['\n'].as_ref() } - .iter() - .copied() - .chain(line.chars()) - }) - .collect() -} - -#[test] -fn test_block_string_value() { - assert_eq!(block_string_value(""), ""); - assert_eq!(block_string_value("\r\n"), ""); - assert_eq!(block_string_value("\r\r\r\r\n\n\r\n\r\r"), ""); - assert_eq!(block_string_value("abc"), "abc"); - assert_eq!( - block_string_value("line 1\r\n line 2\n line 3\r line 4"), - "line 1\nline 2\n line 3\n line 4" - ); - assert_eq!( - block_string_value("\r\r some text\r\n \n \n "), - "some text" - ); - assert_eq!( - block_string_value( - r#" - a - b - - c -"# - ), - "a\nb\n\nc" - ); -} - -pub(super) fn string_value(s: &str) -> String { - let mut chars = s.chars(); - - std::iter::from_fn(|| { - Some(match chars.next()? { - '\\' => match chars.next().expect("backslash at end") { - c @ '\"' | c @ '\\' | c @ '/' => c, - 'b' => '\x08', - 'f' => '\x0C', - 'n' => '\n', - 'r' => '\r', - 't' => '\t', - 'u' => std::char::from_u32( - (0..4) - .map(|_| chars.next().unwrap().to_digit(16).unwrap()) - .fold(0, |acc, digit| acc * 16 + digit), - ) - .unwrap(), - _ => unreachable!(), - }, - other => other, - }) - }) - .collect() -} - -#[test] -fn test_string_value() { - assert_eq!(string_value("abc"), "abc"); - assert_eq!(string_value("\\n\\b\\u2a1A"), "\n\x08\u{2A1A}"); - assert_eq!(string_value("\\\"\\\\"), "\"\\"); -} diff --git a/parser/src/pos.rs b/parser/src/pos.rs deleted file mode 100644 index 063047b44..000000000 --- a/parser/src/pos.rs +++ /dev/null @@ -1,162 +0,0 @@ -use std::{ - borrow::{Borrow, BorrowMut}, - cmp::Ordering, - fmt, - hash::{Hash, Hasher}, -}; - -use pest::{RuleType, iterators::Pair}; -use serde::{Deserialize, Serialize}; - -/// Original position of an element in source code. -/// -/// You can serialize and deserialize it to the GraphQL `locations` format -/// ([reference](https://spec.graphql.org/October2021/#sec-Errors)). -#[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Default, Hash, Serialize, Deserialize)] -pub struct Pos { - /// One-based line number. - pub line: usize, - - /// One-based column number. - pub column: usize, -} - -impl fmt::Debug for Pos { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Pos({}:{})", self.line, self.column) - } -} - -impl fmt::Display for Pos { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}:{}", self.line, self.column) - } -} - -impl From<(usize, usize)> for Pos { - fn from((line, column): (usize, usize)) -> Self { - Self { line, column } - } -} - -/// An AST node that stores its original position. -#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)] -pub struct Positioned { - /// The position of the node. - pub pos: Pos, - /// The node itself. - pub node: T, -} - -impl Positioned { - /// Create a new positioned node from the node and its position. - #[must_use] - pub const fn new(node: T, pos: Pos) -> Positioned { - Positioned { pos, node } - } - - /// Get the inner node. - /// - /// This is most useful in callback chains where `Positioned::into_inner` is - /// easier to read than `|positioned| positioned.node`. - #[inline] - pub fn into_inner(self) -> T { - self.node - } - - /// Create a new positioned node with the same position as this one. - #[must_use] - pub fn position_node(&self, other: U) -> Positioned { - Positioned::new(other, self.pos) - } - - /// Map the inner value of this positioned node. - #[must_use] - pub fn map(self, f: impl FnOnce(T) -> U) -> Positioned { - Positioned::new(f(self.node), self.pos) - } -} - -impl fmt::Display for Positioned { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.node.fmt(f) - } -} -impl PartialEq for Positioned { - fn eq(&self, other: &Self) -> bool { - self.node == other.node - } -} -impl Eq for Positioned {} -impl PartialOrd for Positioned { - fn partial_cmp(&self, other: &Self) -> Option { - self.node.partial_cmp(&other.node) - } -} -impl Ord for Positioned { - fn cmp(&self, other: &Self) -> Ordering { - self.node.cmp(&other.node) - } -} -impl Hash for Positioned { - fn hash(&self, state: &mut H) { - self.node.hash(state) - } -} - -impl Borrow for Positioned { - fn borrow(&self) -> &str { - self.node.as_str() - } -} - -impl BorrowMut for Positioned { - fn borrow_mut(&mut self) -> &mut str { - self.node.as_mut_str() - } -} - -pub(crate) struct PositionCalculator<'a> { - input: &'a str, - pos: usize, - line: usize, - column: usize, -} - -impl<'a> PositionCalculator<'a> { - pub(crate) fn new(input: &'a str) -> PositionCalculator<'a> { - Self { - input, - pos: 0, - line: 1, - column: 1, - } - } - - pub(crate) fn step(&mut self, pair: &Pair) -> Pos { - let pos = pair.as_span().start(); - debug_assert!(pos >= self.pos); - let bytes_to_read = pos - self.pos; - let chars_to_read = self.input[..bytes_to_read].chars(); - for ch in chars_to_read { - match ch { - '\r' => { - self.column = 1; - } - '\n' => { - self.line += 1; - self.column = 1; - } - _ => { - self.column += 1; - } - } - } - self.pos = pos; - self.input = &self.input[bytes_to_read..]; - Pos { - line: self.line, - column: self.column, - } - } -} diff --git a/parser/src/types/executable.rs b/parser/src/types/executable.rs deleted file mode 100644 index e23fffa79..000000000 --- a/parser/src/types/executable.rs +++ /dev/null @@ -1,263 +0,0 @@ -//! Executable document-related GraphQL types. - -use serde::{Deserialize, Serialize}; - -use super::*; - -/// An executable GraphQL file or request string. -/// -/// [Reference](https://spec.graphql.org/October2021/#ExecutableDocument). -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ExecutableDocument { - /// The operations of the document. - pub operations: DocumentOperations, - /// The fragments of the document. - pub fragments: HashMap>, -} - -/// The operations of a GraphQL document. -/// -/// There is either one anonymous operation or many named operations. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum DocumentOperations { - /// The document contains a single anonymous operation. - Single(Positioned), - /// The document contains many named operations. - Multiple(HashMap>), -} - -impl DocumentOperations { - /// Iterate over the operations of the document. - #[must_use] - pub fn iter(&self) -> OperationsIter<'_> { - OperationsIter(match self { - Self::Single(op) => OperationsIterInner::Single(Some(op)), - Self::Multiple(ops) => OperationsIterInner::Multiple(ops.iter()), - }) - } -} - -// TODO: This is not implemented as I would like to later implement IntoIterator -// for DocumentOperations (not a reference) without having a breaking change. -// -// impl<'a> IntoIterator for &'a DocumentOperations { -// type Item = &'a Positioned; -// type IntoIter = OperationsIter<'a>; -// -// fn into_iter(self) -> Self::IntoIter { -// self.iter() -// } -//} - -/// An iterator over the operations of a document. -#[derive(Debug, Clone)] -pub struct OperationsIter<'a>(OperationsIterInner<'a>); - -impl<'a> Iterator for OperationsIter<'a> { - type Item = (Option<&'a Name>, &'a Positioned); - - fn next(&mut self) -> Option { - match &mut self.0 { - OperationsIterInner::Single(op) => op.take().map(|op| (None, op)), - OperationsIterInner::Multiple(iter) => iter.next().map(|(name, op)| (Some(name), op)), - } - } - - fn size_hint(&self) -> (usize, Option) { - let size = self.len(); - (size, Some(size)) - } -} - -impl std::iter::FusedIterator for OperationsIter<'_> {} - -impl ExactSizeIterator for OperationsIter<'_> { - fn len(&self) -> usize { - match &self.0 { - OperationsIterInner::Single(opt) => usize::from(opt.is_some()), - OperationsIterInner::Multiple(iter) => iter.len(), - } - } -} - -#[derive(Debug, Clone)] -enum OperationsIterInner<'a> { - Single(Option<&'a Positioned>), - Multiple(hash_map::Iter<'a, Name, Positioned>), -} - -/// A GraphQL operation, such as `mutation($content:String!) { makePost(content: -/// $content) { id } }`. -/// -/// [Reference](https://spec.graphql.org/October2021/#OperationDefinition). -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct OperationDefinition { - /// The type of operation. - pub ty: OperationType, - /// The variable definitions. - pub variable_definitions: Vec>, - /// The operation's directives. - pub directives: Vec>, - /// The operation's selection set. - pub selection_set: Positioned, -} - -/// A variable definition inside a list of variable definitions, for example -/// `$name:String!`. -/// -/// [Reference](https://spec.graphql.org/October2021/#VariableDefinition). -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct VariableDefinition { - /// The name of the variable, without the preceding `$`. - pub name: Positioned, - /// The type of the variable. - pub var_type: Positioned, - /// The variable's directives. - pub directives: Vec>, - /// The optional default value of the variable. - pub default_value: Option>, -} - -impl VariableDefinition { - /// Get the default value of the variable; this is `default_value` if it is - /// present, `Value::Null` if it is nullable and `None` otherwise. - #[must_use] - pub fn default_value(&self) -> Option<&ConstValue> { - self.default_value.as_ref().map(|value| &value.node).or({ - if self.var_type.node.nullable { - Some(&ConstValue::Null) - } else { - None - } - }) - } -} - -/// A set of fields to be selected, for example `{ name age }`. -/// -/// [Reference](https://spec.graphql.org/October2021/#SelectionSet). -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct SelectionSet { - /// The fields to be selected. - pub items: Vec>, -} - -/// A part of an object to be selected; a single field, a fragment spread or an -/// inline fragment. -/// -/// [Reference](https://spec.graphql.org/October2021/#Selection). -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum Selection { - /// Select a single field, such as `name` or `weightKilos: weight(unit: - /// KILOGRAMS)`. - Field(Positioned), - /// Select using a fragment. - FragmentSpread(Positioned), - /// Select using an inline fragment. - InlineFragment(Positioned), -} - -impl Selection { - /// Get a reference to the directives of the selection. - #[must_use] - pub fn directives(&self) -> &Vec> { - match self { - Self::Field(field) => &field.node.directives, - Self::FragmentSpread(spread) => &spread.node.directives, - Self::InlineFragment(fragment) => &fragment.node.directives, - } - } - /// Get a mutable reference to the directives of the selection. - #[must_use] - pub fn directives_mut(&mut self) -> &mut Vec> { - match self { - Self::Field(field) => &mut field.node.directives, - Self::FragmentSpread(spread) => &mut spread.node.directives, - Self::InlineFragment(fragment) => &mut fragment.node.directives, - } - } -} - -/// A field being selected on an object, such as `name` or `weightKilos: -/// weight(unit: KILOGRAMS)`. -/// -/// [Reference](https://spec.graphql.org/October2021/#Field). -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Field { - /// The optional field alias. - pub alias: Option>, - /// The name of the field. - pub name: Positioned, - /// The arguments to the field, empty if no arguments are provided. - pub arguments: Vec<(Positioned, Positioned)>, - /// The directives in the field selector. - pub directives: Vec>, - /// The subfields being selected in this field, if it is an object. Empty if - /// no fields are being selected. - pub selection_set: Positioned, -} - -impl Field { - /// Get the response key of the field. This is the alias if present and the - /// name otherwise. - #[must_use] - pub fn response_key(&self) -> &Positioned { - self.alias.as_ref().unwrap_or(&self.name) - } - - /// Get the value of the argument with the specified name. - #[must_use] - pub fn get_argument(&self, name: &str) -> Option<&Positioned> { - self.arguments - .iter() - .find(|item| item.0.node == name) - .map(|item| &item.1) - } -} - -/// A fragment selector, such as `... userFields`. -/// -/// [Reference](https://spec.graphql.org/October2021/#FragmentSpread). -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct FragmentSpread { - /// The name of the fragment being selected. - pub fragment_name: Positioned, - /// The directives in the fragment selector. - pub directives: Vec>, -} - -/// An inline fragment selector, such as `... on User { name }`. -/// -/// [Reference](https://spec.graphql.org/October2021/#InlineFragment). -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct InlineFragment { - /// The type condition. - pub type_condition: Option>, - /// The directives in the inline fragment. - pub directives: Vec>, - /// The selected fields of the fragment. - pub selection_set: Positioned, -} - -/// The definition of a fragment, such as `fragment userFields on User { name -/// age }`. -/// -/// [Reference](https://spec.graphql.org/October2021/#FragmentDefinition). -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct FragmentDefinition { - /// The type this fragment operates on. - pub type_condition: Positioned, - /// Directives in the fragment. - pub directives: Vec>, - /// The fragment's selection set. - pub selection_set: Positioned, -} - -/// A type a fragment can apply to (`on` followed by the type). -/// -/// [Reference](https://spec.graphql.org/October2021/#TypeCondition). -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TypeCondition { - /// The type this fragment applies to. - pub on: Positioned, -} diff --git a/parser/src/types/mod.rs b/parser/src/types/mod.rs deleted file mode 100644 index 60ebb6dfd..000000000 --- a/parser/src/types/mod.rs +++ /dev/null @@ -1,182 +0,0 @@ -//! GraphQL types. -//! -//! The two root types are -//! [`ExecutableDocument`](struct.ExecutableDocument.html) and -//! [`ServiceDocument`](struct.ServiceDocument.html), representing an executable -//! GraphQL query and a GraphQL service respectively. -//! -//! This follows the [June 2018 edition of the GraphQL spec](https://spec.graphql.org/October2021/). - -mod executable; -mod service; - -use std::{ - collections::{HashMap, hash_map}, - fmt::{self, Display, Formatter, Write}, -}; - -use async_graphql_value::{ConstValue, Name, Value}; -pub use executable::*; -use serde::{Deserialize, Serialize}; -pub use service::*; - -use crate::pos::Positioned; - -/// The type of an operation; `query`, `mutation` or `subscription`. -/// -/// [Reference](https://spec.graphql.org/October2021/#OperationType). -#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] -pub enum OperationType { - /// A query. - Query, - /// A mutation. - Mutation, - /// A subscription. - Subscription, -} - -impl Display for OperationType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match self { - Self::Query => "query", - Self::Mutation => "mutation", - Self::Subscription => "subscription", - }) - } -} - -/// A GraphQL type, for example `String` or `[String!]!`. -/// -/// [Reference](https://spec.graphql.org/October2021/#Type). -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub struct Type { - /// The base type. - pub base: BaseType, - /// Whether the type is nullable. - pub nullable: bool, -} - -impl Type { - /// Create a type from the type string. - #[must_use] - pub fn new(ty: &str) -> Option { - let (nullable, ty) = if let Some(rest) = ty.strip_suffix('!') { - (false, rest) - } else { - (true, ty) - }; - - Some(Self { - base: if let Some(ty) = ty.strip_prefix('[') { - BaseType::List(Box::new(Self::new(ty.strip_suffix(']')?)?)) - } else { - BaseType::Named(Name::new(ty)) - }, - nullable, - }) - } -} - -impl Display for Type { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.base.fmt(f)?; - if !self.nullable { - f.write_char('!')?; - } - Ok(()) - } -} - -/// A GraphQL base type, for example `String` or `[String!]`. This does not -/// include whether the type is nullable; for that see [Type](struct.Type.html). -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub enum BaseType { - /// A named type, such as `String`. - Named(Name), - /// A list type, such as `[String]`. - List(Box), -} - -impl Display for BaseType { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Self::Named(name) => f.write_str(name), - Self::List(ty) => write!(f, "[{}]", ty), - } - } -} - -/// A const GraphQL directive, such as `@deprecated(reason: "Use the other -/// field)`. This differs from [`Directive`](struct.Directive.html) in that it -/// uses [`ConstValue`](enum.ConstValue.html) instead of -/// [`Value`](enum.Value.html). -/// -/// [Reference](https://spec.graphql.org/October2021/#Directive). -#[derive(Debug, Clone)] -pub struct ConstDirective { - /// The name of the directive. - pub name: Positioned, - /// The arguments to the directive. - pub arguments: Vec<(Positioned, Positioned)>, -} - -impl ConstDirective { - /// Convert this `ConstDirective` into a `Directive`. - #[must_use] - pub fn into_directive(self) -> Directive { - Directive { - name: self.name, - arguments: self - .arguments - .into_iter() - .map(|(name, value)| (name, value.map(ConstValue::into_value))) - .collect(), - } - } - - /// Get the argument with the given name. - #[must_use] - pub fn get_argument(&self, name: &str) -> Option<&Positioned> { - self.arguments - .iter() - .find(|item| item.0.node == name) - .map(|item| &item.1) - } -} - -/// A GraphQL directive, such as `@deprecated(reason: "Use the other field")`. -/// -/// [Reference](https://spec.graphql.org/October2021/#Directive). -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Directive { - /// The name of the directive. - pub name: Positioned, - /// The arguments to the directive. - pub arguments: Vec<(Positioned, Positioned)>, -} - -impl Directive { - /// Attempt to convert this `Directive` into a `ConstDirective`. - #[must_use] - pub fn into_const(self) -> Option { - Some(ConstDirective { - name: self.name, - arguments: self - .arguments - .into_iter() - .map(|(name, value)| { - Some((name, Positioned::new(value.node.into_const()?, value.pos))) - }) - .collect::>()?, - }) - } - - /// Get the argument with the given name. - #[must_use] - pub fn get_argument(&self, name: &str) -> Option<&Positioned> { - self.arguments - .iter() - .find(|item| item.0.node == name) - .map(|item| &item.1) - } -} diff --git a/parser/src/types/service.rs b/parser/src/types/service.rs deleted file mode 100644 index 76cbb3c83..000000000 --- a/parser/src/types/service.rs +++ /dev/null @@ -1,243 +0,0 @@ -//! Service-related GraphQL types. - -use super::*; - -/// A GraphQL file or request string defining a GraphQL service. -/// -/// [Reference](https://spec.graphql.org/October2021/#Document). -#[derive(Debug, Clone)] -pub struct ServiceDocument { - /// The definitions of this document. - pub definitions: Vec, -} - -/// A definition concerning the type system of a GraphQL service. -/// -/// [Reference](https://spec.graphql.org/October2021/#TypeSystemDefinition). This enum also covers -/// [extensions](https://spec.graphql.org/October2021/#TypeSystemExtension). -#[derive(Debug, Clone)] -pub enum TypeSystemDefinition { - /// The definition of the schema of the service. - Schema(Positioned), - /// The definition of a type in the service. - Type(Positioned), - /// The definition of a directive in the service. - Directive(Positioned), -} - -/// The definition of the schema in a GraphQL service. -/// -/// [Reference](https://spec.graphql.org/October2021/#SchemaDefinition). This also covers -/// [extensions](https://spec.graphql.org/October2021/#SchemaExtension). -#[derive(Debug, Clone)] -pub struct SchemaDefinition { - /// Whether the schema is an extension of another schema. - pub extend: bool, - /// The directives of the schema definition. - pub directives: Vec>, - /// The query root. This is always `Some` when the schema is not extended. - pub query: Option>, - /// The mutation root, if present. - pub mutation: Option>, - /// The subscription root, if present. - pub subscription: Option>, -} - -/// The definition of a type in a GraphQL service. -/// -/// [Reference](https://spec.graphql.org/October2021/#TypeDefinition). This also covers -/// [extensions](https://spec.graphql.org/October2021/#TypeExtension). -#[derive(Debug, Clone)] -pub struct TypeDefinition { - /// Whether the type is an extension of another type. - pub extend: bool, - /// The description of the type, if present. This is never present on an - /// extension type. - pub description: Option>, - /// The name of the type. - pub name: Positioned, - /// The directives of type definition. - pub directives: Vec>, - /// Which kind of type is being defined; scalar, object, enum, etc. - pub kind: TypeKind, -} - -/// A kind of type; scalar, object, enum, etc. -#[derive(Debug, Clone)] -pub enum TypeKind { - /// A scalar type. - Scalar, - /// An object type. - Object(ObjectType), - /// An interface type. - Interface(InterfaceType), - /// A union type. - Union(UnionType), - /// An enum type. - Enum(EnumType), - /// An input object type. - InputObject(InputObjectType), -} - -/// The definition of an object type. -/// -/// [Reference](https://spec.graphql.org/October2021/#ObjectType). -#[derive(Debug, Clone)] -pub struct ObjectType { - /// The interfaces implemented by the object. - pub implements: Vec>, - /// The fields of the object type. - pub fields: Vec>, -} - -/// The definition of a field inside an object or interface. -/// -/// [Reference](https://spec.graphql.org/October2021/#FieldDefinition). -#[derive(Debug, Clone)] -pub struct FieldDefinition { - /// The description of the field. - pub description: Option>, - /// The name of the field. - pub name: Positioned, - /// The arguments of the field. - pub arguments: Vec>, - /// The type of the field. - pub ty: Positioned, - /// The directives of the field. - pub directives: Vec>, -} - -/// The definition of an interface type. -/// -/// [Reference](https://spec.graphql.org/October2021/#InterfaceType). -#[derive(Debug, Clone)] -pub struct InterfaceType { - /// The interfaces implemented by the interface. - pub implements: Vec>, - /// The fields of the interface type. - pub fields: Vec>, -} - -/// The definition of a union type. -/// -/// [Reference](https://spec.graphql.org/October2021/#UnionType). -#[derive(Debug, Clone)] -pub struct UnionType { - /// The member types of the union. - pub members: Vec>, -} - -/// The definition of an enum. -/// -/// [Reference](https://spec.graphql.org/October2021/#EnumType). -#[derive(Debug, Clone)] -pub struct EnumType { - /// The possible values of the enum. - pub values: Vec>, -} - -/// The definition of a value inside an enum. -/// -/// [Reference](https://spec.graphql.org/October2021/#EnumValueDefinition). -#[derive(Debug, Clone)] -pub struct EnumValueDefinition { - /// The description of the argument. - pub description: Option>, - /// The value name. - pub value: Positioned, - /// The directives of the enum value. - pub directives: Vec>, -} - -/// The definition of an input object. -/// -/// [Reference](https://spec.graphql.org/October2021/#InputObjectType). -#[derive(Debug, Clone)] -pub struct InputObjectType { - /// The fields of the input object. - pub fields: Vec>, -} - -/// The definition of an input value inside the arguments of a field. -/// -/// [Reference](https://spec.graphql.org/October2021/#InputValueDefinition). -#[derive(Debug, Clone)] -pub struct InputValueDefinition { - /// The description of the argument. - pub description: Option>, - /// The name of the argument. - pub name: Positioned, - /// The type of the argument. - pub ty: Positioned, - /// The default value of the argument, if there is one. - pub default_value: Option>, - /// The directives of the input value. - pub directives: Vec>, -} - -/// The definition of a directive in a service. -/// -/// [Reference](https://spec.graphql.org/October2021/#DirectiveDefinition). -#[derive(Debug, Clone)] -pub struct DirectiveDefinition { - /// The description of the directive. - pub description: Option>, - /// The name of the directive. - pub name: Positioned, - /// The arguments of the directive. - pub arguments: Vec>, - /// Whether the directive can be repeated. - pub is_repeatable: bool, - /// The locations the directive applies to. - pub locations: Vec>, -} - -/// Where a directive can apply to. -/// -/// [Reference](https://spec.graphql.org/October2021/#DirectiveLocation). -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DirectiveLocation { - /// A [query](enum.OperationType.html#variant.Query) - /// [operation](struct.OperationDefinition.html). - Query, - /// A [mutation](enum.OperationType.html#variant.Mutation) - /// [operation](struct.OperationDefinition.html). - Mutation, - /// A [subscription](enum.OperationType.html#variant.Subscription) - /// [operation](struct.OperationDefinition.html). - Subscription, - /// A [field](struct.Field.html). - Field, - /// A [fragment definition](struct.FragmentDefinition.html). - FragmentDefinition, - /// A [fragment spread](struct.FragmentSpread.html). - FragmentSpread, - /// An [inline fragment](struct.InlineFragment.html). - InlineFragment, - /// A [schema](struct.Schema.html). - Schema, - /// A [scalar](enum.TypeKind.html#variant.Scalar). - Scalar, - /// An [object](struct.ObjectType.html). - Object, - /// A [field definition](struct.FieldDefinition.html). - FieldDefinition, - /// An [input value definition](struct.InputFieldDefinition.html) as the - /// arguments of a field but not an input object. - ArgumentDefinition, - /// An [interface](struct.InterfaceType.html). - Interface, - /// A [union](struct.UnionType.html). - Union, - /// An [enum](struct.EnumType.html). - Enum, - /// A [value on an enum](struct.EnumValueDefinition.html). - EnumValue, - /// An [input object](struct.InputObjectType.html). - InputObject, - /// An [input value definition](struct.InputValueDefinition.html) on an - /// input object but not a field. - InputFieldDefinition, - /// An [variable definition](struct.VariableDefinition.html). - VariableDefinition, -} diff --git a/parser/tests/codegen.rs b/parser/tests/codegen.rs deleted file mode 100644 index 18c590ac7..000000000 --- a/parser/tests/codegen.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! `pest_derive` crate has large dependency tree, and, as a build dependency, -//! it imposes these deps onto our consumers. -//! -//! To avoid that, let's just dump generated code to string into this -//! repository, and add a test that checks that the code is fresh. -use std::fs; - -const PREAMBLE: &str = r#" -//! This is @generated code, do not edit by hand. -//! See `graphql.pest` and `tests/codegen.rs`. -#![allow(unused_attributes)] -use super::GraphQLParser; -"#; - -#[test] -fn generated_code_is_fresh() { - let input = r###" -#[derive(Parser)] -#[grammar = r#"graphql.pest"#] -struct GraphQLParser; -"### - .to_string() - .parse::() - .unwrap(); - - let tokens = pest_generator::derive_parser(input, false); - let current = String::from_utf8(fs::read("./src/parse/generated.rs").unwrap()).unwrap(); - - let current_content = match current.len() > PREAMBLE.len() { - true => ¤t[PREAMBLE.len()..], - false => current.as_str(), - }; - - let new = tokens.to_string(); - let is_up_to_date = normalize(current_content) == normalize(&new); - - if is_up_to_date { - return; - } - - let code = format!("{PREAMBLE}\n{new}"); - fs::write("./src/parse/generated.rs", code).unwrap(); - panic!("Generated code in the repository is outdated, updating..."); -} - -fn normalize(code: &str) -> String { - code.replace(|c: char| c.is_ascii_whitespace() || "{},".contains(c), "") -} diff --git a/parser/tests/executables/directive_args.graphql b/parser/tests/executables/directive_args.graphql deleted file mode 100644 index 5937cce5e..000000000 --- a/parser/tests/executables/directive_args.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query { - node @dir(a: 1, b: "2", c: true, d: false, e: null) -} diff --git a/parser/tests/executables/fragment.graphql b/parser/tests/executables/fragment.graphql deleted file mode 100644 index 84ee95b39..000000000 --- a/parser/tests/executables/fragment.graphql +++ /dev/null @@ -1,5 +0,0 @@ -fragment frag on Friend { - __typename - node -} -{ __typename } diff --git a/parser/tests/executables/fragment_spread.graphql b/parser/tests/executables/fragment_spread.graphql deleted file mode 100644 index 940fb624e..000000000 --- a/parser/tests/executables/fragment_spread.graphql +++ /dev/null @@ -1,6 +0,0 @@ -query { - node { - id - ...something - } -} diff --git a/parser/tests/executables/inline_fragment.graphql b/parser/tests/executables/inline_fragment.graphql deleted file mode 100644 index f09fb5291..000000000 --- a/parser/tests/executables/inline_fragment.graphql +++ /dev/null @@ -1,8 +0,0 @@ -query { - node { - id - ... on User { - name - } - } -} diff --git a/parser/tests/executables/inline_fragment_dir.graphql b/parser/tests/executables/inline_fragment_dir.graphql deleted file mode 100644 index 4f9db15b2..000000000 --- a/parser/tests/executables/inline_fragment_dir.graphql +++ /dev/null @@ -1,8 +0,0 @@ -query { - node { - id - ... on User @defer { - name - } - } -} diff --git a/parser/tests/executables/kitchen-sink.graphql b/parser/tests/executables/kitchen-sink.graphql deleted file mode 100644 index 19d06b0db..000000000 --- a/parser/tests/executables/kitchen-sink.graphql +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright (c) 2015-present, Facebook, Inc. -# -# This source code is licensed under the MIT license found in the -# LICENSE file in the root directory of this source tree. - -query queryName($foo: ComplexType, $site: Site = MOBILE) { - whoever123is: node(id: [123, 456]) { - id , - ... on User @defer { - field2 { - id , - alias: field1(first:10, after:$foo,) @include(if: $foo) { - id, - ...frag - } - } - } - ... @skip(unless: $foo) { - id - } - ... { - id - } - } -} - -mutation likeStory { - like(story: 123) @defer { - story { - id - } - } -} - -subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) { - storyLikeSubscribe(input: $input) { - story { - likers { - count - } - likeSentence { - text - } - } - } -} - -fragment frag on Friend { - foo(size: $size, bar: $b, obj: {key: "value"}) -} - -query otherQuery { - unnamed(truthy: true, falsey: false, nullish: null), - query -} diff --git a/parser/tests/executables/kitchen-sink_canonical.graphql b/parser/tests/executables/kitchen-sink_canonical.graphql deleted file mode 100644 index acb4e36a5..000000000 --- a/parser/tests/executables/kitchen-sink_canonical.graphql +++ /dev/null @@ -1,50 +0,0 @@ -query queryName($foo: ComplexType, $site: Site = MOBILE) { - whoever123is: node(id: [123, 456]) { - id - ... on User @defer { - field2 { - id - alias: field1(first: 10, after: $foo) @include(if: $foo) { - id - ...frag - } - } - } - ... @skip(unless: $foo) { - id - } - ... { - id - } - } -} - -mutation likeStory { - like(story: 123) @defer { - story { - id - } - } -} - -subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) { - storyLikeSubscribe(input: $input) { - story { - likers { - count - } - likeSentence { - text - } - } - } -} - -fragment frag on Friend { - foo(size: $size, bar: $b, obj: {key: "value"}) -} - -query otherQuery { - unnamed(truthy: true, falsey: false, nullish: null) - query -} diff --git a/parser/tests/executables/minimal.graphql b/parser/tests/executables/minimal.graphql deleted file mode 100644 index e0d57a038..000000000 --- a/parser/tests/executables/minimal.graphql +++ /dev/null @@ -1,3 +0,0 @@ -{ - a -} diff --git a/parser/tests/executables/minimal_mutation.graphql b/parser/tests/executables/minimal_mutation.graphql deleted file mode 100644 index bf0c4eb54..000000000 --- a/parser/tests/executables/minimal_mutation.graphql +++ /dev/null @@ -1,3 +0,0 @@ -mutation { - notify -} diff --git a/parser/tests/executables/minimal_query.graphql b/parser/tests/executables/minimal_query.graphql deleted file mode 100644 index 5017efe9f..000000000 --- a/parser/tests/executables/minimal_query.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query { - node -} diff --git a/parser/tests/executables/multiline_string.graphql b/parser/tests/executables/multiline_string.graphql deleted file mode 100644 index 4d26a7aa0..000000000 --- a/parser/tests/executables/multiline_string.graphql +++ /dev/null @@ -1,8 +0,0 @@ -{ - a(s: """ - a - b - - c - """) -} \ No newline at end of file diff --git a/parser/tests/executables/mutation_directive.graphql b/parser/tests/executables/mutation_directive.graphql deleted file mode 100644 index 21b632162..000000000 --- a/parser/tests/executables/mutation_directive.graphql +++ /dev/null @@ -1,3 +0,0 @@ -mutation @directive { - node -} diff --git a/parser/tests/executables/named_query.graphql b/parser/tests/executables/named_query.graphql deleted file mode 100644 index f3f48a2c8..000000000 --- a/parser/tests/executables/named_query.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query Foo { - field -} diff --git a/parser/tests/executables/nested_selection.graphql b/parser/tests/executables/nested_selection.graphql deleted file mode 100644 index defe356cd..000000000 --- a/parser/tests/executables/nested_selection.graphql +++ /dev/null @@ -1,5 +0,0 @@ -query { - node { - id - } -} diff --git a/parser/tests/executables/query_aliases.graphql b/parser/tests/executables/query_aliases.graphql deleted file mode 100644 index 5069783ce..000000000 --- a/parser/tests/executables/query_aliases.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query { - an_alias: node -} diff --git a/parser/tests/executables/query_arguments.graphql b/parser/tests/executables/query_arguments.graphql deleted file mode 100644 index 44819c9fe..000000000 --- a/parser/tests/executables/query_arguments.graphql +++ /dev/null @@ -1,4 +0,0 @@ -query { - node(id: 1) - node2(id: -1, id1: -0, id2: 0, id3: -1.23, id4: 1.23, id5: 1.23e+2, id6: 1.23e+2, id7: 0.123) -} diff --git a/parser/tests/executables/query_directive.graphql b/parser/tests/executables/query_directive.graphql deleted file mode 100644 index e649dc17e..000000000 --- a/parser/tests/executables/query_directive.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query @directive { - node -} diff --git a/parser/tests/executables/query_list_argument.graphql b/parser/tests/executables/query_list_argument.graphql deleted file mode 100644 index 096e0c154..000000000 --- a/parser/tests/executables/query_list_argument.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query { - node(id: 1, list: [123, 456]) -} diff --git a/parser/tests/executables/query_object_argument.graphql b/parser/tests/executables/query_object_argument.graphql deleted file mode 100644 index 05379b053..000000000 --- a/parser/tests/executables/query_object_argument.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query { - node(id: 1, obj: {key1: 123, key2: 456}) -} diff --git a/parser/tests/executables/query_var_default_float.graphql b/parser/tests/executables/query_var_default_float.graphql deleted file mode 100644 index d3e03ff50..000000000 --- a/parser/tests/executables/query_var_default_float.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query Foo($site: Float = 0.5) { - field -} diff --git a/parser/tests/executables/query_var_default_list.graphql b/parser/tests/executables/query_var_default_list.graphql deleted file mode 100644 index 7b6a0af8a..000000000 --- a/parser/tests/executables/query_var_default_list.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query Foo($site: [Int] = [123, 456]) { - field -} diff --git a/parser/tests/executables/query_var_default_object.graphql b/parser/tests/executables/query_var_default_object.graphql deleted file mode 100644 index 69c6d4da8..000000000 --- a/parser/tests/executables/query_var_default_object.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query Foo($site: Site = {url: null}) { - field -} diff --git a/parser/tests/executables/query_var_default_string.graphql b/parser/tests/executables/query_var_default_string.graphql deleted file mode 100644 index 64bc38704..000000000 --- a/parser/tests/executables/query_var_default_string.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query Foo($site: String = "string") { - field -} diff --git a/parser/tests/executables/query_var_defaults.graphql b/parser/tests/executables/query_var_defaults.graphql deleted file mode 100644 index 91b11f8ff..000000000 --- a/parser/tests/executables/query_var_defaults.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query Foo($site: Site = MOBILE) { - field -} diff --git a/parser/tests/executables/query_vars.graphql b/parser/tests/executables/query_vars.graphql deleted file mode 100644 index cb43b2b1e..000000000 --- a/parser/tests/executables/query_vars.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query Foo($arg: SomeType) { - field -} diff --git a/parser/tests/executables/string_literal.graphql b/parser/tests/executables/string_literal.graphql deleted file mode 100644 index 160efb44d..000000000 --- a/parser/tests/executables/string_literal.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query { - node(id: "hello") -} diff --git a/parser/tests/executables/subscription_directive.graphql b/parser/tests/executables/subscription_directive.graphql deleted file mode 100644 index c643e53bf..000000000 --- a/parser/tests/executables/subscription_directive.graphql +++ /dev/null @@ -1,3 +0,0 @@ -subscription @directive { - node -} diff --git a/parser/tests/executables/variable_directive.graphql b/parser/tests/executables/variable_directive.graphql deleted file mode 100644 index b6a9d07b8..000000000 --- a/parser/tests/executables/variable_directive.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query Foo($a: Int @directive = 10, $b: Int @directive) { - value -} diff --git a/parser/tests/recursion_limit.rs b/parser/tests/recursion_limit.rs deleted file mode 100644 index a3ce4b2fb..000000000 --- a/parser/tests/recursion_limit.rs +++ /dev/null @@ -1,30 +0,0 @@ -use async_graphql_parser::*; - -#[test] -fn test_recursion_limit() { - let depth = 65; - let field = "a {".repeat(depth) + &"}".repeat(depth); - let query = format!("query {{ {} }}", field.replace("{}", "{b}")); - assert_eq!( - parse_query(query).unwrap_err(), - Error::RecursionLimitExceeded - ); -} - -#[test] -fn test_issue_1039() { - let query = r#" - fragment onboardingFull on OnboardingState { - license - } - - query globalConfig { - globalConfig { - onboarding { - ...onboardingFull - } - } - } - "#; - parse_query(query).unwrap(); -} diff --git a/parser/tests/services/directive.graphql b/parser/tests/services/directive.graphql deleted file mode 100644 index 456904bd4..000000000 --- a/parser/tests/services/directive.graphql +++ /dev/null @@ -1,8 +0,0 @@ -directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT - -directive @test1(service: String!) on FIELD_DEFINITION -directive @test2(service: String!) on FIELD -directive @test3(service: String!) on ENUM_VALUE -directive @test4(service: String!) on ENUM - -directive @test5(service: String!) on VARIABLE_DEFINITION diff --git a/parser/tests/services/directive_descriptions.graphql b/parser/tests/services/directive_descriptions.graphql deleted file mode 100644 index b17eaacd9..000000000 --- a/parser/tests/services/directive_descriptions.graphql +++ /dev/null @@ -1,19 +0,0 @@ -""" -Directs the executor to include this field or fragment only when the `if` argument is true. -""" -directive @include( - """ - Included when true. - """ - if: Boolean! -) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT - -""" -Directs the executor to skip this field or fragment when the `if` argument is true. -""" -directive @skip( - """ - Skipped when true. - """ - if: Boolean! -) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT diff --git a/parser/tests/services/directive_descriptions_canonical.graphql b/parser/tests/services/directive_descriptions_canonical.graphql deleted file mode 100644 index 6bc4d7330..000000000 --- a/parser/tests/services/directive_descriptions_canonical.graphql +++ /dev/null @@ -1,13 +0,0 @@ -""" - Directs the executor to include this field or fragment only when the `if` argument is true. -""" -directive @include(""" - Included when true. -""" if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT - -""" - Directs the executor to skip this field or fragment when the `if` argument is true. -""" -directive @skip(""" - Skipped when true. -""" if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT diff --git a/parser/tests/services/empty_union.graphql b/parser/tests/services/empty_union.graphql deleted file mode 100644 index cba4f2f4c..000000000 --- a/parser/tests/services/empty_union.graphql +++ /dev/null @@ -1 +0,0 @@ -union UndefinedUnion diff --git a/parser/tests/services/enum.graphql b/parser/tests/services/enum.graphql deleted file mode 100644 index 780726034..000000000 --- a/parser/tests/services/enum.graphql +++ /dev/null @@ -1,4 +0,0 @@ -enum Site { - DESKTOP - MOBILE -} diff --git a/parser/tests/services/extend_enum.graphql b/parser/tests/services/extend_enum.graphql deleted file mode 100644 index 12ce65612..000000000 --- a/parser/tests/services/extend_enum.graphql +++ /dev/null @@ -1,3 +0,0 @@ -extend enum Site { - VR -} diff --git a/parser/tests/services/extend_input.graphql b/parser/tests/services/extend_input.graphql deleted file mode 100644 index 64ddf3eda..000000000 --- a/parser/tests/services/extend_input.graphql +++ /dev/null @@ -1,3 +0,0 @@ -extend input InputType { - other: Float = 1.23e4 -} diff --git a/parser/tests/services/extend_input_canonical.graphql b/parser/tests/services/extend_input_canonical.graphql deleted file mode 100644 index df70f320b..000000000 --- a/parser/tests/services/extend_input_canonical.graphql +++ /dev/null @@ -1,3 +0,0 @@ -extend input InputType { - other: Float = 12300 -} diff --git a/parser/tests/services/extend_interface.graphql b/parser/tests/services/extend_interface.graphql deleted file mode 100644 index 5536dc770..000000000 --- a/parser/tests/services/extend_interface.graphql +++ /dev/null @@ -1,3 +0,0 @@ -extend interface Bar { - two(argument: InputType!): Type -} diff --git a/parser/tests/services/extend_object.graphql b/parser/tests/services/extend_object.graphql deleted file mode 100644 index 5e6920a3f..000000000 --- a/parser/tests/services/extend_object.graphql +++ /dev/null @@ -1,3 +0,0 @@ -extend type Foo { - seven(argument: [String]): Type -} diff --git a/parser/tests/services/extend_scalar.graphql b/parser/tests/services/extend_scalar.graphql deleted file mode 100644 index a6dc49cfe..000000000 --- a/parser/tests/services/extend_scalar.graphql +++ /dev/null @@ -1 +0,0 @@ -extend scalar CustomScalar @onScalar diff --git a/parser/tests/services/implements.graphql b/parser/tests/services/implements.graphql deleted file mode 100644 index 473bc7aaf..000000000 --- a/parser/tests/services/implements.graphql +++ /dev/null @@ -1,7 +0,0 @@ -type Type1 implements IOne - -type Type1 implements IOne & ITwo - -interface Type1 implements IOne - -interface Type1 implements IOne & ITwo diff --git a/parser/tests/services/implements_amp.graphql b/parser/tests/services/implements_amp.graphql deleted file mode 100644 index e4ff2d380..000000000 --- a/parser/tests/services/implements_amp.graphql +++ /dev/null @@ -1,2 +0,0 @@ -type Type1 implements & IOne & ITwo -type Type2 implements & IOne diff --git a/parser/tests/services/implements_amp_canonical.graphql b/parser/tests/services/implements_amp_canonical.graphql deleted file mode 100644 index f066126b1..000000000 --- a/parser/tests/services/implements_amp_canonical.graphql +++ /dev/null @@ -1,3 +0,0 @@ -type Type1 implements IOne & ITwo - -type Type2 implements IOne diff --git a/parser/tests/services/input_type.graphql b/parser/tests/services/input_type.graphql deleted file mode 100644 index db7634bac..000000000 --- a/parser/tests/services/input_type.graphql +++ /dev/null @@ -1,4 +0,0 @@ -input InputType { - key: String! - answer: Int = 42 -} diff --git a/parser/tests/services/interface.graphql b/parser/tests/services/interface.graphql deleted file mode 100644 index d167a481f..000000000 --- a/parser/tests/services/interface.graphql +++ /dev/null @@ -1,3 +0,0 @@ -interface Bar { - one: Type -} diff --git a/parser/tests/services/kitchen-sink.graphql b/parser/tests/services/kitchen-sink.graphql deleted file mode 100644 index c16d2d0f6..000000000 --- a/parser/tests/services/kitchen-sink.graphql +++ /dev/null @@ -1,118 +0,0 @@ -# Copyright (c) 2015-present, Facebook, Inc. -# -# This source code is licensed under the MIT license found in the -# LICENSE file in the root directory of this source tree. - -schema { - query: QueryType - mutation: MutationType -} - -""" -This is a description - -of the `Foo` type. -""" -type Foo implements Bar & Baz { - one: Type - two(argument: InputType!): Type - three(argument: InputType, other: String): Int - four(argument: String = "string"): String - five(argument: [String] = ["string", "string"]): String - six(argument: InputType = {key: "value"}): Type - seven(argument: Int = null): Type -} - -type AnnotatedObject @onObject(arg: "value") { - annotatedField(arg: Type = "default" @onArg): Type @onField -} - -type UndefinedType - -extend type Foo { - seven(argument: [String]): Type -} - -extend type Foo @onType - -interface Bar { - one: Type - four(argument: String = "string"): String -} - -interface AnnotatedInterface @onInterface { - annotatedField(arg: Type @onArg): Type @onField -} - -interface UndefinedInterface - -extend interface Bar { - two(argument: InputType!): Type -} - -extend interface Bar @onInterface - -union Feed = Story | Article | Advert - -union AnnotatedUnion @onUnion = A | B - -union AnnotatedUnionTwo @onUnion = | A | B - -union UndefinedUnion - -extend union Feed = Photo | Video - -extend union Feed @onUnion - -scalar CustomScalar - -scalar AnnotatedScalar @onScalar - -extend scalar CustomScalar @onScalar - -enum Site { - DESKTOP - MOBILE -} - -enum AnnotatedEnum @onEnum { - ANNOTATED_VALUE @onEnumValue - OTHER_VALUE -} - -enum UndefinedEnum - -extend enum Site { - VR -} - -extend enum Site @onEnum - -input InputType { - key: String! - answer: Int = 42 -} - -input AnnotatedInput @onInputObject { - annotatedField: Type @onField -} - -input UndefinedInput - -extend input InputType { - other: Float = 1.23e4 -} - -extend input InputType @onInputObject - -directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT - -directive @include(if: Boolean!) - on FIELD - | FRAGMENT_SPREAD - | INLINE_FRAGMENT - -directive @include2(if: Boolean!) on - | FIELD - | FRAGMENT_SPREAD - | INLINE_FRAGMENT diff --git a/parser/tests/services/kitchen-sink_canonical.graphql b/parser/tests/services/kitchen-sink_canonical.graphql deleted file mode 100644 index 7bdb4fadf..000000000 --- a/parser/tests/services/kitchen-sink_canonical.graphql +++ /dev/null @@ -1,106 +0,0 @@ -schema { - query: QueryType - mutation: MutationType -} - -""" - This is a description - of the `Foo` type. -""" -type Foo implements Bar & Baz { - one: Type - two(argument: InputType!): Type - three(argument: InputType, other: String): Int - four(argument: String = "string"): String - five(argument: [String] = ["string", "string"]): String - six(argument: InputType = {key: "value"}): Type - seven(argument: Int = null): Type -} - -type AnnotatedObject @onObject(arg: "value") { - annotatedField(arg: Type = "default" @onArg): Type @onField -} - -type UndefinedType - -extend type Foo { - seven(argument: [String]): Type -} - -extend type Foo @onType - -interface Bar { - one: Type - four(argument: String = "string"): String -} - -interface AnnotatedInterface @onInterface { - annotatedField(arg: Type @onArg): Type @onField -} - -interface UndefinedInterface - -extend interface Bar { - two(argument: InputType!): Type -} - -extend interface Bar @onInterface - -union Feed = Story | Article | Advert - -union AnnotatedUnion @onUnion = A | B - -union AnnotatedUnionTwo @onUnion = A | B - -union UndefinedUnion - -extend union Feed = Photo | Video - -extend union Feed @onUnion - -scalar CustomScalar - -scalar AnnotatedScalar @onScalar - -extend scalar CustomScalar @onScalar - -enum Site { - DESKTOP - MOBILE -} - -enum AnnotatedEnum @onEnum { - ANNOTATED_VALUE @onEnumValue - OTHER_VALUE -} - -enum UndefinedEnum - -extend enum Site { - VR -} - -extend enum Site @onEnum - -input InputType { - key: String! - answer: Int = 42 -} - -input AnnotatedInput @onInputObject { - annotatedField: Type @onField -} - -input UndefinedInput - -extend input InputType { - other: Float = 12300 -} - -extend input InputType @onInputObject - -directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT - -directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT - -directive @include2(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT diff --git a/parser/tests/services/minimal.graphql b/parser/tests/services/minimal.graphql deleted file mode 100644 index 614a1f13c..000000000 --- a/parser/tests/services/minimal.graphql +++ /dev/null @@ -1,3 +0,0 @@ -schema { - query: Query -} diff --git a/parser/tests/services/minimal_type.graphql b/parser/tests/services/minimal_type.graphql deleted file mode 100644 index c2df55881..000000000 --- a/parser/tests/services/minimal_type.graphql +++ /dev/null @@ -1 +0,0 @@ -type UndefinedType diff --git a/parser/tests/services/repeatable_directives.graphql b/parser/tests/services/repeatable_directives.graphql deleted file mode 100644 index c94bf8fda..000000000 --- a/parser/tests/services/repeatable_directives.graphql +++ /dev/null @@ -1,10 +0,0 @@ -directive @skip( - if: Boolean! -) repeatable on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT - -directive @test1(service: String!) repeatable on FIELD_DEFINITION -directive @test2(service: String!) repeatable on FIELD -directive @test3(service: String!) repeatable on ENUM_VALUE -directive @test4(service: String!) repeatable on ENUM - -directive @test5(service: String!) repeatable on VARIABLE_DEFINITION diff --git a/parser/tests/services/scalar_type.graphql b/parser/tests/services/scalar_type.graphql deleted file mode 100644 index 43955310a..000000000 --- a/parser/tests/services/scalar_type.graphql +++ /dev/null @@ -1,2 +0,0 @@ -"This is the best scalar type" -scalar BestType @perfectness(value: 100500) diff --git a/parser/tests/services/simple_object.graphql b/parser/tests/services/simple_object.graphql deleted file mode 100644 index a454e38ed..000000000 --- a/parser/tests/services/simple_object.graphql +++ /dev/null @@ -1,3 +0,0 @@ -type Foo { - bar: Type -} diff --git a/parser/tests/services/union.graphql b/parser/tests/services/union.graphql deleted file mode 100644 index ddadd4a8c..000000000 --- a/parser/tests/services/union.graphql +++ /dev/null @@ -1 +0,0 @@ -union Feed = Story | Article | Advert diff --git a/parser/tests/services/union_extension.graphql b/parser/tests/services/union_extension.graphql deleted file mode 100644 index a18d4648b..000000000 --- a/parser/tests/services/union_extension.graphql +++ /dev/null @@ -1 +0,0 @@ -extend union Feed = Photo | Video diff --git a/src/base.rs b/src/base.rs deleted file mode 100644 index daee87ce4..000000000 --- a/src/base.rs +++ /dev/null @@ -1,303 +0,0 @@ -#[cfg(not(feature = "boxed-trait"))] -use std::future::Future; -use std::{ - borrow::Cow, - sync::{Arc, Weak}, -}; - -use async_graphql_value::ConstValue; - -use crate::{ - ContainerType, Context, ContextSelectionSet, Error, InputValueError, InputValueResult, - Positioned, Result, ServerResult, Value, - parser::types::Field, - registry::{self, Registry}, -}; - -#[doc(hidden)] -pub trait Description { - fn description() -> &'static str; -} - -/// Used to specify the GraphQL Type name. -pub trait TypeName: Send + Sync { - /// Returns a GraphQL type name. - fn type_name() -> Cow<'static, str>; -} - -/// Represents a GraphQL input type. -pub trait InputType: Send + Sync + Sized { - /// The raw type used for validator. - /// - /// Usually it is `Self`, but the wrapper type is its internal type. - /// - /// For example: - /// - /// `i32::RawValueType` is `i32` - /// `Option::RawValueType` is `i32`. - type RawValueType: ?Sized; - - /// Type the name. - fn type_name() -> Cow<'static, str>; - - /// Qualified typename. - fn qualified_type_name() -> String { - format!("{}!", Self::type_name()) - } - - /// Create type information in the registry and return qualified typename. - fn create_type_info(registry: &mut registry::Registry) -> String; - - /// Parse from `Value`. None represents undefined. - fn parse(value: Option) -> InputValueResult; - - /// Convert to a `Value` for introspection. - fn to_value(&self) -> Value; - - /// Get the federation fields, only for InputObject. - #[doc(hidden)] - fn federation_fields() -> Option { - None - } - - /// Returns a reference to the raw value. - fn as_raw_value(&self) -> Option<&Self::RawValueType>; -} - -/// Represents a GraphQL output type. -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -pub trait OutputType: Send + Sync { - /// Type the name. - fn type_name() -> Cow<'static, str>; - - /// Qualified typename. - fn qualified_type_name() -> String { - format!("{}!", Self::type_name()) - } - - /// Introspection type name - /// - /// Is the return value of field `__typename`, the interface and union - /// should return the current type, and the others return `Type::type_name`. - fn introspection_type_name(&self) -> Cow<'static, str> { - Self::type_name() - } - - /// Create type information in the registry and return qualified typename. - fn create_type_info(registry: &mut registry::Registry) -> String; - - /// Resolve an output value to `async_graphql::Value`. - #[cfg(feature = "boxed-trait")] - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult; - - /// Resolve an output value to `async_graphql::Value`. - #[cfg(not(feature = "boxed-trait"))] - fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> impl Future> + Send; -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for &T { - fn type_name() -> Cow<'static, str> { - T::type_name() - } - - fn create_type_info(registry: &mut Registry) -> String { - T::create_type_info(registry) - } - - #[allow(clippy::trivially_copy_pass_by_ref)] - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { - T::resolve(*self, ctx, field).await - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl + Send + Sync + Clone> OutputType for Result { - fn type_name() -> Cow<'static, str> { - T::type_name() - } - - fn create_type_info(registry: &mut Registry) -> String { - T::create_type_info(registry) - } - - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { - match self { - Ok(value) => value.resolve(ctx, field).await, - Err(err) => Err(ctx.set_error_path(err.clone().into().into_server_error(field.pos))), - } - } -} - -/// A GraphQL object. -pub trait ObjectType: ContainerType {} - -impl ObjectType for &T {} - -impl ObjectType for Box {} - -impl ObjectType for Arc {} - -/// A GraphQL interface. -pub trait InterfaceType: ContainerType {} - -/// A GraphQL interface. -pub trait UnionType: ContainerType {} - -/// A GraphQL input object. -pub trait InputObjectType: InputType {} - -/// A GraphQL oneof input object. -pub trait OneofObjectType: InputObjectType {} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for Box { - fn type_name() -> Cow<'static, str> { - T::type_name() - } - - fn create_type_info(registry: &mut Registry) -> String { - T::create_type_info(registry) - } - - #[cfg(feature = "boxed-trait")] - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { - T::resolve(self.as_ref(), ctx, field).await - } - - #[allow(clippy::trivially_copy_pass_by_ref)] - #[cfg(not(feature = "boxed-trait"))] - fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> impl Future> + Send { - T::resolve(self.as_ref(), ctx, field) - } -} - -impl InputType for Box { - type RawValueType = T::RawValueType; - - fn type_name() -> Cow<'static, str> { - T::type_name() - } - - fn create_type_info(registry: &mut Registry) -> String { - T::create_type_info(registry) - } - - fn parse(value: Option) -> InputValueResult { - T::parse(value) - .map(Box::new) - .map_err(InputValueError::propagate) - } - - fn to_value(&self) -> ConstValue { - T::to_value(&self) - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - self.as_ref().as_raw_value() - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for Arc { - fn type_name() -> Cow<'static, str> { - T::type_name() - } - - fn create_type_info(registry: &mut Registry) -> String { - T::create_type_info(registry) - } - - #[allow(clippy::trivially_copy_pass_by_ref)] - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { - T::resolve(&**self, ctx, field).await - } -} - -impl InputType for Arc { - type RawValueType = T::RawValueType; - - fn type_name() -> Cow<'static, str> { - T::type_name() - } - - fn create_type_info(registry: &mut Registry) -> String { - T::create_type_info(registry) - } - - fn parse(value: Option) -> InputValueResult { - T::parse(value) - .map(Arc::new) - .map_err(InputValueError::propagate) - } - - fn to_value(&self) -> ConstValue { - T::to_value(&self) - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - self.as_ref().as_raw_value() - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for Weak { - fn type_name() -> Cow<'static, str> { - > as OutputType>::type_name() - } - - fn create_type_info(registry: &mut Registry) -> String { - > as OutputType>::create_type_info(registry) - } - - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { - self.upgrade().resolve(ctx, field).await - } -} - -#[doc(hidden)] -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -pub trait ComplexObject { - fn fields(registry: &mut registry::Registry) -> Vec<(String, registry::MetaField)>; - - #[cfg(feature = "boxed-trait")] - async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult>; - - #[cfg(not(feature = "boxed-trait"))] - fn resolve_field( - &self, - ctx: &Context<'_>, - ) -> impl Future>> + Send; -} diff --git a/src/context.rs b/src/context.rs deleted file mode 100644 index 1a92315f9..000000000 --- a/src/context.rs +++ /dev/null @@ -1,890 +0,0 @@ -//! Query context. - -use std::{ - any::{Any, TypeId}, - collections::HashMap, - fmt::{self, Debug, Display, Formatter}, - ops::Deref, - sync::{Arc, Mutex}, -}; - -use async_graphql_parser::types::ConstDirective; -use async_graphql_value::{Value as InputValue, Variables}; -use fnv::FnvHashMap; -use serde::{ - Serialize, - ser::{SerializeSeq, Serializer}, -}; - -use crate::{ - Error, InputType, Lookahead, Name, OneofObjectType, PathSegment, Pos, Positioned, Result, - ServerError, ServerResult, UploadValue, Value, - extensions::Extensions, - parser::types::{ - Directive, Field, FragmentDefinition, OperationDefinition, Selection, SelectionSet, - }, - schema::{IntrospectionMode, SchemaEnv}, -}; - -/// Data related functions of the context. -pub trait DataContext<'a> { - /// Gets the global data defined in the `Context` or `Schema`. - /// - /// If both `Schema` and `Query` have the same data type, the data in the - /// `Query` is obtained. - /// - /// # Errors - /// - /// Returns a `Error` if the specified type data does not exist. - fn data(&self) -> Result<&'a D>; - - /// Gets the global data defined in the `Context` or `Schema`. - /// - /// # Panics - /// - /// It will panic if the specified data type does not exist. - fn data_unchecked(&self) -> &'a D; - - /// Gets the global data defined in the `Context` or `Schema` or `None` if - /// the specified type data does not exist. - fn data_opt(&self) -> Option<&'a D>; -} - -/// Schema/Context data. -/// -/// This is a type map, allowing you to store anything inside it. -#[derive(Default)] -pub struct Data(FnvHashMap>); - -impl Deref for Data { - type Target = FnvHashMap>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Data { - /// Insert data. - pub fn insert(&mut self, data: D) { - self.0.insert(TypeId::of::(), Box::new(data)); - } - - pub(crate) fn merge(&mut self, other: Data) { - self.0.extend(other.0); - } -} - -impl Debug for Data { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_tuple("Data").finish() - } -} - -/// Context for `SelectionSet` -pub type ContextSelectionSet<'a> = ContextBase<'a, &'a Positioned>; - -/// Context object for resolve field -pub type Context<'a> = ContextBase<'a, &'a Positioned>; - -/// Context object for execute directive. -pub type ContextDirective<'a> = ContextBase<'a, &'a Positioned>; - -/// A segment in the path to the current query. -/// -/// This is a borrowed form of [`PathSegment`](enum.PathSegment.html) used -/// during execution instead of passed back when errors occur. -#[derive(Debug, Clone, Copy, Serialize)] -#[serde(untagged)] -pub enum QueryPathSegment<'a> { - /// We are currently resolving an element in a list. - Index(usize), - /// We are currently resolving a field in an object. - Name(&'a str), -} - -/// A path to the current query. -/// -/// The path is stored as a kind of reverse linked list. -#[derive(Debug, Clone, Copy)] -pub struct QueryPathNode<'a> { - /// The parent node to this, if there is one. - pub parent: Option<&'a QueryPathNode<'a>>, - - /// The current path segment being resolved. - pub segment: QueryPathSegment<'a>, -} - -impl serde::Serialize for QueryPathNode<'_> { - fn serialize(&self, serializer: S) -> Result { - let mut seq = serializer.serialize_seq(None)?; - self.try_for_each(|segment| seq.serialize_element(segment))?; - seq.end() - } -} - -impl Display for QueryPathNode<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut first = true; - self.try_for_each(|segment| { - if !first { - write!(f, ".")?; - } - first = false; - - match segment { - QueryPathSegment::Index(idx) => write!(f, "{}", *idx), - QueryPathSegment::Name(name) => write!(f, "{}", name), - } - }) - } -} - -impl<'a> QueryPathNode<'a> { - /// Get the current field name. - /// - /// This traverses all the parents of the node until it finds one that is a - /// field name. - pub fn field_name(&self) -> &str { - std::iter::once(self) - .chain(self.parents()) - .find_map(|node| match node.segment { - QueryPathSegment::Name(name) => Some(name), - QueryPathSegment::Index(_) => None, - }) - .unwrap() - } - - /// Get the path represented by `Vec`; numbers will be stringified. - #[must_use] - pub fn to_string_vec(self) -> Vec { - let mut res = Vec::new(); - self.for_each(|s| { - res.push(match s { - QueryPathSegment::Name(name) => (*name).to_string(), - QueryPathSegment::Index(idx) => idx.to_string(), - }); - }); - res - } - - /// Iterate over the parents of the node. - pub fn parents(&self) -> Parents<'_> { - Parents(self) - } - - pub(crate) fn for_each)>(&self, mut f: F) { - let _ = self.try_for_each::(|segment| { - f(segment); - Ok(()) - }); - } - - pub(crate) fn try_for_each) -> Result<(), E>>( - &self, - mut f: F, - ) -> Result<(), E> { - self.try_for_each_ref(&mut f) - } - - fn try_for_each_ref) -> Result<(), E>>( - &self, - f: &mut F, - ) -> Result<(), E> { - if let Some(parent) = &self.parent { - parent.try_for_each_ref(f)?; - } - f(&self.segment) - } -} - -/// An iterator over the parents of a -/// [`QueryPathNode`](struct.QueryPathNode.html). -#[derive(Debug, Clone)] -pub struct Parents<'a>(&'a QueryPathNode<'a>); - -impl<'a> Parents<'a> { - /// Get the current query path node, which the next call to `next` will get - /// the parents of. - #[must_use] - pub fn current(&self) -> &'a QueryPathNode<'a> { - self.0 - } -} - -impl<'a> Iterator for Parents<'a> { - type Item = &'a QueryPathNode<'a>; - - fn next(&mut self) -> Option { - let parent = self.0.parent; - if let Some(parent) = parent { - self.0 = parent; - } - parent - } -} - -impl std::iter::FusedIterator for Parents<'_> {} - -/// Query context. -/// -/// **This type is not stable and should not be used directly.** -#[derive(Clone)] -pub struct ContextBase<'a, T> { - /// The current path node being resolved. - pub path_node: Option>, - /// If `true` means the current field is for introspection. - pub(crate) is_for_introspection: bool, - #[doc(hidden)] - pub item: T, - #[doc(hidden)] - pub schema_env: &'a SchemaEnv, - #[doc(hidden)] - pub query_env: &'a QueryEnv, - #[doc(hidden)] - pub execute_data: Option<&'a Data>, -} - -#[doc(hidden)] -pub struct QueryEnvInner { - pub extensions: Extensions, - pub variables: Variables, - pub operation_name: Option, - pub operation: Positioned, - pub fragments: HashMap>, - pub uploads: Vec, - pub session_data: Arc, - pub query_data: Arc, - pub http_headers: Mutex, - pub introspection_mode: IntrospectionMode, - pub errors: Mutex>, -} - -#[doc(hidden)] -#[derive(Clone)] -pub struct QueryEnv(Arc); - -impl Deref for QueryEnv { - type Target = QueryEnvInner; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl QueryEnv { - #[doc(hidden)] - pub fn new(inner: QueryEnvInner) -> QueryEnv { - QueryEnv(Arc::new(inner)) - } - - #[doc(hidden)] - pub fn create_context<'a, T>( - &'a self, - schema_env: &'a SchemaEnv, - path_node: Option>, - item: T, - execute_data: Option<&'a Data>, - ) -> ContextBase<'a, T> { - ContextBase { - path_node, - is_for_introspection: false, - item, - schema_env, - query_env: self, - execute_data, - } - } -} - -impl<'a, T> DataContext<'a> for ContextBase<'a, T> { - fn data(&self) -> Result<&'a D> { - ContextBase::data::(self) - } - - fn data_unchecked(&self) -> &'a D { - ContextBase::data_unchecked::(self) - } - - fn data_opt(&self) -> Option<&'a D> { - ContextBase::data_opt::(self) - } -} - -impl<'a, T> ContextBase<'a, T> { - #[doc(hidden)] - pub fn with_field( - &'a self, - field: &'a Positioned, - ) -> ContextBase<'a, &'a Positioned> { - ContextBase { - path_node: Some(QueryPathNode { - parent: self.path_node.as_ref(), - segment: QueryPathSegment::Name(&field.node.response_key().node), - }), - is_for_introspection: self.is_for_introspection, - item: field, - schema_env: self.schema_env, - query_env: self.query_env, - execute_data: self.execute_data, - } - } - - #[doc(hidden)] - pub fn with_selection_set( - &self, - selection_set: &'a Positioned, - ) -> ContextBase<'a, &'a Positioned> { - ContextBase { - path_node: self.path_node, - is_for_introspection: self.is_for_introspection, - item: selection_set, - schema_env: self.schema_env, - query_env: self.query_env, - execute_data: self.execute_data, - } - } - - #[doc(hidden)] - pub fn set_error_path(&self, error: ServerError) -> ServerError { - if let Some(node) = self.path_node { - let mut path = Vec::new(); - node.for_each(|current_node| { - path.push(match current_node { - QueryPathSegment::Name(name) => PathSegment::Field((*name).to_string()), - QueryPathSegment::Index(idx) => PathSegment::Index(*idx), - }) - }); - ServerError { path, ..error } - } else { - error - } - } - - /// Report a resolver error. - /// - /// When implementing `OutputType`, if an error occurs, call this function - /// to report this error and return `Value::Null`. - pub fn add_error(&self, error: ServerError) { - self.query_env.errors.lock().unwrap().push(error); - } - - /// Gets the global data defined in the `Context` or `Schema`. - /// - /// If both `Schema` and `Query` have the same data type, the data in the - /// `Query` is obtained. - /// - /// # Errors - /// - /// Returns a `Error` if the specified type data does not exist. - pub fn data(&self) -> Result<&'a D> { - self.data_opt::().ok_or_else(|| { - Error::new(format!( - "Data `{}` does not exist.", - std::any::type_name::() - )) - }) - } - - /// Gets the global data defined in the `Context` or `Schema`. - /// - /// # Panics - /// - /// It will panic if the specified data type does not exist. - pub fn data_unchecked(&self) -> &'a D { - self.data_opt::() - .unwrap_or_else(|| panic!("Data `{}` does not exist.", std::any::type_name::())) - } - - /// Gets the global data defined in the `Context` or `Schema` or `None` if - /// the specified type data does not exist. - pub fn data_opt(&self) -> Option<&'a D> { - self.execute_data - .as_ref() - .and_then(|execute_data| execute_data.get(&TypeId::of::())) - .or_else(|| self.query_env.query_data.0.get(&TypeId::of::())) - .or_else(|| self.query_env.session_data.0.get(&TypeId::of::())) - .or_else(|| self.schema_env.data.0.get(&TypeId::of::())) - .and_then(|d| d.downcast_ref::()) - } - - /// Returns whether the HTTP header `key` is currently set on the response - /// - /// # Examples - /// - /// ```no_run - /// use ::http::header::ACCESS_CONTROL_ALLOW_ORIGIN; - /// use async_graphql::*; - /// - /// struct Query; - /// - /// #[Object] - /// impl Query { - /// async fn greet(&self, ctx: &Context<'_>) -> String { - /// let header_exists = ctx.http_header_contains("Access-Control-Allow-Origin"); - /// assert!(!header_exists); - /// - /// ctx.insert_http_header(ACCESS_CONTROL_ALLOW_ORIGIN, "*"); - /// - /// let header_exists = ctx.http_header_contains("Access-Control-Allow-Origin"); - /// assert!(header_exists); - /// - /// String::from("Hello world") - /// } - /// } - /// ``` - pub fn http_header_contains(&self, key: impl http::header::AsHeaderName) -> bool { - self.query_env - .http_headers - .lock() - .unwrap() - .contains_key(key) - } - - /// Sets a HTTP header to response. - /// - /// If the header was not currently set on the response, then `None` is - /// returned. - /// - /// If the response already contained this header then the new value is - /// associated with this key and __all the previous values are - /// removed__, however only a the first previous value is returned. - /// - /// See [`http::HeaderMap`] for more details on the underlying - /// implementation - /// - /// # Examples - /// - /// ```no_run - /// use ::http::{HeaderValue, header::ACCESS_CONTROL_ALLOW_ORIGIN}; - /// use async_graphql::*; - /// - /// struct Query; - /// - /// #[Object] - /// impl Query { - /// async fn greet(&self, ctx: &Context<'_>) -> String { - /// // Headers can be inserted using the `http` constants - /// let was_in_headers = ctx.insert_http_header(ACCESS_CONTROL_ALLOW_ORIGIN, "*"); - /// assert_eq!(was_in_headers, None); - /// - /// // They can also be inserted using &str - /// let was_in_headers = ctx.insert_http_header("Custom-Header", "1234"); - /// assert_eq!(was_in_headers, None); - /// - /// // If multiple headers with the same key are `inserted` then the most recent - /// // one overwrites the previous. If you want multiple headers for the same key, use - /// // `append_http_header` for subsequent headers - /// let was_in_headers = ctx.insert_http_header("Custom-Header", "Hello World"); - /// assert_eq!(was_in_headers, Some(HeaderValue::from_static("1234"))); - /// - /// String::from("Hello world") - /// } - /// } - /// ``` - pub fn insert_http_header( - &self, - name: impl http::header::IntoHeaderName, - value: impl TryInto, - ) -> Option { - if let Ok(value) = value.try_into() { - self.query_env - .http_headers - .lock() - .unwrap() - .insert(name, value) - } else { - None - } - } - - /// Sets a HTTP header to response. - /// - /// If the header was not currently set on the response, then `false` is - /// returned. - /// - /// If the response did have this header then the new value is appended to - /// the end of the list of values currently associated with the key, - /// however the key is not updated _(which is important for types that - /// can be `==` without being identical)_. - /// - /// See [`http::HeaderMap`] for more details on the underlying - /// implementation - /// - /// # Examples - /// - /// ```no_run - /// use ::http::header::SET_COOKIE; - /// use async_graphql::*; - /// - /// struct Query; - /// - /// #[Object] - /// impl Query { - /// async fn greet(&self, ctx: &Context<'_>) -> String { - /// // Insert the first instance of the header - /// ctx.insert_http_header(SET_COOKIE, "Chocolate Chip"); - /// - /// // Subsequent values should be appended - /// let header_already_exists = ctx.append_http_header("Set-Cookie", "Macadamia"); - /// assert!(header_already_exists); - /// - /// String::from("Hello world") - /// } - /// } - /// ``` - pub fn append_http_header( - &self, - name: impl http::header::IntoHeaderName, - value: impl TryInto, - ) -> bool { - if let Ok(value) = value.try_into() { - self.query_env - .http_headers - .lock() - .unwrap() - .append(name, value) - } else { - false - } - } - - fn var_value(&self, name: &str, pos: Pos) -> ServerResult { - self.query_env - .operation - .node - .variable_definitions - .iter() - .find(|def| def.node.name.node == name) - .and_then(|def| { - self.query_env - .variables - .get(&def.node.name.node) - .or_else(|| def.node.default_value()) - }) - .cloned() - .ok_or_else(|| { - ServerError::new(format!("Variable {} is not defined.", name), Some(pos)) - }) - } - - pub(crate) fn resolve_input_value(&self, value: Positioned) -> ServerResult { - let pos = value.pos; - value - .node - .into_const_with(|name| self.var_value(&name, pos)) - } - - #[doc(hidden)] - fn get_param_value( - &self, - arguments: &[(Positioned, Positioned)], - name: &str, - default: Option Q>, - ) -> ServerResult<(Pos, Q)> { - let value = arguments - .iter() - .find(|(n, _)| n.node.as_str() == name) - .map(|(_, value)| value) - .cloned(); - if value.is_none() { - if let Some(default) = default { - return Ok((Pos::default(), default())); - } - } - let (pos, value) = match value { - Some(value) => (value.pos, Some(self.resolve_input_value(value)?)), - None => (Pos::default(), None), - }; - InputType::parse(value) - .map(|value| (pos, value)) - .map_err(|e| e.into_server_error(pos)) - } - - #[doc(hidden)] - #[must_use] - pub fn with_index(&'a self, idx: usize) -> ContextBase<'a, T> - where - T: Copy, - { - ContextBase { - path_node: Some(QueryPathNode { - parent: self.path_node.as_ref(), - segment: QueryPathSegment::Index(idx), - }), - is_for_introspection: self.is_for_introspection, - item: self.item, - schema_env: self.schema_env, - query_env: self.query_env, - execute_data: self.execute_data, - } - } -} - -impl<'a> ContextBase<'a, &'a Positioned> { - #[doc(hidden)] - pub fn param_value( - &self, - name: &str, - default: Option T>, - ) -> ServerResult<(Pos, T)> { - self.get_param_value(&self.item.node.arguments, name, default) - } - - #[doc(hidden)] - pub fn oneof_param_value(&self) -> ServerResult<(Pos, T)> { - use indexmap::IndexMap; - - let mut map = IndexMap::new(); - - for (name, value) in &self.item.node.arguments { - let value = self.resolve_input_value(value.clone())?; - map.insert(name.node.clone(), value); - } - - InputType::parse(Some(Value::Object(map))) - .map(|value| (self.item.pos, value)) - .map_err(|e| e.into_server_error(self.item.pos)) - } - - /// Creates a uniform interface to inspect the forthcoming selections. - /// - /// # Examples - /// - /// ```no_run - /// use async_graphql::*; - /// - /// #[derive(SimpleObject)] - /// struct Detail { - /// c: i32, - /// d: i32, - /// } - /// - /// #[derive(SimpleObject)] - /// struct MyObj { - /// a: i32, - /// b: i32, - /// detail: Detail, - /// } - /// - /// struct Query; - /// - /// #[Object] - /// impl Query { - /// async fn obj(&self, ctx: &Context<'_>) -> MyObj { - /// if ctx.look_ahead().field("a").exists() { - /// // This is a query like `obj { a }` - /// } else if ctx.look_ahead().field("detail").field("c").exists() { - /// // This is a query like `obj { detail { c } }` - /// } else { - /// // This query doesn't have `a` - /// } - /// unimplemented!() - /// } - /// } - /// ``` - pub fn look_ahead(&self) -> Lookahead { - Lookahead::new(&self.query_env.fragments, &self.item.node, self) - } - - /// Get the current field. - /// - /// # Examples - /// - /// ```rust - /// use async_graphql::*; - /// - /// #[derive(SimpleObject)] - /// struct MyObj { - /// a: i32, - /// b: i32, - /// c: i32, - /// } - /// - /// pub struct Query; - /// - /// #[Object] - /// impl Query { - /// async fn obj(&self, ctx: &Context<'_>) -> MyObj { - /// let fields = ctx - /// .field() - /// .selection_set() - /// .map(|field| field.name()) - /// .collect::>(); - /// assert_eq!(fields, vec!["a", "b", "c"]); - /// MyObj { a: 1, b: 2, c: 3 } - /// } - /// } - /// - /// # tokio::runtime::Runtime::new().unwrap().block_on(async move { - /// let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - /// assert!(schema.execute("{ obj { a b c }}").await.is_ok()); - /// assert!(schema.execute("{ obj { a ... { b c } }}").await.is_ok()); - /// assert!( - /// schema - /// .execute("{ obj { a ... BC }} fragment BC on MyObj { b c }") - /// .await - /// .is_ok() - /// ); - /// # }); - /// ``` - pub fn field(&self) -> SelectionField { - SelectionField { - fragments: &self.query_env.fragments, - field: &self.item.node, - context: self, - } - } -} - -impl<'a> ContextBase<'a, &'a Positioned> { - #[doc(hidden)] - pub fn param_value( - &self, - name: &str, - default: Option T>, - ) -> ServerResult<(Pos, T)> { - self.get_param_value(&self.item.node.arguments, name, default) - } -} - -/// Selection field. -#[derive(Clone, Copy)] -pub struct SelectionField<'a> { - pub(crate) fragments: &'a HashMap>, - pub(crate) field: &'a Field, - pub(crate) context: &'a Context<'a>, -} - -impl<'a> SelectionField<'a> { - /// Get the name of this field. - #[inline] - pub fn name(&self) -> &'a str { - self.field.name.node.as_str() - } - - /// Get the alias of this field. - #[inline] - pub fn alias(&self) -> Option<&'a str> { - self.field.alias.as_ref().map(|alias| alias.node.as_str()) - } - - /// Get the directives of this field. - pub fn directives(&self) -> ServerResult> { - let mut directives = Vec::with_capacity(self.field.directives.len()); - - for directive in &self.field.directives { - let directive = &directive.node; - - let mut arguments = Vec::with_capacity(directive.arguments.len()); - for (name, value) in &directive.arguments { - let pos = name.pos; - arguments.push(( - name.clone(), - value.position_node( - value - .node - .clone() - .into_const_with(|name| self.context.var_value(&name, pos))?, - ), - )); - } - - directives.push(ConstDirective { - name: directive.name.clone(), - arguments, - }); - } - - Ok(directives) - } - - /// Get the arguments of this field. - pub fn arguments(&self) -> ServerResult> { - let mut arguments = Vec::with_capacity(self.field.arguments.len()); - for (name, value) in &self.field.arguments { - let pos = name.pos; - arguments.push(( - name.node.clone(), - value - .clone() - .node - .into_const_with(|name| self.context.var_value(&name, pos))?, - )); - } - Ok(arguments) - } - - /// Get all subfields of the current selection set. - pub fn selection_set(&self) -> impl Iterator> { - SelectionFieldsIter { - fragments: self.fragments, - iter: vec![self.field.selection_set.node.items.iter()], - context: self.context, - } - } -} - -impl Debug for SelectionField<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - struct DebugSelectionSet<'a>(Vec>); - - impl Debug for DebugSelectionSet<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_list().entries(&self.0).finish() - } - } - - f.debug_struct(self.name()) - .field("name", &self.name()) - .field( - "selection_set", - &DebugSelectionSet(self.selection_set().collect()), - ) - .finish() - } -} - -struct SelectionFieldsIter<'a> { - fragments: &'a HashMap>, - iter: Vec>>, - context: &'a Context<'a>, -} - -impl<'a> Iterator for SelectionFieldsIter<'a> { - type Item = SelectionField<'a>; - - fn next(&mut self) -> Option { - loop { - let it = self.iter.last_mut()?; - let item = it.next(); - - match item { - Some(selection) => match &selection.node { - Selection::Field(field) => { - return Some(SelectionField { - fragments: self.fragments, - field: &field.node, - context: self.context, - }); - } - Selection::FragmentSpread(fragment_spread) => { - if let Some(fragment) = - self.fragments.get(&fragment_spread.node.fragment_name.node) - { - self.iter - .push(fragment.node.selection_set.node.items.iter()); - } - } - Selection::InlineFragment(inline_fragment) => { - self.iter - .push(inline_fragment.node.selection_set.node.items.iter()); - } - }, - None => { - self.iter.pop(); - } - } - } - } -} diff --git a/src/custom_directive.rs b/src/custom_directive.rs deleted file mode 100644 index d9e243751..000000000 --- a/src/custom_directive.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::borrow::Cow; - -use crate::{ - Context, ContextDirective, ServerResult, Value, extensions::ResolveFut, - parser::types::Directive, registry::Registry, -}; - -#[doc(hidden)] -pub trait CustomDirectiveFactory: Send + Sync + 'static { - fn name(&self) -> Cow<'static, str>; - - fn register(&self, registry: &mut Registry); - - fn create( - &self, - ctx: &ContextDirective<'_>, - directive: &Directive, - ) -> ServerResult>; -} - -#[doc(hidden)] -// minimal amount required to register directive into registry -pub trait TypeDirective { - fn name(&self) -> Cow<'static, str>; - - fn register(&self, registry: &mut Registry); -} - -/// Represents a custom directive. -#[async_trait::async_trait] -#[allow(unused_variables)] -pub trait CustomDirective: Sync + Send + 'static { - /// Called at resolve field. - async fn resolve_field( - &self, - ctx: &Context<'_>, - resolve: ResolveFut<'_>, - ) -> ServerResult> { - resolve.await - } -} diff --git a/src/dataloader/cache.rs b/src/dataloader/cache.rs deleted file mode 100644 index c0c50b29d..000000000 --- a/src/dataloader/cache.rs +++ /dev/null @@ -1,215 +0,0 @@ -use std::{ - borrow::Cow, - collections::{HashMap, hash_map::RandomState}, - hash::{BuildHasher, Hash}, - marker::PhantomData, - num::NonZeroUsize, -}; - -/// Factory for creating cache storage. -pub trait CacheFactory: Send + Sync + 'static { - /// Create a cache storage. - /// - /// TODO: When GAT is stable, this memory allocation can be optimized away. - fn create(&self) -> Box> - where - K: Send + Sync + Clone + Eq + Hash + 'static, - V: Send + Sync + Clone + 'static; -} - -/// Cache storage for [DataLoader](crate::dataloader::DataLoader). -pub trait CacheStorage: Send + Sync + 'static { - /// The key type of the record. - type Key: Send + Sync + Clone + Eq + Hash + 'static; - - /// The value type of the record. - type Value: Send + Sync + Clone + 'static; - - /// Returns a reference to the value of the key in the cache or None if it - /// is not present in the cache. - fn get(&mut self, key: &Self::Key) -> Option<&Self::Value>; - - /// Puts a key-value pair into the cache. If the key already exists in the - /// cache, then it updates the key's value. - fn insert(&mut self, key: Cow<'_, Self::Key>, val: Cow<'_, Self::Value>); - - /// Removes the value corresponding to the key from the cache. - fn remove(&mut self, key: &Self::Key); - - /// Clears the cache, removing all key-value pairs. - fn clear(&mut self); - - /// Returns an iterator over the key-value pairs in the cache. - fn iter(&self) -> Box + '_>; -} - -/// No cache. -pub struct NoCache; - -impl CacheFactory for NoCache { - fn create(&self) -> Box> - where - K: Send + Sync + Clone + Eq + Hash + 'static, - V: Send + Sync + Clone + 'static, - { - Box::new(NoCacheImpl { - _mark1: PhantomData, - _mark2: PhantomData, - }) - } -} - -struct NoCacheImpl { - _mark1: PhantomData, - _mark2: PhantomData, -} - -impl CacheStorage for NoCacheImpl -where - K: Send + Sync + Clone + Eq + Hash + 'static, - V: Send + Sync + Clone + 'static, -{ - type Key = K; - type Value = V; - - #[inline] - fn get(&mut self, _key: &K) -> Option<&V> { - None - } - - #[inline] - fn insert(&mut self, _key: Cow<'_, Self::Key>, _val: Cow<'_, Self::Value>) {} - - #[inline] - fn remove(&mut self, _key: &K) {} - - #[inline] - fn clear(&mut self) {} - - fn iter(&self) -> Box + '_> { - Box::new(std::iter::empty()) - } -} - -/// [std::collections::HashMap] cache. -pub struct HashMapCache { - _mark: PhantomData, -} - -impl HashMapCache { - /// Use specified `S: BuildHasher` to create a `HashMap` cache. - pub fn new() -> Self { - Self { _mark: PhantomData } - } -} - -impl Default for HashMapCache { - fn default() -> Self { - Self { _mark: PhantomData } - } -} - -impl CacheFactory for HashMapCache { - fn create(&self) -> Box> - where - K: Send + Sync + Clone + Eq + Hash + 'static, - V: Send + Sync + Clone + 'static, - { - Box::new(HashMapCacheImpl::(HashMap::::default())) - } -} - -struct HashMapCacheImpl(HashMap); - -impl CacheStorage for HashMapCacheImpl -where - K: Send + Sync + Clone + Eq + Hash + 'static, - V: Send + Sync + Clone + 'static, - S: Send + Sync + BuildHasher + 'static, -{ - type Key = K; - type Value = V; - - #[inline] - fn get(&mut self, key: &Self::Key) -> Option<&Self::Value> { - self.0.get(key) - } - - #[inline] - fn insert(&mut self, key: Cow<'_, Self::Key>, val: Cow<'_, Self::Value>) { - self.0.insert(key.into_owned(), val.into_owned()); - } - - #[inline] - fn remove(&mut self, key: &Self::Key) { - self.0.remove(key); - } - - #[inline] - fn clear(&mut self) { - self.0.clear(); - } - - fn iter(&self) -> Box + '_> { - Box::new(self.0.iter()) - } -} - -/// LRU cache. -pub struct LruCache { - cap: usize, -} - -impl LruCache { - /// Creates a new LRU Cache that holds at most `cap` items. - pub fn new(cap: usize) -> Self { - Self { cap } - } -} - -impl CacheFactory for LruCache { - fn create(&self) -> Box> - where - K: Send + Sync + Clone + Eq + Hash + 'static, - V: Send + Sync + Clone + 'static, - { - Box::new(LruCacheImpl(lru::LruCache::new( - NonZeroUsize::new(self.cap).unwrap(), - ))) - } -} - -struct LruCacheImpl(lru::LruCache); - -impl CacheStorage for LruCacheImpl -where - K: Send + Sync + Clone + Eq + Hash + 'static, - V: Send + Sync + Clone + 'static, -{ - type Key = K; - type Value = V; - - #[inline] - fn get(&mut self, key: &Self::Key) -> Option<&Self::Value> { - self.0.get(key) - } - - #[inline] - fn insert(&mut self, key: Cow<'_, Self::Key>, val: Cow<'_, Self::Value>) { - self.0.put(key.into_owned(), val.into_owned()); - } - - #[inline] - fn remove(&mut self, key: &Self::Key) { - self.0.pop(key); - } - - #[inline] - fn clear(&mut self) { - self.0.clear(); - } - - fn iter(&self) -> Box + '_> { - Box::new(self.0.iter()) - } -} diff --git a/src/dataloader/mod.rs b/src/dataloader/mod.rs deleted file mode 100644 index 7099d2946..000000000 --- a/src/dataloader/mod.rs +++ /dev/null @@ -1,720 +0,0 @@ -//! Batch loading support, used to solve N+1 problem. -//! -//! # Examples -//! -//! ```rust -//! use async_graphql::*; -//! use async_graphql::dataloader::*; -//! use std::collections::{HashSet, HashMap}; -//! use std::convert::Infallible; -//! use async_graphql::dataloader::Loader; -//! -//! /// This loader simply converts the integer key into a string value. -//! struct MyLoader; -//! -//! #[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -//! impl Loader for MyLoader { -//! type Value = String; -//! type Error = Infallible; -//! -//! async fn load(&self, keys: &[i32]) -> Result, Self::Error> { -//! // Use `MyLoader` to load data. -//! Ok(keys.iter().copied().map(|n| (n, n.to_string())).collect()) -//! } -//! } -//! -//! struct Query; -//! -//! #[Object] -//! impl Query { -//! async fn value(&self, ctx: &Context<'_>, n: i32) -> Option { -//! ctx.data_unchecked::>().load_one(n).await.unwrap() -//! } -//! } -//! -//! # tokio::runtime::Runtime::new().unwrap().block_on(async move { -//! let schema = Schema::new(Query, EmptyMutation, EmptySubscription); -//! let query = r#" -//! { -//! v1: value(n: 1) -//! v2: value(n: 2) -//! v3: value(n: 3) -//! v4: value(n: 4) -//! v5: value(n: 5) -//! } -//! "#; -//! let request = Request::new(query).data(DataLoader::new(MyLoader, tokio::spawn)); -//! let res = schema.execute(request).await.into_result().unwrap().data; -//! -//! assert_eq!(res, value!({ -//! "v1": "1", -//! "v2": "2", -//! "v3": "3", -//! "v4": "4", -//! "v5": "5", -//! })); -//! # }); -//! ``` - -mod cache; - -#[cfg(not(feature = "boxed-trait"))] -use std::future::Future; -use std::{ - any::{Any, TypeId}, - borrow::Cow, - collections::{HashMap, HashSet}, - hash::Hash, - sync::{ - Arc, Mutex, - atomic::{AtomicBool, Ordering}, - }, - time::Duration, -}; - -pub use cache::{CacheFactory, CacheStorage, HashMapCache, LruCache, NoCache}; -use fnv::FnvHashMap; -use futures_channel::oneshot; -use futures_timer::Delay; -use futures_util::future::BoxFuture; -#[cfg(feature = "tracing")] -use tracing::{Instrument, info_span, instrument}; -#[cfg(feature = "tracing")] -use tracinglib as tracing; - -#[allow(clippy::type_complexity)] -struct ResSender> { - use_cache_values: HashMap, - tx: oneshot::Sender, T::Error>>, -} - -struct Requests> { - keys: HashSet, - pending: Vec<(HashSet, ResSender)>, - cache_storage: Box>, - disable_cache: bool, -} - -type KeysAndSender = (HashSet, Vec<(HashSet, ResSender)>); - -impl> Requests { - fn new(cache_factory: &C) -> Self { - Self { - keys: Default::default(), - pending: Vec::new(), - cache_storage: cache_factory.create::(), - disable_cache: false, - } - } - - fn take(&mut self) -> KeysAndSender { - ( - std::mem::take(&mut self.keys), - std::mem::take(&mut self.pending), - ) - } -} - -/// Trait for batch loading. -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -pub trait Loader: Send + Sync + 'static { - /// type of value. - type Value: Send + Sync + Clone + 'static; - - /// Type of error. - type Error: Send + Clone + 'static; - - /// Load the data set specified by the `keys`. - #[cfg(feature = "boxed-trait")] - async fn load(&self, keys: &[K]) -> Result, Self::Error>; - - /// Load the data set specified by the `keys`. - #[cfg(not(feature = "boxed-trait"))] - fn load( - &self, - keys: &[K], - ) -> impl Future, Self::Error>> + Send; -} - -struct DataLoaderInner { - requests: Mutex>>, - loader: T, -} - -impl DataLoaderInner { - #[cfg_attr(feature = "tracing", instrument(skip_all))] - async fn do_load(&self, disable_cache: bool, (keys, senders): KeysAndSender) - where - K: Send + Sync + Hash + Eq + Clone + 'static, - T: Loader, - { - let tid = TypeId::of::(); - let keys = keys.into_iter().collect::>(); - - match self.loader.load(&keys).await { - Ok(values) => { - // update cache - let mut request = self.requests.lock().unwrap(); - let typed_requests = request - .get_mut(&tid) - .unwrap() - .downcast_mut::>() - .unwrap(); - let disable_cache = typed_requests.disable_cache || disable_cache; - if !disable_cache { - for (key, value) in &values { - typed_requests - .cache_storage - .insert(Cow::Borrowed(key), Cow::Borrowed(value)); - } - } - - // send response - for (keys, sender) in senders { - let mut res = HashMap::new(); - res.extend(sender.use_cache_values); - for key in &keys { - res.extend(values.get(key).map(|value| (key.clone(), value.clone()))); - } - sender.tx.send(Ok(res)).ok(); - } - } - Err(err) => { - for (_, sender) in senders { - sender.tx.send(Err(err.clone())).ok(); - } - } - } - } -} - -/// Data loader. -/// -/// Reference: -pub struct DataLoader { - inner: Arc>, - cache_factory: C, - delay: Duration, - max_batch_size: usize, - disable_cache: AtomicBool, - spawner: Box) + Send + Sync>, -} - -impl DataLoader { - /// Use `Loader` to create a [DataLoader] that does not cache records. - pub fn new(loader: T, spawner: S) -> Self - where - S: Fn(BoxFuture<'static, ()>) -> R + Send + Sync + 'static, - { - Self { - inner: Arc::new(DataLoaderInner { - requests: Mutex::new(Default::default()), - loader, - }), - cache_factory: NoCache, - delay: Duration::from_millis(1), - max_batch_size: 1000, - disable_cache: false.into(), - spawner: Box::new(move |fut| { - spawner(fut); - }), - } - } -} - -impl DataLoader { - /// Use `Loader` to create a [DataLoader] with a cache factory. - pub fn with_cache(loader: T, spawner: S, cache_factory: C) -> Self - where - S: Fn(BoxFuture<'static, ()>) -> R + Send + Sync + 'static, - { - Self { - inner: Arc::new(DataLoaderInner { - requests: Mutex::new(Default::default()), - loader, - }), - cache_factory, - delay: Duration::from_millis(1), - max_batch_size: 1000, - disable_cache: false.into(), - spawner: Box::new(move |fut| { - spawner(fut); - }), - } - } - - /// Specify the delay time for loading data, the default is `1ms`. - #[must_use] - pub fn delay(self, delay: Duration) -> Self { - Self { delay, ..self } - } - - /// pub fn Specify the max batch size for loading data, the default is - /// `1000`. - /// - /// If the keys waiting to be loaded reach the threshold, they are loaded - /// immediately. - #[must_use] - pub fn max_batch_size(self, max_batch_size: usize) -> Self { - Self { - max_batch_size, - ..self - } - } - - /// Get the loader. - #[inline] - pub fn loader(&self) -> &T { - &self.inner.loader - } - - /// Enable/Disable cache of all loaders. - pub fn enable_all_cache(&self, enable: bool) { - self.disable_cache.store(!enable, Ordering::SeqCst); - } - - /// Enable/Disable cache of specified loader. - pub fn enable_cache(&self, enable: bool) - where - K: Send + Sync + Hash + Eq + Clone + 'static, - T: Loader, - { - let tid = TypeId::of::(); - let mut requests = self.inner.requests.lock().unwrap(); - let typed_requests = requests - .get_mut(&tid) - .unwrap() - .downcast_mut::>() - .unwrap(); - typed_requests.disable_cache = !enable; - } - - /// Use this `DataLoader` load a data. - #[cfg_attr(feature = "tracing", instrument(skip_all))] - pub async fn load_one(&self, key: K) -> Result, T::Error> - where - K: Send + Sync + Hash + Eq + Clone + 'static, - T: Loader, - { - let mut values = self.load_many(std::iter::once(key.clone())).await?; - Ok(values.remove(&key)) - } - - /// Use this `DataLoader` to load some data. - #[cfg_attr(feature = "tracing", instrument(skip_all))] - pub async fn load_many(&self, keys: I) -> Result, T::Error> - where - K: Send + Sync + Hash + Eq + Clone + 'static, - I: IntoIterator, - T: Loader, - { - enum Action> { - ImmediateLoad(KeysAndSender), - StartFetch, - Delay, - } - - let tid = TypeId::of::(); - - let (action, rx) = { - let mut requests = self.inner.requests.lock().unwrap(); - let typed_requests = requests - .entry(tid) - .or_insert_with(|| Box::new(Requests::::new(&self.cache_factory))) - .downcast_mut::>() - .unwrap(); - let prev_count = typed_requests.keys.len(); - let mut keys_set = HashSet::new(); - let mut use_cache_values = HashMap::new(); - - if typed_requests.disable_cache || self.disable_cache.load(Ordering::SeqCst) { - keys_set = keys.into_iter().collect(); - } else { - for key in keys { - if let Some(value) = typed_requests.cache_storage.get(&key) { - // Already in cache - use_cache_values.insert(key.clone(), value.clone()); - } else { - keys_set.insert(key); - } - } - } - - if !use_cache_values.is_empty() && keys_set.is_empty() { - return Ok(use_cache_values); - } else if use_cache_values.is_empty() && keys_set.is_empty() { - return Ok(Default::default()); - } - - typed_requests.keys.extend(keys_set.clone()); - let (tx, rx) = oneshot::channel(); - typed_requests.pending.push(( - keys_set, - ResSender { - use_cache_values, - tx, - }, - )); - - if typed_requests.keys.len() >= self.max_batch_size { - (Action::ImmediateLoad(typed_requests.take()), rx) - } else { - ( - if !typed_requests.keys.is_empty() && prev_count == 0 { - Action::StartFetch - } else { - Action::Delay - }, - rx, - ) - } - }; - - match action { - Action::ImmediateLoad(keys) => { - let inner = self.inner.clone(); - let disable_cache = self.disable_cache.load(Ordering::SeqCst); - let task = async move { inner.do_load(disable_cache, keys).await }; - #[cfg(feature = "tracing")] - let task = task - .instrument(info_span!("immediate_load")) - .in_current_span(); - - (self.spawner)(Box::pin(task)); - } - Action::StartFetch => { - let inner = self.inner.clone(); - let disable_cache = self.disable_cache.load(Ordering::SeqCst); - let delay = self.delay; - - let task = async move { - Delay::new(delay).await; - - let keys = { - let mut request = inner.requests.lock().unwrap(); - let typed_requests = request - .get_mut(&tid) - .unwrap() - .downcast_mut::>() - .unwrap(); - typed_requests.take() - }; - - if !keys.0.is_empty() { - inner.do_load(disable_cache, keys).await - } - }; - #[cfg(feature = "tracing")] - let task = task.instrument(info_span!("start_fetch")).in_current_span(); - (self.spawner)(Box::pin(task)) - } - Action::Delay => {} - } - - rx.await.unwrap() - } - - /// Feed some data into the cache. - /// - /// **NOTE: If the cache type is [NoCache], this function will not take - /// effect. ** - #[cfg_attr(feature = "tracing", instrument(skip_all))] - pub async fn feed_many(&self, values: I) - where - K: Send + Sync + Hash + Eq + Clone + 'static, - I: IntoIterator, - T: Loader, - { - let tid = TypeId::of::(); - let mut requests = self.inner.requests.lock().unwrap(); - let typed_requests = requests - .entry(tid) - .or_insert_with(|| Box::new(Requests::::new(&self.cache_factory))) - .downcast_mut::>() - .unwrap(); - for (key, value) in values { - typed_requests - .cache_storage - .insert(Cow::Owned(key), Cow::Owned(value)); - } - } - - /// Feed some data into the cache. - /// - /// **NOTE: If the cache type is [NoCache], this function will not take - /// effect. ** - #[cfg_attr(feature = "tracing", instrument(skip_all))] - pub async fn feed_one(&self, key: K, value: T::Value) - where - K: Send + Sync + Hash + Eq + Clone + 'static, - T: Loader, - { - self.feed_many(std::iter::once((key, value))).await; - } - - /// Clears the cache. - /// - /// **NOTE: If the cache type is [NoCache], this function will not take - /// effect. ** - #[cfg_attr(feature = "tracing", instrument(skip_all))] - pub fn clear(&self) - where - K: Send + Sync + Hash + Eq + Clone + 'static, - T: Loader, - { - let tid = TypeId::of::(); - let mut requests = self.inner.requests.lock().unwrap(); - let typed_requests = requests - .entry(tid) - .or_insert_with(|| Box::new(Requests::::new(&self.cache_factory))) - .downcast_mut::>() - .unwrap(); - typed_requests.cache_storage.clear(); - } - - /// Gets all values in the cache. - pub fn get_cached_values(&self) -> HashMap - where - K: Send + Sync + Hash + Eq + Clone + 'static, - T: Loader, - { - let tid = TypeId::of::(); - let requests = self.inner.requests.lock().unwrap(); - match requests.get(&tid) { - None => HashMap::new(), - Some(requests) => { - let typed_requests = requests.downcast_ref::>().unwrap(); - typed_requests - .cache_storage - .iter() - .map(|(k, v)| (k.clone(), v.clone())) - .collect() - } - } - } -} - -#[cfg(test)] -mod tests { - use fnv::FnvBuildHasher; - - use super::*; - - struct MyLoader; - - #[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] - impl Loader for MyLoader { - type Value = i32; - type Error = (); - - async fn load(&self, keys: &[i32]) -> Result, Self::Error> { - assert!(keys.len() <= 10); - Ok(keys.iter().copied().map(|k| (k, k)).collect()) - } - } - - #[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] - impl Loader for MyLoader { - type Value = i64; - type Error = (); - - async fn load(&self, keys: &[i64]) -> Result, Self::Error> { - assert!(keys.len() <= 10); - Ok(keys.iter().copied().map(|k| (k, k)).collect()) - } - } - - #[tokio::test] - async fn test_dataloader() { - let loader = Arc::new(DataLoader::new(MyLoader, tokio::spawn).max_batch_size(10)); - assert_eq!( - futures_util::future::try_join_all((0..100i32).map({ - let loader = loader.clone(); - move |n| { - let loader = loader.clone(); - async move { loader.load_one(n).await } - } - })) - .await - .unwrap(), - (0..100).map(Option::Some).collect::>() - ); - - assert_eq!( - futures_util::future::try_join_all((0..100i64).map({ - let loader = loader.clone(); - move |n| { - let loader = loader.clone(); - async move { loader.load_one(n).await } - } - })) - .await - .unwrap(), - (0..100).map(Option::Some).collect::>() - ); - } - - #[tokio::test] - async fn test_duplicate_keys() { - let loader = Arc::new(DataLoader::new(MyLoader, tokio::spawn).max_batch_size(10)); - assert_eq!( - futures_util::future::try_join_all([1, 3, 5, 1, 7, 8, 3, 7].iter().copied().map({ - let loader = loader.clone(); - move |n| { - let loader = loader.clone(); - async move { loader.load_one(n).await } - } - })) - .await - .unwrap(), - [1, 3, 5, 1, 7, 8, 3, 7] - .iter() - .copied() - .map(Option::Some) - .collect::>() - ); - } - - #[tokio::test] - async fn test_dataloader_load_empty() { - let loader = DataLoader::new(MyLoader, tokio::spawn); - assert!(loader.load_many::(vec![]).await.unwrap().is_empty()); - } - - #[tokio::test] - async fn test_dataloader_with_cache() { - let loader = DataLoader::with_cache(MyLoader, tokio::spawn, HashMapCache::default()); - loader.feed_many(vec![(1, 10), (2, 20), (3, 30)]).await; - - // All from the cache - assert_eq!( - loader.load_many(vec![1, 2, 3]).await.unwrap(), - vec![(1, 10), (2, 20), (3, 30)].into_iter().collect() - ); - - // Part from the cache - assert_eq!( - loader.load_many(vec![1, 5, 6]).await.unwrap(), - vec![(1, 10), (5, 5), (6, 6)].into_iter().collect() - ); - - // All from the loader - assert_eq!( - loader.load_many(vec![8, 9, 10]).await.unwrap(), - vec![(8, 8), (9, 9), (10, 10)].into_iter().collect() - ); - - // Clear cache - loader.clear::(); - assert_eq!( - loader.load_many(vec![1, 2, 3]).await.unwrap(), - vec![(1, 1), (2, 2), (3, 3)].into_iter().collect() - ); - } - - #[tokio::test] - async fn test_dataloader_with_cache_hashmap_fnv() { - let loader = DataLoader::with_cache( - MyLoader, - tokio::spawn, - HashMapCache::::new(), - ); - loader.feed_many(vec![(1, 10), (2, 20), (3, 30)]).await; - - // All from the cache - assert_eq!( - loader.load_many(vec![1, 2, 3]).await.unwrap(), - vec![(1, 10), (2, 20), (3, 30)].into_iter().collect() - ); - - // Part from the cache - assert_eq!( - loader.load_many(vec![1, 5, 6]).await.unwrap(), - vec![(1, 10), (5, 5), (6, 6)].into_iter().collect() - ); - - // All from the loader - assert_eq!( - loader.load_many(vec![8, 9, 10]).await.unwrap(), - vec![(8, 8), (9, 9), (10, 10)].into_iter().collect() - ); - - // Clear cache - loader.clear::(); - assert_eq!( - loader.load_many(vec![1, 2, 3]).await.unwrap(), - vec![(1, 1), (2, 2), (3, 3)].into_iter().collect() - ); - } - - #[tokio::test] - async fn test_dataloader_disable_all_cache() { - let loader = DataLoader::with_cache(MyLoader, tokio::spawn, HashMapCache::default()); - loader.feed_many(vec![(1, 10), (2, 20), (3, 30)]).await; - - // All from the loader - loader.enable_all_cache(false); - assert_eq!( - loader.load_many(vec![1, 2, 3]).await.unwrap(), - vec![(1, 1), (2, 2), (3, 3)].into_iter().collect() - ); - - // All from the cache - loader.enable_all_cache(true); - assert_eq!( - loader.load_many(vec![1, 2, 3]).await.unwrap(), - vec![(1, 10), (2, 20), (3, 30)].into_iter().collect() - ); - } - - #[tokio::test] - async fn test_dataloader_disable_cache() { - let loader = DataLoader::with_cache(MyLoader, tokio::spawn, HashMapCache::default()); - loader.feed_many(vec![(1, 10), (2, 20), (3, 30)]).await; - - // All from the loader - loader.enable_cache::(false); - assert_eq!( - loader.load_many(vec![1, 2, 3]).await.unwrap(), - vec![(1, 1), (2, 2), (3, 3)].into_iter().collect() - ); - - // All from the cache - loader.enable_cache::(true); - assert_eq!( - loader.load_many(vec![1, 2, 3]).await.unwrap(), - vec![(1, 10), (2, 20), (3, 30)].into_iter().collect() - ); - } - - #[tokio::test] - async fn test_dataloader_dead_lock() { - struct MyDelayLoader; - - #[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] - impl Loader for MyDelayLoader { - type Value = i32; - type Error = (); - - async fn load(&self, keys: &[i32]) -> Result, Self::Error> { - tokio::time::sleep(Duration::from_secs(1)).await; - Ok(keys.iter().copied().map(|k| (k, k)).collect()) - } - } - - let loader = Arc::new( - DataLoader::with_cache(MyDelayLoader, tokio::spawn, NoCache) - .delay(Duration::from_secs(1)), - ); - let handle = tokio::spawn({ - let loader = loader.clone(); - async move { - loader.load_many(vec![1, 2, 3]).await.unwrap(); - } - }); - - tokio::time::sleep(Duration::from_millis(500)).await; - handle.abort(); - loader.load_many(vec![4, 5, 6]).await.unwrap(); - } -} diff --git a/src/docs/complex_object.md b/src/docs/complex_object.md deleted file mode 100644 index bc4296b66..000000000 --- a/src/docs/complex_object.md +++ /dev/null @@ -1,105 +0,0 @@ -Define a complex GraphQL object for SimpleObject's complex field resolver. - -*[See also the Book](https://async-graphql.github.io/async-graphql/en/define_simple_object.html).* - -Sometimes most of the fields of a GraphQL object simply return the value of the structure member, but a few -fields are calculated. Usually we use the `Object` macro to define such a GraphQL object. - -But this can be done more beautifully with the `ComplexObject` macro. We can use the `SimpleObject` macro to define -some simple fields, and use the `ComplexObject` macro to define some other fields that need to be calculated. - -# Macro attributes - -| Attribute | description | Type | Optional | -|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------| -| rename_fields | Rename all the fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y | -| rename_args | Rename all the arguments according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y | -| guard | Field of guard *[See also the Book](https://async-graphql.github.io/async-graphql/en/field_guard.html)* | string | Y | -| inaccessible | Indicate that an object is not accessible from a supergraph when using Apollo Federation | bool | Y | -| tag | Arbitrary string metadata that will be propagated to the supergraph when using Apollo Federation. This attribute is repeatable | string | Y | - -# Field attributes - -| Attribute | description | Type | Optional | -|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|----------| -| skip | Skip this field | bool | Y | -| name | Field name | string | Y | -| desc | Field description | string | Y | -| deprecation | Field deprecated | bool | Y | -| deprecation | Field deprecation reason | string | Y | -| cache_control | Field cache control | [`CacheControl`](struct.CacheControl.html) | Y | -| external | Mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. | bool | Y | -| provides | Annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. | string | Y | -| requires | Annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. | string | Y | -| shareable | Indicate that a field is allowed to be resolved by multiple subgraphs | bool | Y | -| inaccessible | Indicate that a field is not accessible from a supergraph when using Apollo Federation | bool | Y | -| tag | Arbitrary string metadata that will be propagated to the supergraph when using Apollo Federation. This attribute is repeatable | string | Y | -| override_from | Mark the field as overriding a field currently present on another subgraph. It is used to migrate fields between subgraphs. | string | Y | -| guard | Field of guard *[See also the Book](https://async-graphql.github.io/async-graphql/en/field_guard.html)* | string | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| complexity | Custom field complexity. *[See also the Book](https://async-graphql.github.io/async-graphql/en/depth_and_complexity.html).* | bool | Y | -| complexity | Custom field complexity. | string | Y | -| derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y | -| flatten | Similar to serde (flatten) | boolean | Y | -| directives | Directives | expr | Y | - -# Field argument attributes - -| Attribute | description | Type | Optional | -|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------|-------------|----------| -| name | Argument name | string | Y | -| desc | Argument description | string | Y | -| deprecation | Argument deprecation | bool | Y | -| deprecation | Argument deprecation reason | string | Y | -| default | Use `Default::default` for default value | none | Y | -| default | Argument default value | literal | Y | -| default_with | Expression to generate default value | code string | Y | -| validator | Input value validator *[See also the Book](https://async-graphql.github.io/async-graphql/en/input_value_validators.html)* | object | Y | -| inaccessible | Indicate that a field argument is not accessible from a supergraph when using Apollo Federation | bool | Y | -| tag | Arbitrary string metadata that will be propagated to the supergraph when using Apollo Federation. This attribute is repeatable | string | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y | -| process_with | Upon successful parsing, invokes specified function. Its signature must be `fn(&mut T)`. | code path | Y | - -# Examples - -```rust -use async_graphql::*; - -#[derive(SimpleObject)] -#[graphql(complex)] // NOTE: If you want the `ComplexObject` macro to take effect, this `complex` attribute is required. -struct MyObj { - a: i32, - b: i32, -} - -#[ComplexObject] -impl MyObj { - async fn c(&self) -> i32 { - self.a + self.b - } -} - -struct Query; - -#[Object] -impl Query { - async fn obj(&self) -> MyObj { - MyObj { a: 10, b: 20 } - } -} - -# tokio::runtime::Runtime::new().unwrap().block_on(async move { -let schema = Schema::new(Query, EmptyMutation, EmptySubscription); -let res = schema.execute("{ obj { a b c } }").await.into_result().unwrap().data; -assert_eq!(res, value!({ - "obj": { - "a": 10, - "b": 20, - "c": 30, - }, -})); -# }); -``` diff --git a/src/docs/description.md b/src/docs/description.md deleted file mode 100644 index ad4fa0903..000000000 --- a/src/docs/description.md +++ /dev/null @@ -1,40 +0,0 @@ -Attach a description to `Object`, `Scalar` or `Subscription`. - -The three types above use the rustdoc on the implementation block as -the GraphQL type description, but if you want to use the rustdoc on the -type declaration as the GraphQL type description, you can use that derived macro. - -# Examples - -```rust -use async_graphql::*; - -/// This is MyObj -#[derive(Description, Default)] -struct MyObj; - -#[Object(use_type_description)] -impl MyObj { - async fn value(&self) -> i32 { - 100 - } -} - -#[derive(SimpleObject, Default)] -struct Query { - obj: MyObj, -} - -# tokio::runtime::Runtime::new().unwrap().block_on(async move { -let schema = Schema::new(Query::default(), EmptyMutation, EmptySubscription); -assert_eq!( - schema - .execute(r#"{ __type(name: "MyObj") { description } }"#) - .await - .data, - value!({ - "__type": { "description": "This is MyObj" } - }) -); -# }); -``` diff --git a/src/docs/directive.md b/src/docs/directive.md deleted file mode 100644 index 4a0258394..000000000 --- a/src/docs/directive.md +++ /dev/null @@ -1,77 +0,0 @@ -Define a directive for query. - -*[See also the Book](https://async-graphql.github.io/async-graphql/en/custom_directive.html).* - -# Macro attributes - -| Attribute | description | Type | Optional | -|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------| -| name | Object name | string | Y | -| name_type | If `true`, the directive name will be specified from [`async_graphql::TypeName`](https://docs.rs/async-graphql/latest/async_graphql/trait.TypeName.html) trait | bool | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| repeatable | It means that the directive can be used multiple times in the same location. | bool | Y | -| rename_args | Rename all the arguments according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y | -| locations | Specify the location where the directive is available, multiples are allowed. The possible values is "field", ... | string | N | - -# Directive arguments - -| Attribute | description | Type | Optional | -|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------|-------------|----------| -| name | Argument name | string | Y | -| desc | Argument description | string | Y | -| deprecation | Argument deprecation | bool | Y | -| deprecation | Argument deprecation reason | string | Y | -| default | Use `Default::default` for default value | none | Y | -| default | Argument default value | literal | Y | -| default_with | Expression to generate default value | code string | Y | -| validator | Input value validator *[See also the Book](https://async-graphql.github.io/async-graphql/en/input_value_validators.html)* | object | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y | - -# Examples - -```rust -use async_graphql::*; - -struct ConcatDirective { - value: String, -} - -#[async_trait::async_trait] -impl CustomDirective for ConcatDirective { - async fn resolve_field(&self, _ctx: &Context<'_>, resolve: ResolveFut<'_>) -> ServerResult> { - resolve.await.map(|value| { - value.map(|value| match value { - Value::String(str) => Value::String(str + &self.value), - _ => value, - }) - }) - } -} - -#[Directive(location = "Field")] -fn concat(value: String) -> impl CustomDirective { - ConcatDirective { value } -} - -struct Query; - -#[Object] -impl Query { - async fn value(&self) -> &'static str { - "abc" - } -} - -# tokio::runtime::Runtime::new().unwrap().block_on(async move { -let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .directive(concat) - .finish(); -let res = schema.execute(r#"{ value @concat(value: "def") }"#).await.into_result().unwrap().data; -assert_eq!(res, value!({ - "value": "abcdef", -})); -# }); -``` diff --git a/src/docs/enum.md b/src/docs/enum.md deleted file mode 100644 index 54773e00c..000000000 --- a/src/docs/enum.md +++ /dev/null @@ -1,67 +0,0 @@ -Define a GraphQL enum - -*[See also the Book](https://async-graphql.github.io/async-graphql/en/define_enum.html).* - -# Macro attributes - -| Attribute | description | Type | Optional | -|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------| -| name | Enum name | string | Y | -| name_type | If `true`, the enum name will be specified from [`async_graphql::TypeName`](https://docs.rs/async-graphql/latest/async_graphql/trait.TypeName.html) trait | bool | Y | -| display | Implements `std::fmt::Display` for the enum type | bool | Y | -| rename_items | Rename all the fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y | -| remote | Derive a remote enum | string | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| inaccessible | Indicate that an enum is not accessible from a supergraph when using Apollo Federation | bool | Y | -| tag | Arbitrary string metadata that will be propagated to the supergraph when using Apollo Federation. This attribute is repeatable | string | Y | -| directives | Directives | expr | Y | - -# Item attributes - -| Attribute | description | Type | Optional | -|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------| -| name | Item name | string | Y | -| deprecation | Item deprecated | bool | Y | -| deprecation | Item deprecation reason | string | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| inaccessible | Indicate that an item is not accessible from a supergraph when using Apollo Federation | bool | Y | -| tag | Arbitrary string metadata that will be propagated to the supergraph when using Apollo Federation. This attribute is repeatable | string | Y | -| directives | Directives | expr | Y | - -# Examples - -```rust -use async_graphql::*; - -#[derive(Enum, Copy, Clone, Eq, PartialEq)] -enum MyEnum { - A, - #[graphql(name = "b")] B, -} - -struct Query { - value1: MyEnum, - value2: MyEnum, -} - -#[Object] -impl Query { - /// value1 - async fn value1(&self) -> MyEnum { - self.value1 - } - - /// value2 - async fn value2(&self) -> MyEnum { - self.value2 - } -} - -# tokio::runtime::Runtime::new().unwrap().block_on(async move { -let schema = Schema::new(Query{ value1: MyEnum::A, value2: MyEnum::B }, EmptyMutation, EmptySubscription); -let res = schema.execute("{ value1 value2 }").await.into_result().unwrap().data; -assert_eq!(res, value!({ "value1": "A", "value2": "b" })); -# }); -``` diff --git a/src/docs/input_object.md b/src/docs/input_object.md deleted file mode 100644 index 53f2aab2f..000000000 --- a/src/docs/input_object.md +++ /dev/null @@ -1,72 +0,0 @@ -Define a GraphQL input object - -*[See also the Book](https://async-graphql.github.io/async-graphql/en/define_input_object.html).* - -# Macro attributes - -| Attribute | description | Type | Optional | -|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|----------| -| name | Object name | string | Y | -| name_type | If `true`, the object name will be specified from [`async_graphql::TypeName`](https://docs.rs/async-graphql/latest/async_graphql/trait.TypeName.html) trait | bool | Y | -| rename_fields | Rename all the fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| concretes | Specify how the concrete type of the generic SimpleObject should be implemented. | ConcreteType | Y | -| inaccessible | Indicate that an input object is not accessible from a supergraph when using Apollo Federation | bool | Y | -| tag | Arbitrary string metadata that will be propagated to the supergraph when using Apollo Federation. This attribute is repeatable | string | Y | -| directives | Directives | expr | Y | - -# Field attributes - -| Attribute | description | Type | Optional | -|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------|-------------|----------| -| name | Field name | string | Y | -| deprecation | Field deprecation | bool | Y | -| deprecation | Field deprecation reason | string | Y | -| default | Use `Default::default` for default value | none | Y | -| default | Argument default value | literal | Y | -| default_with | Expression to generate default value | code string | Y | -| validator | Input value validator *[See also the Book](https://async-graphql.github.io/async-graphql/en/input_value_validators.html)* | object | Y | -| flatten | Similar to serde (flatten) | boolean | Y | -| skip | Skip this field, use `Default::default` to get a default value for this field. | bool | Y | -| skip_input | Skip this field, similar to `skip`, but avoids conflicts when this macro is used with `SimpleObject`. | bool | Y | -| process_with | Upon successful parsing, invokes specified function. Its signature must be `fn(&mut T)`. | code path | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y | -| inaccessible | Indicate that a field is not accessible from a supergraph when using Apollo Federation | bool | Y | -| tag | Arbitrary string metadata that will be propagated to the supergraph when using Apollo Federation. This attribute is repeatable | string | Y | -| directives | Directives | expr | Y | - -# Examples - -```rust -use async_graphql::*; - -#[derive(InputObject)] -struct MyInputObject { - a: i32, - #[graphql(default = 10)] - b: i32, -} - -struct Query; - -#[Object] -impl Query { - /// value - async fn value(&self, input: MyInputObject) -> i32 { - input.a * input.b - } -} - -# tokio::runtime::Runtime::new().unwrap().block_on(async move { -let schema = Schema::new(Query, EmptyMutation, EmptySubscription); -let res = schema.execute(r#" -{ - value1: value(input:{a:9, b:3}) - value2: value(input:{a:9}) -}"#).await.into_result().unwrap().data; -assert_eq!(res, value!({ "value1": 27, "value2": 90 })); -# }); -``` diff --git a/src/docs/interface.md b/src/docs/interface.md deleted file mode 100644 index b08ef0565..000000000 --- a/src/docs/interface.md +++ /dev/null @@ -1,154 +0,0 @@ -Define a GraphQL interface - -*[See also the Book](https://async-graphql.github.io/async-graphql/en/define_interface.html).* - -# Macro attributes - -| Attribute | description | Type | Optional | -|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------|----------| -| name | Object name | string | Y | -| name_type | If `true`, the interface name will be specified from [`async_graphql::TypeName`](https://docs.rs/async-graphql/latest/async_graphql/trait.TypeName.html) trait | bool | Y | -| rename_fields | Rename all the fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y | -| rename_args | Rename all the arguments according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y | -| field | Fields of this Interface | InterfaceField | N | -| extends | Add fields to an entity that's defined in another service | bool | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| inaccessible | Indicate that an interface is not accessible from a supergraph when using Apollo Federation | bool | Y | -| tag | Arbitrary string metadata that will be propagated to the supergraph when using Apollo Federation. This attribute is repeatable | string | Y | -| directives | Directives | expr | Y | - -# Field attributes - -| Attribute | description | Type | Optional | -|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------|----------| -| name | Field name | string | N | -| ty | Field type | string | N | -| method | Rust resolver method name. If specified, `name` will not be camelCased in schema definition | string | Y | -| desc | Field description | string | Y | -| deprecation | Field deprecated | bool | Y | -| deprecation | Field deprecation reason | string | Y | -| arg | Field arguments | InterfaceFieldArgument | Y | -| external | Mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. | bool | Y | -| provides | Annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. | string | Y | -| requires | Annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. | string | Y | -| override_from | Mark the field as overriding a field currently present on another subgraph. It is used to migrate fields between subgraphs. | string | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| inaccessible | Indicate that a field is not accessible from a supergraph when using Apollo Federation | bool | Y | -| tag | Arbitrary string metadata that will be propagated to the supergraph when using Apollo Federation. This attribute is repeatable | string | Y | -| directives | Directives | expr | Y | - -# Field argument attributes - -| Attribute | description | Type | Optional | -|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------|-------------|----------| -| name | Argument name | string | N | -| ty | Argument type | string | N | -| desc | Argument description | string | Y | -| deprecation | Argument deprecation | bool | Y | -| deprecation | Argument deprecation reason | string | Y | -| default | Use `Default::default` for default value | none | Y | -| default | Argument default value | literal | Y | -| default_with | Expression to generate default value | code string | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y | -| inaccessible | Indicate that an argument is not accessible from a supergraph when using Apollo Federation | bool | Y | -| tag | Arbitrary string metadata that will be propagated to the supergraph when using Apollo Federation. This attribute is repeatable | string | Y | -| directives | Directives | expr | Y | - - -# Define an interface - -Define TypeA, TypeB, TypeC... Implement the MyInterface - -```ignore -#[derive(Interface)] -enum MyInterface { - TypeA(TypeA), - TypeB(TypeB), - TypeC(TypeC), - ... -} -``` - -# Fields - -The type, name, and parameter fields of the interface must exactly match the type of the -implementation interface, but Result can be omitted. - -```rust -use async_graphql::*; - -struct TypeA { - value: i32, -} - -#[Object] -impl TypeA { - /// Returns data borrowed from the context - async fn value_a<'a>(&self, ctx: &'a Context<'_>) -> Result<&'a str> { - Ok(ctx.data::()?.as_str()) - } - - /// Returns data borrowed self - async fn value_b(&self) -> &i32 { - &self.value - } - - /// With parameters - async fn value_c(&self, a: i32, b: i32) -> i32 { - a + b - } - - /// Disabled name transformation, don't forget "method" argument in interface! - #[graphql(name = "value_d")] - async fn value_d(&self) -> i32 { - &self.value + 1 - } -} - -#[derive(Interface)] -#[graphql( - field(name = "value_a", ty = "&'ctx str"), - field(name = "value_b", ty = "&i32"), - field(name = "value_c", ty = "i32", - arg(name = "a", ty = "i32"), - arg(name = "b", ty = "i32")), - field(name = "value_d", method = "value_d", ty = "i32"), -)] -enum MyInterface { - TypeA(TypeA) -} - -struct Query; - -#[Object] -impl Query { - async fn type_a(&self) -> MyInterface { - TypeA { value: 10 }.into() - } -} - -# tokio::runtime::Runtime::new().unwrap().block_on(async move { -let schema = Schema::build(Query, EmptyMutation, EmptySubscription).data("hello".to_string()).finish(); -let res = schema.execute(r#" -{ - typeA { - valueA - valueB - valueC(a: 3, b: 2) - value_d - } -}"#).await.into_result().unwrap().data; -assert_eq!(res, value!({ - "typeA": { - "valueA": "hello", - "valueB": 10, - "valueC": 5, - "value_d": 11 - } -})); -# }); -``` diff --git a/src/docs/merged_object.md b/src/docs/merged_object.md deleted file mode 100644 index 2946b28b1..000000000 --- a/src/docs/merged_object.md +++ /dev/null @@ -1,44 +0,0 @@ -Define a merged object with multiple object types. - -*[See also the Book](https://async-graphql.github.io/async-graphql/en/merging_objects.html).* - -# Macro attributes - -| Attribute | description | Type | Optional | -|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|----------| -| name | Object name | string | Y | -| name_type | If `true`, the object name will be specified from [`async_graphql::TypeName`](https://docs.rs/async-graphql/latest/async_graphql/trait.TypeName.html) trait | bool | Y | -| cache_control | Object cache control | [`CacheControl`](struct.CacheControl.html) | Y | -| extends | Add fields to an entity that's defined in another service | bool | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| serial | Resolve each field sequentially. | bool | Y | -| inaccessible | Indicate that an object is not accessible from a supergraph when using Apollo Federation | bool | Y | -| tag | Arbitrary string metadata that will be propagated to the supergraph when using Apollo Federation. This attribute is repeatable | string | Y | -| directives | Directives | expr | Y | - -# Examples - -```rust -use async_graphql::*; - -#[derive(SimpleObject)] - struct Object1 { - a: i32, - } - -#[derive(SimpleObject)] -struct Object2 { - b: i32, -} - -#[derive(SimpleObject)] -struct Object3 { - c: i32, -} - -#[derive(MergedObject)] -struct MyObj(Object1, Object2, Object3); - -let obj = MyObj(Object1 { a: 10 }, Object2 { b: 20 }, Object3 { c: 30 }); -``` diff --git a/src/docs/merged_subscription.md b/src/docs/merged_subscription.md deleted file mode 100644 index d00df08dd..000000000 --- a/src/docs/merged_subscription.md +++ /dev/null @@ -1,43 +0,0 @@ -Define a merged subscription with multiple subscription types. - -*[See also the Book](https://async-graphql.github.io/async-graphql/en/merging_objects.html).* - -# Macro attributes - -| Attribute | description | Type | Optional | -|-----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------| -| name | Object name | string | Y | -| name_type | If `true`, the object name will be specified from [`async_graphql::TypeName`](https://docs.rs/async-graphql/latest/async_graphql/trait.TypeName.html) trait | bool | Y | -| extends | Add fields to an entity that's defined in another service | bool | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | - -# Examples - -```rust -use async_graphql::*; -use futures_util::stream::Stream; - -#[derive(Default)] -struct Subscription1; - -#[Subscription] -impl Subscription1 { - async fn events1(&self) -> impl Stream { - futures_util::stream::iter(0..10) - } -} - -#[derive(Default)] -struct Subscription2; - -#[Subscription] -impl Subscription2 { - async fn events2(&self) -> impl Stream { - futures_util::stream::iter(10..20) - } -} - -#[derive(MergedSubscription, Default)] -struct Subscription(Subscription1, Subscription2); -``` diff --git a/src/docs/newtype.md b/src/docs/newtype.md deleted file mode 100644 index 8433d09dd..000000000 --- a/src/docs/newtype.md +++ /dev/null @@ -1,133 +0,0 @@ -Define a NewType Scalar - -It also implements `From` and `Into`. - -# Macro attributes - -| Attribute | description | Type | Optional | -|----------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------| -| name | If this attribute is provided then define a new scalar, otherwise it is just a transparent proxy for the internal scalar. | string | Y | -| visible(Only valid for new scalars) | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible(Only valid for new scalars) | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| specified_by_url(Only valid for new scalars) | Provide a specification URL for this scalar type, it must link to a human-readable specification of the data format, serialization and coercion rules for this scalar. | string | Y | -| inaccessible | Indicate that an object is not accessible from a supergraph when using Apollo Federation | bool | Y | -| tag | Arbitrary string metadata that will be propagated to the supergraph when using Apollo Federation. This attribute is repeatable | string | Y | - -# Examples - -## Use the original scalar name - -```rust -use async_graphql::*; - -#[derive(NewType)] -struct Weight(f64); - -struct Query; - -#[Object] -impl Query { - async fn value(&self) -> Weight { - Weight(1.234) - } -} - -// Test conversion -let weight: Weight = 10f64.into(); -let weight_f64: f64 = weight.into(); - -# tokio::runtime::Runtime::new().unwrap().block_on(async move { -let schema = Schema::build(Query, EmptyMutation, EmptySubscription).data("hello".to_string()).finish(); - -let res = schema.execute("{ value }").await.into_result().unwrap().data; -assert_eq!(res, value!({ - "value": 1.234, -})); - -let res = schema.execute(r#" -{ - __type(name: "Query") { - fields { - name type { - kind - ofType { name } - } - } - } -}"#).await.into_result().unwrap().data; -assert_eq!(res, value!({ - "__type": { - "fields": [{ - "name": "value", - "type": { - "kind": "NON_NULL", - "ofType": { - "name": "Float" - } - } - }] - } -})); -# }); -``` - -## Define a new scalar - -```rust -use async_graphql::*; - -/// Widget NewType -#[derive(NewType)] -#[graphql(name)] // or: #[graphql(name = true)], #[graphql(name = "Weight")] -struct Weight(f64); - -struct Query; - -#[Object] -impl Query { - async fn value(&self) -> Weight { - Weight(1.234) - } -} - -# tokio::runtime::Runtime::new().unwrap().block_on(async move { -let schema = Schema::build(Query, EmptyMutation, EmptySubscription).data("hello".to_string()).finish(); - -let res = schema.execute("{ value }").await.into_result().unwrap().data; -assert_eq!(res, value!({ - "value": 1.234, -})); - -let res = schema.execute(r#" -{ - __type(name: "Query") { - fields { - name type { - kind - ofType { name } - } - } - } -}"#).await.into_result().unwrap().data; -assert_eq!(res, value!({ - "__type": { - "fields": [{ - "name": "value", - "type": { - "kind": "NON_NULL", - "ofType": { - "name": "Weight" - } - } - }] - } -})); - -assert_eq!(schema.execute(r#"{ __type(name: "Weight") { name description } }"#). - await.into_result().unwrap().data, value!({ - "__type": { - "name": "Weight", "description": "Widget NewType" - } - })); -# }); -``` diff --git a/src/docs/object.md b/src/docs/object.md deleted file mode 100644 index 9d5e82603..000000000 --- a/src/docs/object.md +++ /dev/null @@ -1,205 +0,0 @@ -Define a GraphQL object with methods - -*[See also the Book](https://async-graphql.github.io/async-graphql/en/define_complex_object.html).* - -All methods are converted to camelCase. - -# Macro attributes - -| Attribute | description | Type | Optional | -|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|----------| -| name | Object name | string | Y | -| rename_fields | Rename all the fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y | -| rename_args | Rename all the arguments according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y | -| cache_control | Object cache control | [`CacheControl`](struct.CacheControl.html) | Y | -| extends | Add fields to an entity that's defined in another service | bool | Y | -| shareable | Indicate that an object type's field is allowed to be resolved by multiple subgraphs | bool | Y | -| use_type_description | Specifies that the description of the type is on the type declaration. [`Description`]()(derive.Description.html) | bool | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| inaccessible | Indicate that an object is not accessible from a supergraph when using Apollo Federation | bool | Y | -| tag | Arbitrary string metadata that will be propagated to the supergraph when using Apollo Federation. This attribute is repeatable | string | Y | -| serial | Resolve each field sequentially. | bool | Y | -| concretes | Specify how the concrete type of the generic SimpleObject should be implemented. | ConcreteType | Y | -| guard | Field of guard *[See also the Book](https://async-graphql.github.io/async-graphql/en/field_guard.html)* | string | Y | -| directives | Directives | expr | Y | - -# Field attributes - -| Attribute | description | Type | Optional | -|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|----------| -| skip | Skip this field | bool | Y | -| name | Field name | string | Y | -| desc | Field description | string | Y | -| deprecation | Field deprecated | bool | Y | -| deprecation | Field deprecation reason | string | Y | -| cache_control | Field cache control | [`CacheControl`](struct.CacheControl.html) | Y | -| external | Mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. | bool | Y | -| provides | Annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. | string | Y | -| requires | Annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. | string | Y | -| shareable | Indicate that a field is allowed to be resolved by multiple subgraphs | bool | Y | -| inaccessible | Indicate that a field is not accessible from a supergraph when using Apollo Federation | bool | Y | -| tag | Arbitrary string metadata that will be propagated to the supergraph when using Apollo Federation. This attribute is repeatable | string | Y | -| override_from | Mark the field as overriding a field currently present on another subgraph. It is used to migrate fields between subgraphs. | string | Y | -| guard | Field of guard *[See also the Book](https://async-graphql.github.io/async-graphql/en/field_guard.html)* | string | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| complexity | Custom field complexity. *[See also the Book](https://async-graphql.github.io/async-graphql/en/depth_and_complexity.html).* | bool | Y | -| complexity | Custom field complexity. | string | Y | -| derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y | -| flatten | Similar to serde (flatten) | boolean | Y | - -# Field argument attributes - -| Attribute | description | Type | Optional | -|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------|-------------|----------| -| name | Argument name | string | Y | -| desc | Argument description | string | Y | -| default | Use `Default::default` for default value | none | Y | -| deprecation | Argument deprecated | bool | Y | -| deprecation | Argument deprecation reason | string | Y | -| default | Argument default value | literal | Y | -| default_with | Expression to generate default value | code string | Y | -| validator | Input value validator *[See also the Book](https://async-graphql.github.io/async-graphql/en/input_value_validators.html)* | object | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| inaccessible | Indicate that an argument is not accessible from a supergraph when using Apollo Federation | bool | Y | -| tag | Arbitrary string metadata that will be propagated to the supergraph when using Apollo Federation. This attribute is repeatable | string | Y | -| secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y | -| key | Is entity key(for Federation) | bool | Y | -| process_with | Upon successful parsing, invokes specified function. Its signature must be `fn(&mut T)`. | code path | Y | -| directives | Directives | expr | Y | - -# Derived argument attributes - -| Attribute | description | Type | Optional | -|-----------|------------------------------------------------|--------|----------| -| name | Generated derived field name | string | N | -| into | Type to derived an into | string | Y | -| with | Function to apply to manage advanced use cases | string | Y | - -# Valid field return types - -- Scalar values, such as `i32` and `bool`. `usize`, `isize`, `u128` and `i128` are not supported -- `Vec`, such as `Vec` -- Slices, such as `&[i32]` -- `Option`, such as `Option` -- `BTree`, `HashMap`, `HashSet`, `BTreeSet`, `LinkedList`, `VecDeque` -- GraphQL objects. -- GraphQL enums. -- References to any of the above types, such as `&i32` or `&Option`. -- `Result`, such as `Result` - -# Context - -You can define a context as an argument to a method, and the context should be the first argument to the method. - -```ignore -#[Object] -impl Query { - async fn value(&self, ctx: &Context<'_>) -> { ... } -} -``` - -# Examples - -Implements GraphQL Object for struct. - -```rust -use async_graphql::*; - -struct Query { - value: i32, -} - -#[Object] -impl Query { - /// value - async fn value(&self) -> i32 { - self.value - } - - /// reference value - async fn value_ref(&self) -> &i32 { - &self.value - } - - /// value with error - async fn value_with_error(&self) -> Result { - Ok(self.value) - } - - async fn value_with_arg(&self, #[graphql(default = 1)] a: i32) -> i32 { - a - } -} - -# tokio::runtime::Runtime::new().unwrap().block_on(async move { -let schema = Schema::new(Query { value: 10 }, EmptyMutation, EmptySubscription); -let res = schema.execute(r#"{ - value - valueRef - valueWithError - valueWithArg1: valueWithArg - valueWithArg2: valueWithArg(a: 99) -}"#).await.into_result().unwrap().data; -assert_eq!(res, value!({ - "value": 10, - "valueRef": 10, - "valueWithError": 10, - "valueWithArg1": 1, - "valueWithArg2": 99 -})); -# }); -``` - -# Examples - -Implements GraphQL Object for trait object. - -```rust -use async_graphql::*; - -trait MyTrait: Send + Sync { - fn name(&self) -> &str; -} - -#[Object] -impl dyn MyTrait + '_ { - #[graphql(name = "name")] - async fn gql_name(&self) -> &str { - self.name() - } -} - -struct MyObj(String); - -impl MyTrait for MyObj { - fn name(&self) -> &str { - &self.0 - } -} - -struct Query; - -#[Object] -impl Query { - async fn objs(&self) -> Vec> { - vec![ - Box::new(MyObj("a".to_string())), - Box::new(MyObj("b".to_string())), - ] - } -} - -# tokio::runtime::Runtime::new().unwrap().block_on(async move { -let schema = Schema::new(Query, EmptyMutation, EmptySubscription); -let res = schema.execute("{ objs { name } }").await.into_result().unwrap().data; -assert_eq!(res, value!({ - "objs": [ - { "name": "a" }, - { "name": "b" }, - ] -})); -# }); -``` diff --git a/src/docs/oneof_object.md b/src/docs/oneof_object.md deleted file mode 100644 index c4a125175..000000000 --- a/src/docs/oneof_object.md +++ /dev/null @@ -1,60 +0,0 @@ -Define a GraphQL oneof input object - -# Macro attributes - -| Attribute | description | Type | Optional | -|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|----------| -| name | Oneof input object name | string | Y | -| name_type | If `true`, the object name will be specified from [`async_graphql::TypeName`](https://docs.rs/async-graphql/latest/async_graphql/trait.TypeName.html) trait | bool | Y | -| rename_fields | Rename all the fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| concretes | Specify how the concrete type of the generic SimpleObject should be implemented. | ConcreteType | Y | -| directives | Directives | expr | Y | - -# Field attributes - -| Attribute | description | Type | Optional | -|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------| -| name | Field name | string | Y | -| validator | Input value validator *[See also the Book](https://async-graphql.github.io/async-graphql/en/input_value_validators.html)* | object | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y | -| directives | Directives | expr | Y | -| deprecation | Field deprecation | bool | Y | -| deprecation | Field deprecation reason | string | Y | - -# Examples - -```rust -use async_graphql::*; - -#[derive(OneofObject)] -enum MyInputObject { - A(i32), - B(String), -} - -struct Query; - -#[Object] -impl Query { - async fn value(&self, input: MyInputObject) -> String { - match input { - MyInputObject::A(value) => format!("a:{}", value), - MyInputObject::B(value) => format!("b:{}", value), - } - } -} - -# tokio::runtime::Runtime::new().unwrap().block_on(async move { -let schema = Schema::new(Query, EmptyMutation, EmptySubscription); -let res = schema.execute(r#" -{ - value1: value(input:{a:100}) - value2: value(input:{b:"abc"}) -}"#).await.into_result().unwrap().data; -assert_eq!(res, value!({ "value1": "a:100", "value2": "b:abc" })); -# }); -``` diff --git a/src/docs/scalar.md b/src/docs/scalar.md deleted file mode 100644 index 8457617d1..000000000 --- a/src/docs/scalar.md +++ /dev/null @@ -1,11 +0,0 @@ -Define a Scalar - -# Macro attributes - -| Attribute | description | Type | Optional | -|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------| -| name | Scalar name | string | Y | -| name_type | If `true`, the scalar name will be specified from [`async_graphql::TypeName`](https://docs.rs/async-graphql/latest/async_graphql/trait.TypeName.html) trait | bool | Y | -| specified_by_url | Provide a specification URL for this scalar type, it must link to a human-readable specification of the data format, serialization and coercion rules for this scalar. | string | Y | -| inaccessible | Indicate that a scalar is not accessible from a supergraph when using Apollo Federation | bool | Y | -| tag | Arbitrary string metadata that will be propagated to the supergraph when using Apollo Federation. This attribute is repeatable | string | Y | \ No newline at end of file diff --git a/src/docs/simple_object.md b/src/docs/simple_object.md deleted file mode 100644 index a54827a4c..000000000 --- a/src/docs/simple_object.md +++ /dev/null @@ -1,79 +0,0 @@ -Define a GraphQL object with fields - -*[See also the Book](https://async-graphql.github.io/async-graphql/en/define_simple_object.html).* - -Similar to `Object`, but defined on a structure that automatically generates getters for all fields. For a list of valid field types, see [`Object`](attr.Object.html). All fields are converted to camelCase. - -# Macro attributes - -| Attribute | description | Type | Optional | -|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|----------| -| name | Object name | string | Y | -| name_type | If `true`, the object name will be specified from [`async_graphql::TypeName`](https://docs.rs/async-graphql/latest/async_graphql/trait.TypeName.html) trait | bool | Y | -| rename_fields | Rename all the fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y | -| cache_control | Object cache control | [`CacheControl`](struct.CacheControl.html) | Y | -| extends | Add fields to an entity that's defined in another service | bool | Y | -| shareable | Indicate that an object type's field is allowed to be resolved by multiple subgraphs | bool | Y | -| inaccessible | Indicate that an object is not accessible from a supergraph when using Apollo Federation | bool | Y | -| tag | Arbitrary string metadata that will be propagated to the supergraph when using Apollo Federation. This attribute is repeatable | string | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| concretes | Specify how the concrete type of the generic SimpleObject should be implemented. *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_simple_object.html#generic-simpleobjects) | ConcreteType | Y | -| serial | Resolve each field sequentially. | bool | Y | -| guard | Field of guard *[See also the Book](https://async-graphql.github.io/async-graphql/en/field_guard.html)* | string | Y | -| directives | Directives | expr | Y | - -# Field attributes - -| Attribute | description | Type | Optional | -|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|----------| -| skip | Skip this field | bool | Y | -| skip_output | Skip this field, similar to `skip`, but avoids conflicts when this macro is used with `InputObject`. | bool | Y | -| name | Field name | string | Y | -| deprecation | Field deprecated | bool | Y | -| deprecation | Field deprecation reason | string | Y | -| derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y | -| owned | Field resolver return a ownedship value | bool | Y | -| cache_control | Field cache control | [`CacheControl`](struct.CacheControl.html) | Y | -| external | Mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. | bool | Y | -| provides | Annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. | string | Y | -| requires | Annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. | string | Y | -| shareable | Indicate that a field is allowed to be resolved by multiple subgraphs | bool | Y | -| inaccessible | Indicate that a field is not accessible from a supergraph when using Apollo Federation | bool | Y | -| tag | Arbitrary string metadata that will be propagated to the supergraph when using Apollo Federation. This attribute is repeatable | string | Y | -| override_from | Mark the field as overriding a field currently present on another subgraph. It is used to migrate fields between subgraphs. | string | Y | -| guard | Field of guard *[See also the Book](https://async-graphql.github.io/async-graphql/en/field_guard.html)* | string | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| flatten | Similar to serde (flatten) | boolean | Y | -| directives | Directives | expr | Y | -| complexity | Custom field complexity. *[See also the Book](https://async-graphql.github.io/async-graphql/en/depth_and_complexity.html).* | bool | Y | - -# Derived attributes - -| Attribute | description | Type | Optional | -|-----------|------------------------------------------------|--------|----------| -| name | Generated derived field name | string | N | -| into | Type to derived an into | string | Y | -| owned | Field resolver return a ownedship value | bool | Y | -| with | Function to apply to manage advanced use cases | string | Y | - - -# Examples - -```rust -use async_graphql::*; - -#[derive(SimpleObject)] -struct Query { - value: i32, -} - -# tokio::runtime::Runtime::new().unwrap().block_on(async move { -let schema = Schema::new(Query{ value: 10 }, EmptyMutation, EmptySubscription); -let res = schema.execute("{ value }").await.into_result().unwrap().data; -assert_eq!(res, value!({ - "value": 10, -})); -# }); -``` diff --git a/src/docs/subscription.md b/src/docs/subscription.md deleted file mode 100644 index bf0aca353..000000000 --- a/src/docs/subscription.md +++ /dev/null @@ -1,71 +0,0 @@ -Define a GraphQL subscription - -*[See also the Book](https://async-graphql.github.io/async-graphql/en/subscription.html).* - -The field function is a synchronization function that performs filtering. When true is returned, the message is pushed to the client. -The second parameter is the type of the field. -Starting with the third parameter is one or more filtering conditions, The filter condition is the parameter of the field. -The filter function should be synchronous. - -# Macro attributes - -| Attribute | description | Type | Optional | -|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------| -| name | Object name | string | Y | -| name_type | If `true`, the object name will be specified from [`async_graphql::TypeName`](https://docs.rs/async-graphql/latest/async_graphql/trait.TypeName.html) trait | bool | Y | -| rename_fields | Rename all the fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y | -| rename_args | Rename all the arguments according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y | -| extends | Add fields to an entity that's defined in another service | bool | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| use_type_description | Specifies that the description of the type is on the type declaration. [`Description`]()(derive.Description.html) | bool | Y | -| guard | Field of guard *[See also the Book](https://async-graphql.github.io/async-graphql/en/field_guard.html)* | string | Y | -| directives | Directives | expr | Y | - -# Field attributes - -| Attribute | description | Type | Optional | -|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------| -| name | Field name | string | Y | -| deprecation | Field deprecated | bool | Y | -| deprecation | Field deprecation reason | string | Y | -| guard | Field of guard *[See also the Book](https://async-graphql.github.io/async-graphql/en/field_guard.html)* | string | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| complexity | Custom field complexity. *[See also the Book](https://async-graphql.github.io/async-graphql/en/depth_and_complexity.html).* | bool | Y | -| complexity | Custom field complexity. | string | Y | -| secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y | -| directives | Directives | expr | Y | - -# Field argument attributes - -| Attribute | description | Type | Optional | -|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------|-------------|----------| -| name | Argument name | string | Y | -| desc | Argument description | string | Y | -| deprecation | Argument deprecation | bool | Y | -| deprecation | Argument deprecation reason | string | Y | -| default | Use `Default::default` for default value | none | Y | -| default | Argument default value | literal | Y | -| default_with | Expression to generate default value | code string | Y | -| validator | Input value validator *[See also the Book](https://async-graphql.github.io/async-graphql/en/input_value_validators.html)* | object | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| process_with | Upon successful parsing, invokes specified function. Its signature must be `fn(&mut T)`. | code path | Y | - -# Examples - -```rust -use async_graphql::*; -use futures_util::stream::{Stream, StreamExt}; - -struct Subscription; - -#[Subscription] -impl Subscription { - async fn value(&self, condition: i32) -> impl Stream { - // Returns the number from 0 to `condition`. - futures_util::stream::iter(0..condition) - } -} -``` diff --git a/src/docs/union.md b/src/docs/union.md deleted file mode 100644 index ccf0a7650..000000000 --- a/src/docs/union.md +++ /dev/null @@ -1,74 +0,0 @@ -Define a GraphQL union - -*[See also the Book](https://async-graphql.github.io/async-graphql/en/define_union.html).* - -# Macro attributes - -| Attribute | description | Type | Optional | -|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------| -| name | Object name | string | Y | -| name_type | If `true`, the object name will be specified from [`async_graphql::TypeName`](https://docs.rs/async-graphql/latest/async_graphql/trait.TypeName.html) trait | bool | Y | -| visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | -| inaccessible | Indicate that an union is not accessible from a supergraph when using Apollo Federation | bool | Y | -| tag | Arbitrary string metadata that will be propagated to the supergraph when using Apollo Federation. This attribute is repeatable | string | Y | - -# Item attributes - -| Attribute | description | Type | Optional | -|-----------|----------------------------|---------|----------| -| flatten | Similar to serde (flatten) | boolean | Y | - -# Define a union - -Define TypeA, TypeB, ... as MyUnion - -```rust -use async_graphql::*; - -#[derive(SimpleObject)] -struct TypeA { - value_a: i32, -} - -#[derive(SimpleObject)] -struct TypeB { - value_b: i32 -} - -#[derive(Union)] -enum MyUnion { - TypeA(TypeA), - TypeB(TypeB), -} - -struct Query; - -#[Object] -impl Query { - async fn all_data(&self) -> Vec { - vec![TypeA { value_a: 10 }.into(), TypeB { value_b: 20 }.into()] - } -} - -# tokio::runtime::Runtime::new().unwrap().block_on(async move { -let schema = Schema::build(Query, EmptyMutation, EmptySubscription).data("hello".to_string()).finish(); -let res = schema.execute(r#" -{ - allData { - ... on TypeA { - valueA - } - ... on TypeB { - valueB - } - } -}"#).await.into_result().unwrap().data; -assert_eq!(res, value!({ - "allData": [ - { "valueA": 10 }, - { "valueB": 20 }, - ] -})); -# }); -``` diff --git a/src/dynamic/base.rs b/src/dynamic/base.rs deleted file mode 100644 index 69ecadc0d..000000000 --- a/src/dynamic/base.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::dynamic::{Field, InputValue, Interface, InterfaceField, Object, TypeRef}; - -pub(crate) trait BaseField { - fn ty(&self) -> &TypeRef; - - fn argument(&self, name: &str) -> Option<&InputValue>; -} - -pub(crate) trait BaseContainer { - type FieldType: BaseField; - - fn name(&self) -> &str; - - fn graphql_type(&self) -> &str; - - fn field(&self, name: &str) -> Option<&Self::FieldType>; -} - -impl BaseField for Field { - #[inline] - fn ty(&self) -> &TypeRef { - &self.ty - } - - #[inline] - fn argument(&self, name: &str) -> Option<&InputValue> { - self.arguments.get(name) - } -} - -impl BaseContainer for Object { - type FieldType = Field; - - #[inline] - fn name(&self) -> &str { - &self.name - } - - fn graphql_type(&self) -> &str { - "Object" - } - - #[inline] - fn field(&self, name: &str) -> Option<&Self::FieldType> { - self.fields.get(name) - } -} - -impl BaseField for InterfaceField { - #[inline] - fn ty(&self) -> &TypeRef { - &self.ty - } - - #[inline] - fn argument(&self, name: &str) -> Option<&InputValue> { - self.arguments.get(name) - } -} - -impl BaseContainer for Interface { - type FieldType = InterfaceField; - - #[inline] - fn name(&self) -> &str { - &self.name - } - - fn graphql_type(&self) -> &str { - "Interface" - } - - #[inline] - fn field(&self, name: &str) -> Option<&Self::FieldType> { - self.fields.get(name) - } -} diff --git a/src/dynamic/check.rs b/src/dynamic/check.rs deleted file mode 100644 index aee896744..000000000 --- a/src/dynamic/check.rs +++ /dev/null @@ -1,505 +0,0 @@ -use std::collections::HashSet; - -use indexmap::IndexMap; - -use crate::dynamic::{ - InputObject, Interface, Object, SchemaError, Type, - base::{BaseContainer, BaseField}, - schema::SchemaInner, - type_ref::TypeRef, -}; - -impl SchemaInner { - pub(crate) fn check(&self) -> Result<(), SchemaError> { - self.check_types_exists()?; - self.check_root_types()?; - self.check_objects()?; - self.check_input_objects()?; - self.check_interfaces()?; - self.check_unions()?; - Ok(()) - } - - fn check_root_types(&self) -> Result<(), SchemaError> { - if let Some(ty) = self.types.get(&self.env.registry.query_type) { - if !matches!(ty, Type::Object(_)) { - return Err("The query root must be an object".into()); - } - } - - if let Some(mutation_type) = &self.env.registry.mutation_type { - if let Some(ty) = self.types.get(mutation_type) { - if !matches!(ty, Type::Object(_)) { - return Err("The mutation root must be an object".into()); - } - } - } - - if let Some(subscription_type) = &self.env.registry.subscription_type { - if let Some(ty) = self.types.get(subscription_type) { - if !matches!(ty, Type::Subscription(_)) { - return Err("The subscription root must be a subscription object".into()); - } - } - } - - Ok(()) - } - - fn check_types_exists(&self) -> Result<(), SchemaError> { - fn check, T: AsRef>( - types: &IndexMap, - type_names: I, - ) -> Result<(), SchemaError> { - for name in type_names { - if !types.contains_key(name.as_ref()) { - return Err(format!("Type \"{0}\" not found", name.as_ref()).into()); - } - } - Ok(()) - } - - check( - &self.types, - std::iter::once(self.env.registry.query_type.as_str()) - .chain(self.env.registry.mutation_type.as_deref()), - )?; - - for ty in self.types.values() { - match ty { - Type::Object(obj) => check( - &self.types, - obj.fields - .values() - .map(|field| { - std::iter::once(field.ty.type_name()) - .chain(field.arguments.values().map(|arg| arg.ty.type_name())) - }) - .flatten() - .chain(obj.implements.iter().map(AsRef::as_ref)), - )?, - Type::InputObject(obj) => { - check( - &self.types, - obj.fields.values().map(|field| field.ty.type_name()), - )?; - } - Type::Interface(interface) => check( - &self.types, - interface - .fields - .values() - .map(|field| { - std::iter::once(field.ty.type_name()) - .chain(field.arguments.values().map(|arg| arg.ty.type_name())) - }) - .flatten(), - )?, - Type::Union(union) => check(&self.types, &union.possible_types)?, - Type::Subscription(subscription) => check( - &self.types, - subscription - .fields - .values() - .map(|field| { - std::iter::once(field.ty.type_name()) - .chain(field.arguments.values().map(|arg| arg.ty.type_name())) - }) - .flatten(), - )?, - Type::Scalar(_) | Type::Enum(_) | Type::Upload => {} - } - } - - Ok(()) - } - - fn check_objects(&self) -> Result<(), SchemaError> { - let has_entities = self - .types - .iter() - .filter_map(|(_, ty)| ty.as_object()) - .any(Object::is_entity); - - // https://spec.graphql.org/October2021/#sec-Objects.Type-Validation - for ty in self.types.values() { - if let Type::Object(obj) = ty { - // An Object type must define one or more fields. - if obj.fields.is_empty() - && !(obj.type_name() == self.env.registry.query_type && has_entities) - { - return Err( - format!("Object \"{}\" must define one or more fields", obj.name).into(), - ); - } - - for field in obj.fields.values() { - // The field must not have a name which begins with the characters "__" (two - // underscores) - if field.name.starts_with("__") { - return Err(format!("Field \"{}.{}\" must not have a name which begins with the characters \"__\" (two underscores)", obj.name, field.name).into()); - } - - // The field must return a type where IsOutputType(fieldType) returns true. - if let Some(ty) = self.types.get(field.ty.type_name()) { - if !ty.is_output_type() { - return Err(format!( - "Field \"{}.{}\" must return a output type", - obj.name, field.name - ) - .into()); - } - } - - for arg in field.arguments.values() { - // The argument must not have a name which begins with the characters "__" - // (two underscores). - if arg.name.starts_with("__") { - return Err(format!("Argument \"{}.{}.{}\" must not have a name which begins with the characters \"__\" (two underscores)", obj.name, field.name, arg.name).into()); - } - - // The argument must accept a type where - // IsInputType(argumentType) returns true. - if let Some(ty) = self.types.get(arg.ty.type_name()) { - if !ty.is_input_type() { - return Err(format!( - "Argument \"{}.{}.{}\" must accept a input type", - obj.name, field.name, arg.name - ) - .into()); - } - } - } - } - - for interface_name in &obj.implements { - if let Some(ty) = self.types.get(interface_name) { - let interface = ty.as_interface().ok_or_else(|| { - format!("Type \"{}\" is not interface", interface_name) - })?; - check_is_valid_implementation(obj, interface)?; - } - } - } - } - - Ok(()) - } - - fn check_input_objects(&self) -> Result<(), SchemaError> { - // https://spec.graphql.org/October2021/#sec-Input-Objects.Type-Validation - for ty in self.types.values() { - if let Type::InputObject(obj) = ty { - for field in obj.fields.values() { - // The field must not have a name which begins with the characters "__" (two - // underscores) - if field.name.starts_with("__") { - return Err(format!("Field \"{}.{}\" must not have a name which begins with the characters \"__\" (two underscores)", obj.name, field.name).into()); - } - - // The input field must accept a type where IsInputType(inputFieldType) returns - // true. - if let Some(ty) = self.types.get(field.ty.type_name()) { - if !ty.is_input_type() { - return Err(format!( - "Field \"{}.{}\" must accept a input type", - obj.name, field.name - ) - .into()); - } - } - - if obj.oneof { - // The type of the input field must be nullable. - if !field.ty.is_nullable() { - return Err(format!( - "Field \"{}.{}\" must be nullable", - obj.name, field.name - ) - .into()); - } - - // The input field must not have a default value. - if field.default_value.is_some() { - return Err(format!( - "Field \"{}.{}\" must not have a default value", - obj.name, field.name - ) - .into()); - } - } - } - - // If an Input Object references itself either directly or - // through referenced Input Objects, at least one of the - // fields in the chain of references must be either a - // nullable or a List type. - self.check_input_object_reference(&obj.name, &obj, &mut HashSet::new())?; - } - } - - Ok(()) - } - - fn check_input_object_reference<'a>( - &'a self, - current: &str, - obj: &'a InputObject, - ref_chain: &mut HashSet<&'a str>, - ) -> Result<(), SchemaError> { - fn typeref_nonnullable_name(ty: &TypeRef) -> Option<&str> { - match ty { - TypeRef::NonNull(inner) => match inner.as_ref() { - TypeRef::Named(name) => Some(name), - _ => None, - }, - _ => None, - } - } - - for field in obj.fields.values() { - if let Some(this_name) = typeref_nonnullable_name(&field.ty) { - if this_name == current { - return Err(format!("\"{}\" references itself either directly or through referenced Input Objects, at least one of the fields in the chain of references must be either a nullable or a List type.", current).into()); - } else if let Some(obj) = self - .types - .get(field.ty.type_name()) - .and_then(Type::as_input_object) - { - // don't visit the reference if we've already visited it in this call chain - // (prevents getting stuck in local cycles and overflowing stack) - // true return from insert indicates the value was not previously there - if ref_chain.insert(this_name) { - self.check_input_object_reference(current, obj, ref_chain)?; - ref_chain.remove(this_name); - } - } - } - } - - Ok(()) - } - - fn check_interfaces(&self) -> Result<(), SchemaError> { - // https://spec.graphql.org/October2021/#sec-Interfaces.Type-Validation - for ty in self.types.values() { - if let Type::Interface(interface) = ty { - for field in interface.fields.values() { - // The field must not have a name which begins with the characters "__" (two - // underscores) - if field.name.starts_with("__") { - return Err(format!("Field \"{}.{}\" must not have a name which begins with the characters \"__\" (two underscores)", interface.name, field.name).into()); - } - - // The field must return a type where IsOutputType(fieldType) returns true. - if let Some(ty) = self.types.get(field.ty.type_name()) { - if !ty.is_output_type() { - return Err(format!( - "Field \"{}.{}\" must return a output type", - interface.name, field.name - ) - .into()); - } - } - - for arg in field.arguments.values() { - // The argument must not have a name which begins with the characters "__" - // (two underscores). - if arg.name.starts_with("__") { - return Err(format!("Argument \"{}.{}.{}\" must not have a name which begins with the characters \"__\" (two underscores)", interface.name, field.name, arg.name).into()); - } - - // The argument must accept a type where - // IsInputType(argumentType) returns true. - if let Some(ty) = self.types.get(arg.ty.type_name()) { - if !ty.is_input_type() { - return Err(format!( - "Argument \"{}.{}.{}\" must accept a input type", - interface.name, field.name, arg.name - ) - .into()); - } - } - } - - // An interface type may declare that it implements one or more unique - // interfaces, but may not implement itself. - if interface.implements.contains(&interface.name) { - return Err(format!( - "Interface \"{}\" may not implement itself", - interface.name - ) - .into()); - } - - // An interface type must be a super-set of all interfaces - // it implements - for interface_name in &interface.implements { - if let Some(ty) = self.types.get(interface_name) { - let implemenented_type = ty.as_interface().ok_or_else(|| { - format!("Type \"{}\" is not interface", interface_name) - })?; - check_is_valid_implementation(interface, implemenented_type)?; - } - } - } - } - } - - Ok(()) - } - - fn check_unions(&self) -> Result<(), SchemaError> { - // https://spec.graphql.org/October2021/#sec-Unions.Type-Validation - for ty in self.types.values() { - if let Type::Union(union) = ty { - // The member types of a Union type must all be Object base - // types; Scalar, Interface and Union types must not be member - // types of a Union. Similarly, wrapping types must not be - // member types of a Union. - for type_name in &union.possible_types { - if let Some(ty) = self.types.get(type_name) { - if ty.as_object().is_none() { - return Err(format!( - "Member \"{}\" of union \"{}\" is not an object", - type_name, union.name - ) - .into()); - } - } - } - } - } - - Ok(()) - } -} - -fn check_is_valid_implementation( - implementing_type: &impl BaseContainer, - implemented_type: &Interface, -) -> Result<(), SchemaError> { - for field in implemented_type.fields.values() { - let impl_field = implementing_type.field(&field.name).ok_or_else(|| { - format!( - "{} \"{}\" requires field \"{}\" defined by interface \"{}\"", - implementing_type.graphql_type(), - implementing_type.name(), - field.name, - implemented_type.name - ) - })?; - - for arg in field.arguments.values() { - let impl_arg = match impl_field.argument(&arg.name) { - Some(impl_arg) => impl_arg, - None if !arg.ty.is_nullable() => { - return Err(format!( - "Field \"{}.{}\" requires argument \"{}\" defined by interface \"{}.{}\"", - implementing_type.name(), - field.name, - arg.name, - implemented_type.name, - field.name, - ) - .into()); - } - None => continue, - }; - - if !arg.ty.is_subtype(&impl_arg.ty) { - return Err(format!( - "Argument \"{}.{}.{}\" is not sub-type of \"{}.{}.{}\"", - implemented_type.name, - field.name, - arg.name, - implementing_type.name(), - field.name, - arg.name - ) - .into()); - } - } - - // field must return a type which is equal to or a sub-type of (covariant) the - // return type of implementedField field’s return type - if !impl_field.ty().is_subtype(&field.ty) { - return Err(format!( - "Field \"{}.{}\" is not sub-type of \"{}.{}\"", - implementing_type.name(), - field.name, - implemented_type.name, - field.name, - ) - .into()); - } - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use crate::{ - Value, - dynamic::{ - Field, FieldFuture, InputObject, InputValue, Object, Schema, SchemaBuilder, TypeRef, - }, - }; - - fn base_schema() -> SchemaBuilder { - let query = Object::new("Query").field(Field::new("dummy", TypeRef::named("Int"), |_| { - FieldFuture::new(async { Ok(Some(Value::from(42))) }) - })); - Schema::build("Query", None, None).register(query) - } - - #[test] - fn test_recursive_input_objects() { - let top_level = InputObject::new("TopLevel") - .field(InputValue::new("mid", TypeRef::named_nn("MidLevel"))); - let mid_level = InputObject::new("MidLevel") - .field(InputValue::new("bottom", TypeRef::named("BotLevel"))) - .field(InputValue::new( - "list_bottom", - TypeRef::named_nn_list_nn("BotLevel"), - )); - let bot_level = InputObject::new("BotLevel") - .field(InputValue::new("top", TypeRef::named_nn("TopLevel"))); - let schema = base_schema() - .register(top_level) - .register(mid_level) - .register(bot_level); - schema.finish().unwrap(); - } - - #[test] - fn test_recursive_input_objects_bad() { - let top_level = InputObject::new("TopLevel") - .field(InputValue::new("mid", TypeRef::named_nn("MidLevel"))); - let mid_level = InputObject::new("MidLevel") - .field(InputValue::new("bottom", TypeRef::named_nn("BotLevel"))); - let bot_level = InputObject::new("BotLevel") - .field(InputValue::new("top", TypeRef::named_nn("TopLevel"))); - let schema = base_schema() - .register(top_level) - .register(mid_level) - .register(bot_level); - schema.finish().unwrap_err(); - } - - #[test] - fn test_recursive_input_objects_local_cycle() { - let top_level = InputObject::new("TopLevel") - .field(InputValue::new("mid", TypeRef::named_nn("MidLevel"))); - let mid_level = InputObject::new("MidLevel") - .field(InputValue::new("bottom", TypeRef::named_nn("BotLevel"))); - let bot_level = InputObject::new("BotLevel") - .field(InputValue::new("mid", TypeRef::named_nn("MidLevel"))); - let schema = base_schema() - .register(top_level) - .register(mid_level) - .register(bot_level); - schema.finish().unwrap_err(); - } -} diff --git a/src/dynamic/directive.rs b/src/dynamic/directive.rs deleted file mode 100644 index df515bfd3..000000000 --- a/src/dynamic/directive.rs +++ /dev/null @@ -1,43 +0,0 @@ -use indexmap::IndexMap; - -use crate::{Value, registry::MetaDirectiveInvocation}; - -/// A GraphQL directive -#[derive(Debug, Clone)] -pub struct Directive { - name: String, - args: IndexMap, -} - -impl Directive { - /// Create a directive usage - pub fn new(name: impl Into) -> Self { - Self { - name: name.into(), - args: IndexMap::default(), - } - } - - /// Add an argument to the directive - #[inline] - pub fn argument(mut self, name: impl Into, value: Value) -> Self { - self.args.insert(name.into(), value); - self - } -} - -impl From for MetaDirectiveInvocation { - fn from(directive: Directive) -> Self { - Self { - name: directive.name, - args: directive.args, - } - } -} - -pub fn to_meta_directive_invocation(directives: Vec) -> Vec { - directives - .into_iter() - .map(MetaDirectiveInvocation::from) - .collect() -} diff --git a/src/dynamic/enum.rs b/src/dynamic/enum.rs deleted file mode 100644 index a56e88024..000000000 --- a/src/dynamic/enum.rs +++ /dev/null @@ -1,207 +0,0 @@ -use indexmap::IndexMap; - -use super::{Directive, directive::to_meta_directive_invocation}; -use crate::{ - dynamic::SchemaError, - registry::{Deprecation, MetaEnumValue, MetaType, Registry}, -}; - -/// A GraphQL enum item -#[derive(Debug)] -pub struct EnumItem { - pub(crate) name: String, - pub(crate) description: Option, - pub(crate) deprecation: Deprecation, - inaccessible: bool, - tags: Vec, - pub(crate) directives: Vec, -} - -impl> From for EnumItem { - #[inline] - fn from(name: T) -> Self { - EnumItem { - name: name.into(), - description: None, - deprecation: Deprecation::NoDeprecated, - inaccessible: false, - tags: Vec::new(), - directives: Vec::new(), - } - } -} - -impl EnumItem { - /// Create a new EnumItem - #[inline] - pub fn new(name: impl Into) -> Self { - name.into().into() - } - - impl_set_description!(); - impl_set_deprecation!(); - impl_set_inaccessible!(); - impl_set_tags!(); - impl_directive!(); -} - -/// A GraphQL enum type -#[derive(Debug)] -pub struct Enum { - pub(crate) name: String, - pub(crate) description: Option, - pub(crate) enum_values: IndexMap, - inaccessible: bool, - tags: Vec, - pub(crate) directives: Vec, - requires_scopes: Vec, -} - -impl Enum { - /// Create a GraphqL enum type - #[inline] - pub fn new(name: impl Into) -> Self { - Self { - name: name.into(), - description: None, - enum_values: Default::default(), - inaccessible: false, - tags: Vec::new(), - directives: Vec::new(), - requires_scopes: Vec::new(), - } - } - - impl_set_description!(); - impl_directive!(); - - /// Add an item - #[inline] - pub fn item(mut self, item: impl Into) -> Self { - let item = item.into(); - self.enum_values.insert(item.name.clone(), item); - self - } - - /// Add items - pub fn items(mut self, items: impl IntoIterator>) -> Self { - for item in items { - let item = item.into(); - self.enum_values.insert(item.name.clone(), item); - } - self - } - - impl_set_inaccessible!(); - impl_set_tags!(); - - /// Returns the type name - #[inline] - pub fn type_name(&self) -> &str { - &self.name - } - - pub(crate) fn register(&self, registry: &mut Registry) -> Result<(), SchemaError> { - let mut enum_values = IndexMap::new(); - - for item in self.enum_values.values() { - enum_values.insert( - item.name.clone(), - MetaEnumValue { - name: item.name.as_str().into(), - description: item.description.clone(), - deprecation: item.deprecation.clone(), - visible: None, - inaccessible: item.inaccessible, - tags: item.tags.clone(), - directive_invocations: to_meta_directive_invocation(item.directives.clone()), - }, - ); - } - - registry.types.insert( - self.name.clone(), - MetaType::Enum { - name: self.name.clone(), - description: self.description.clone(), - enum_values, - visible: None, - inaccessible: self.inaccessible, - tags: self.tags.clone(), - rust_typename: None, - directive_invocations: to_meta_directive_invocation(self.directives.clone()), - requires_scopes: self.requires_scopes.clone(), - }, - ); - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use crate::{Name, PathSegment, Pos, ServerError, Value, dynamic::*, value}; - - #[tokio::test] - async fn enum_type() { - let my_enum = Enum::new("MyEnum").item("A").item("B"); - - let query = Object::new("Query") - .field(Field::new( - "value", - TypeRef::named_nn(my_enum.type_name()), - |_| FieldFuture::new(async { Ok(Some(Value::from(Name::new("A")))) }), - )) - .field( - Field::new("value2", TypeRef::named_nn(my_enum.type_name()), |ctx| { - FieldFuture::new(async move { - Ok(Some(FieldValue::value(Name::new( - ctx.args.try_get("input")?.enum_name()?, - )))) - }) - }) - .argument(InputValue::new( - "input", - TypeRef::named_nn(my_enum.type_name()), - )), - ) - .field(Field::new( - "errValue", - TypeRef::named_nn(my_enum.type_name()), - |_| FieldFuture::new(async { Ok(Some(Value::from(Name::new("C")))) }), - )); - let schema = Schema::build("Query", None, None) - .register(my_enum) - .register(query) - .finish() - .unwrap(); - - assert_eq!( - schema - .execute("{ value value2(input: B) }") - .await - .into_result() - .unwrap() - .data, - value!({ - "value": "A", - "value2": "B" - }) - ); - - assert_eq!( - schema - .execute("{ errValue }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "internal: invalid item for enum \"MyEnum\"".to_owned(), - source: None, - locations: vec![Pos { column: 3, line: 1 }], - path: vec![PathSegment::Field("errValue".to_owned())], - extensions: None, - }] - ); - } -} diff --git a/src/dynamic/error.rs b/src/dynamic/error.rs deleted file mode 100644 index 14f5d6206..000000000 --- a/src/dynamic/error.rs +++ /dev/null @@ -1,10 +0,0 @@ -/// An error can occur when building dynamic schema -#[derive(Debug, thiserror::Error, Eq, PartialEq)] -#[error("{0}")] -pub struct SchemaError(pub String); - -impl> From for SchemaError { - fn from(err: T) -> Self { - SchemaError(err.into()) - } -} diff --git a/src/dynamic/field.rs b/src/dynamic/field.rs deleted file mode 100644 index b6f592a73..000000000 --- a/src/dynamic/field.rs +++ /dev/null @@ -1,397 +0,0 @@ -use std::{ - any::Any, - borrow::Cow, - fmt::{self, Debug}, - ops::Deref, -}; - -use futures_util::{Future, FutureExt, future::BoxFuture}; -use indexmap::IndexMap; - -use super::Directive; -use crate::{ - Context, Error, Result, Value, - dynamic::{InputValue, ObjectAccessor, TypeRef}, - registry::Deprecation, -}; - -/// A value returned from the resolver function -pub struct FieldValue<'a>(pub(crate) FieldValueInner<'a>); - -pub(crate) enum FieldValueInner<'a> { - /// Const value - Value(Value), - /// Borrowed any value - /// The first item is the [`std::any::type_name`] of the value used for - /// debugging. - BorrowedAny(Cow<'static, str>, &'a (dyn Any + Send + Sync)), - /// Owned any value - /// The first item is the [`std::any::type_name`] of the value used for - /// debugging. - OwnedAny(Cow<'static, str>, Box), - /// A list - List(Vec>), - /// A typed Field value - WithType { - /// Field value - value: Box>, - /// Object name - ty: Cow<'static, str>, - }, -} - -impl Debug for FieldValue<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match &self.0 { - FieldValueInner::Value(v) => write!(f, "{}", v), - FieldValueInner::BorrowedAny(ty, _) - | FieldValueInner::OwnedAny(ty, _) - | FieldValueInner::WithType { ty, .. } => write!(f, "{}", ty), - FieldValueInner::List(list) => match list.first() { - Some(v) => { - write!(f, "[{:?}, ...]", v) - } - None => { - write!(f, "[()]") - } - }, - } - } -} - -impl From<()> for FieldValue<'_> { - #[inline] - fn from(_: ()) -> Self { - Self(FieldValueInner::Value(Value::Null)) - } -} - -impl From for FieldValue<'_> { - #[inline] - fn from(value: Value) -> Self { - Self(FieldValueInner::Value(value)) - } -} - -impl<'a, T: Into>> From> for FieldValue<'a> { - fn from(values: Vec) -> Self { - Self(FieldValueInner::List( - values.into_iter().map(Into::into).collect(), - )) - } -} - -impl<'a> FieldValue<'a> { - /// A null value equivalent to `FieldValue::Value(Value::Null)` - pub const NULL: FieldValue<'a> = Self(FieldValueInner::Value(Value::Null)); - - /// A none value equivalent to `None::` - /// - /// It is more convenient to use when your resolver needs to return `None`. - /// - /// # Examples - /// - /// ``` - /// use async_graphql::dynamic::*; - /// - /// let query = - /// Object::new("Query").field(Field::new("value", TypeRef::named(TypeRef::INT), |ctx| { - /// FieldFuture::new(async move { Ok(FieldValue::NONE) }) - /// })); - /// ``` - pub const NONE: Option> = None; - - /// Returns a `None::` meaning the resolver no results. - pub const fn none() -> Option> { - None - } - - /// Create a FieldValue from [`Value`] - #[inline] - pub fn value(value: impl Into) -> Self { - Self(FieldValueInner::Value(value.into())) - } - - /// Create a FieldValue from owned any value - #[inline] - pub fn owned_any(obj: T) -> Self { - Self(FieldValueInner::OwnedAny( - std::any::type_name::().into(), - Box::new(obj), - )) - } - - /// Create a FieldValue from unsized any value - #[inline] - pub fn boxed_any(obj: Box) -> Self { - Self(FieldValueInner::OwnedAny("Any".into(), obj)) - } - - /// Create a FieldValue from owned any value - #[inline] - pub fn borrowed_any(obj: &'a (dyn Any + Send + Sync)) -> Self { - Self(FieldValueInner::BorrowedAny("Any".into(), obj)) - } - - /// Create a FieldValue from list - #[inline] - pub fn list(values: I) -> Self - where - I: IntoIterator, - T: Into>, - { - Self(FieldValueInner::List( - values.into_iter().map(Into::into).collect(), - )) - } - - /// Create a FieldValue and specify its type, which must be an object - /// - /// NOTE: Fields of type `Interface` or `Union` must return - /// `FieldValue::WithType`. - /// - /// # Examples - /// - /// ``` - /// use async_graphql::{dynamic::*, value, Value}; - /// - /// struct MyObjData { - /// a: i32, - /// } - /// - /// let my_obj = Object::new("MyObj").field(Field::new( - /// "a", - /// TypeRef::named_nn(TypeRef::INT), - /// |ctx| FieldFuture::new(async move { - /// let data = ctx.parent_value.try_downcast_ref::()?; - /// Ok(Some(Value::from(data.a))) - /// }), - /// )); - /// - /// let my_union = Union::new("MyUnion").possible_type(my_obj.type_name()); - /// - /// let query = Object::new("Query").field(Field::new( - /// "obj", - /// TypeRef::named_nn(my_union.type_name()), - /// |_| FieldFuture::new(async move { - /// Ok(Some(FieldValue::owned_any(MyObjData { a: 10 }).with_type("MyObj"))) - /// }), - /// )); - /// - /// let schema = Schema::build("Query", None, None) - /// .register(my_obj) - /// .register(my_union) - /// .register(query) - /// .finish() - /// .unwrap(); - /// - /// # tokio::runtime::Runtime::new().unwrap().block_on(async move { - /// assert_eq!( - /// schema - /// .execute("{ obj { ... on MyObj { a } } }") - /// .await - /// .into_result() - /// .unwrap() - /// .data, - /// value!({ "obj": { "a": 10 } }) - /// ); - /// # }); - /// ``` - pub fn with_type(self, ty: impl Into>) -> Self { - Self(FieldValueInner::WithType { - value: Box::new(self), - ty: ty.into(), - }) - } - - /// If the FieldValue is a value, returns the associated - /// Value. Returns `None` otherwise. - #[inline] - pub fn as_value(&self) -> Option<&Value> { - match &self.0 { - FieldValueInner::Value(value) => Some(value), - _ => None, - } - } - - /// Like `as_value`, but returns `Result`. - #[inline] - pub fn try_to_value(&self) -> Result<&Value> { - self.as_value() - .ok_or_else(|| Error::new(format!("internal: \"{:?}\" not a Value", self))) - } - - /// If the FieldValue is a list, returns the associated - /// vector. Returns `None` otherwise. - #[inline] - pub fn as_list(&self) -> Option<&[FieldValue]> { - match &self.0 { - FieldValueInner::List(values) => Some(values), - _ => None, - } - } - - /// Like `as_list`, but returns `Result`. - #[inline] - pub fn try_to_list(&self) -> Result<&[FieldValue]> { - self.as_list() - .ok_or_else(|| Error::new(format!("internal: \"{:?}\" not a List", self))) - } - - /// If the FieldValue is a any, returns the associated - /// vector. Returns `None` otherwise. - #[inline] - pub fn downcast_ref(&self) -> Option<&T> { - match &self.0 { - FieldValueInner::BorrowedAny(_, value) => value.downcast_ref::(), - FieldValueInner::OwnedAny(_, value) => value.downcast_ref::(), - _ => None, - } - } - - /// Like `downcast_ref`, but returns `Result`. - #[inline] - pub fn try_downcast_ref(&self) -> Result<&T> { - self.downcast_ref().ok_or_else(|| { - Error::new(format!( - "internal: \"{:?}\" is not of the expected type \"{}\"", - self, - std::any::type_name::() - )) - }) - } -} - -type BoxResolveFut<'a> = BoxFuture<'a, Result>>>; - -/// A context for resolver function -pub struct ResolverContext<'a> { - /// GraphQL context - pub ctx: &'a Context<'a>, - /// Field arguments - pub args: ObjectAccessor<'a>, - /// Parent value - pub parent_value: &'a FieldValue<'a>, -} - -impl<'a> Deref for ResolverContext<'a> { - type Target = Context<'a>; - - fn deref(&self) -> &Self::Target { - self.ctx - } -} - -/// A future that returned from field resolver -pub enum FieldFuture<'a> { - /// A pure value without any async operation - Value(Option>), - - /// A future that returned from field resolver - Future(BoxResolveFut<'a>), -} - -impl<'a> FieldFuture<'a> { - /// Create a `FieldFuture` from a `Future` - pub fn new(future: Fut) -> Self - where - Fut: Future>> + Send + 'a, - R: Into> + Send, - { - FieldFuture::Future( - async move { - let res = future.await?; - Ok(res.map(Into::into)) - } - .boxed(), - ) - } - - /// Create a `FieldFuture` from a `Value` - pub fn from_value(value: Option) -> Self { - FieldFuture::Value(value.map(FieldValue::from)) - } -} - -pub(crate) type BoxResolverFn = - Box<(dyn for<'a> Fn(ResolverContext<'a>) -> FieldFuture<'a> + Send + Sync)>; - -/// A GraphQL field -pub struct Field { - pub(crate) name: String, - pub(crate) description: Option, - pub(crate) arguments: IndexMap, - pub(crate) ty: TypeRef, - pub(crate) ty_str: String, - pub(crate) resolver_fn: BoxResolverFn, - pub(crate) deprecation: Deprecation, - pub(crate) external: bool, - pub(crate) requires: Option, - pub(crate) provides: Option, - pub(crate) shareable: bool, - pub(crate) inaccessible: bool, - pub(crate) tags: Vec, - pub(crate) override_from: Option, - pub(crate) directives: Vec, - pub(crate) requires_scopes: Vec, -} - -impl Debug for Field { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Field") - .field("name", &self.name) - .field("description", &self.description) - .field("arguments", &self.arguments) - .field("ty", &self.ty) - .field("deprecation", &self.deprecation) - .finish() - } -} - -impl Field { - /// Create a GraphQL field - pub fn new(name: N, ty: T, resolver_fn: F) -> Self - where - N: Into, - T: Into, - F: for<'a> Fn(ResolverContext<'a>) -> FieldFuture<'a> + Send + Sync + 'static, - { - let ty = ty.into(); - Self { - name: name.into(), - description: None, - arguments: Default::default(), - ty_str: ty.to_string(), - ty, - resolver_fn: Box::new(resolver_fn), - deprecation: Deprecation::NoDeprecated, - external: false, - requires: None, - provides: None, - shareable: false, - inaccessible: false, - tags: Vec::new(), - override_from: None, - directives: Vec::new(), - requires_scopes: Vec::new(), - } - } - - impl_set_description!(); - impl_set_deprecation!(); - impl_set_external!(); - impl_set_requires!(); - impl_set_provides!(); - impl_set_shareable!(); - impl_set_inaccessible!(); - impl_set_tags!(); - impl_set_override_from!(); - impl_directive!(); - - /// Add an argument to the field - #[inline] - pub fn argument(mut self, input_value: InputValue) -> Self { - self.arguments.insert(input_value.name.clone(), input_value); - self - } -} diff --git a/src/dynamic/input_object.rs b/src/dynamic/input_object.rs deleted file mode 100644 index 46230d8cb..000000000 --- a/src/dynamic/input_object.rs +++ /dev/null @@ -1,331 +0,0 @@ -use indexmap::IndexMap; - -use super::{Directive, directive::to_meta_directive_invocation}; -use crate::{ - dynamic::InputValue, - registry::{MetaInputValue, MetaType, Registry}, -}; - -/// A GraphQL input object type -/// -/// # Examples -/// -/// ``` -/// use async_graphql::{dynamic::*, value, Value}; -/// -/// let my_input = InputObject::new("MyInput") -/// .field(InputValue::new("a", TypeRef::named_nn(TypeRef::INT))) -/// .field(InputValue::new("b", TypeRef::named_nn(TypeRef::INT))); -/// -/// let query = Object::new("Query").field( -/// Field::new("add", TypeRef::named_nn(TypeRef::INT), |ctx| { -/// FieldFuture::new(async move { -/// let input = ctx.args.try_get("input")?; -/// let input = input.object()?; -/// let a = input.try_get("a")?.i64()?; -/// let b = input.try_get("b")?.i64()?; -/// Ok(Some(Value::from(a + b))) -/// }) -/// }) -/// .argument(InputValue::new("input", TypeRef::named_nn(my_input.type_name()))) -/// ); -/// -/// # tokio::runtime::Runtime::new().unwrap().block_on(async move { -/// -/// let schema = Schema::build(query.type_name(), None, None) -/// .register(my_input) -/// .register(query) -/// .finish()?; -/// -/// assert_eq!( -/// schema -/// .execute("{ add(input: { a: 10, b: 20 }) }") -/// .await -/// .into_result() -/// .unwrap() -/// .data, -/// value!({ "add": 30 }) -/// ); -/// -/// # Ok::<_, SchemaError>(()) -/// # }).unwrap(); -/// ``` -#[derive(Debug)] -pub struct InputObject { - pub(crate) name: String, - pub(crate) description: Option, - pub(crate) fields: IndexMap, - pub(crate) oneof: bool, - inaccessible: bool, - tags: Vec, - directives: Vec, -} - -impl InputObject { - /// Create a GraphQL input object type - #[inline] - pub fn new(name: impl Into) -> Self { - Self { - name: name.into(), - description: None, - fields: Default::default(), - oneof: false, - inaccessible: false, - tags: Vec::new(), - directives: Vec::new(), - } - } - - impl_set_description!(); - impl_set_inaccessible!(); - impl_set_tags!(); - impl_directive!(); - - /// Add a field - #[inline] - pub fn field(mut self, field: InputValue) -> Self { - assert!( - !self.fields.contains_key(&field.name), - "Field `{}` already exists", - field.name - ); - self.fields.insert(field.name.clone(), field); - self - } - - /// Indicates this Input Object is a OneOf Input Object - pub fn oneof(self) -> Self { - Self { - oneof: true, - ..self - } - } - - /// Returns the type name - #[inline] - pub fn type_name(&self) -> &str { - &self.name - } - - pub(crate) fn register(&self, registry: &mut Registry) -> Result<(), super::SchemaError> { - let mut input_fields = IndexMap::new(); - - for field in self.fields.values() { - input_fields.insert( - field.name.clone(), - MetaInputValue { - name: field.name.clone(), - description: field.description.clone(), - ty: field.ty.to_string(), - deprecation: field.deprecation.clone(), - default_value: field.default_value.as_ref().map(ToString::to_string), - visible: None, - inaccessible: self.inaccessible, - tags: self.tags.clone(), - is_secret: false, - directive_invocations: to_meta_directive_invocation(field.directives.clone()), - }, - ); - } - - registry.types.insert( - self.name.clone(), - MetaType::InputObject { - name: self.name.clone(), - description: self.description.clone(), - input_fields, - visible: None, - inaccessible: self.inaccessible, - tags: self.tags.clone(), - rust_typename: None, - oneof: self.oneof, - directive_invocations: to_meta_directive_invocation(self.directives.clone()), - }, - ); - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use crate::{Pos, ServerError, Value, dynamic::*, value}; - - #[tokio::test] - async fn input_object() { - let myinput = InputObject::new("MyInput") - .field(InputValue::new("a", TypeRef::named_nn(TypeRef::INT))) - .field(InputValue::new("b", TypeRef::named_nn(TypeRef::INT))); - let query = Object::new("Query").field( - Field::new("add", TypeRef::named_nn(TypeRef::INT), |ctx| { - FieldFuture::new(async move { - let input = ctx.args.try_get("input")?; - let input = input.object()?; - let a = input.try_get("a")?.i64()?; - let b = input.try_get("b")?.i64()?; - Ok(Some(Value::from(a + b))) - }) - }) - .argument(InputValue::new( - "input", - TypeRef::named_nn(myinput.type_name()), - )), - ); - - let schema = Schema::build(query.type_name(), None, None) - .register(query) - .register(myinput) - .finish() - .unwrap(); - - assert_eq!( - schema - .execute("{ add(input: {a: 10, b: 20}) }") - .await - .into_result() - .unwrap() - .data, - value!({ - "add": 30 - }) - ); - } - - #[tokio::test] - async fn oneof_input_object() { - let myinput = InputObject::new("MyInput") - .oneof() - .field(InputValue::new("a", TypeRef::named(TypeRef::INT))) - .field(InputValue::new("b", TypeRef::named(TypeRef::INT))); - - let query = Object::new("Query").field( - Field::new("add10", TypeRef::named_nn(TypeRef::INT), |ctx| { - FieldFuture::new(async move { - let input = ctx.args.try_get("input")?; - let input = input.object()?; - Ok(Some(Value::from(if let Some(a) = input.get("a") { - a.i64()? + 10 - } else if let Some(b) = input.get("b") { - b.i64()? + 10 - } else { - unreachable!() - }))) - }) - }) - .argument(InputValue::new( - "input", - TypeRef::named_nn(myinput.type_name()), - )), - ); - - let schema = Schema::build(query.type_name(), None, None) - .register(query) - .register(myinput) - .finish() - .unwrap(); - - assert_eq!( - schema - .execute("{ add10(input: {a: 10}) }") - .await - .into_result() - .unwrap() - .data, - value!({ - "add10": 20 - }) - ); - - assert_eq!( - schema - .execute("{ add10(input: {b: 20}) }") - .await - .into_result() - .unwrap() - .data, - value!({ - "add10": 30 - }) - ); - - assert_eq!( - schema - .execute("{ add10(input: {}) }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "Invalid value for argument \"input\", Oneof input objects requires have exactly one field".to_owned(), - source: None, - locations: vec![Pos { column: 9, line: 1 }], - path: vec![], - extensions: None, - }] - ); - - assert_eq!( - schema - .execute("{ add10(input: { a: 10, b: 20 }) }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "Invalid value for argument \"input\", Oneof input objects requires have exactly one field".to_owned(), - source: None, - locations: vec![Pos { column: 9, line: 1 }], - path: vec![], - extensions: None, - }] - ); - } - - #[tokio::test] - async fn invalid_oneof_input_object() { - let myinput = InputObject::new("MyInput") - .oneof() - .field(InputValue::new("a", TypeRef::named(TypeRef::INT))) - .field(InputValue::new("b", TypeRef::named_nn(TypeRef::INT))); - - let query = Object::new("Query").field( - Field::new("value", TypeRef::named_nn(TypeRef::INT), |_| { - FieldFuture::new(async move { Ok(Some(Value::from(10))) }) - }) - .argument(InputValue::new( - "input", - TypeRef::named_nn(myinput.type_name()), - )), - ); - - let err = Schema::build(query.type_name(), None, None) - .register(query) - .register(myinput) - .finish() - .unwrap_err(); - assert_eq!(err.0, "Field \"MyInput.b\" must be nullable".to_string()); - - let myinput = InputObject::new("MyInput") - .oneof() - .field(InputValue::new("a", TypeRef::named(TypeRef::INT))) - .field(InputValue::new("b", TypeRef::named(TypeRef::INT)).default_value(value!(10))); - - let query = Object::new("Query").field( - Field::new("value", TypeRef::named_nn(TypeRef::INT), |_| { - FieldFuture::new(async move { Ok(Some(Value::from(10))) }) - }) - .argument(InputValue::new( - "input", - TypeRef::named_nn(myinput.type_name()), - )), - ); - - let err = Schema::build(query.type_name(), None, None) - .register(query) - .register(myinput) - .finish() - .unwrap_err(); - assert_eq!( - err.0, - "Field \"MyInput.b\" must not have a default value".to_string() - ); - } -} diff --git a/src/dynamic/input_value.rs b/src/dynamic/input_value.rs deleted file mode 100644 index b75f1341e..000000000 --- a/src/dynamic/input_value.rs +++ /dev/null @@ -1,69 +0,0 @@ -use super::{Directive, directive::to_meta_directive_invocation}; -use crate::{ - Value, - dynamic::TypeRef, - registry::{Deprecation, MetaInputValue}, -}; - -/// A GraphQL input value type -#[derive(Debug)] -pub struct InputValue { - pub(crate) name: String, - pub(crate) description: Option, - pub(crate) ty: TypeRef, - pub(crate) default_value: Option, - pub(crate) inaccessible: bool, - pub(crate) tags: Vec, - pub(crate) directives: Vec, - pub(crate) deprecation: Deprecation, -} - -impl InputValue { - /// Create a GraphQL input value type - #[inline] - pub fn new(name: impl Into, ty: impl Into) -> Self { - Self { - name: name.into(), - description: None, - ty: ty.into(), - default_value: None, - inaccessible: false, - tags: Vec::new(), - directives: vec![], - deprecation: Deprecation::NoDeprecated, - } - } - - impl_set_description!(); - impl_set_inaccessible!(); - impl_set_tags!(); - impl_directive!(); - impl_set_deprecation!(); - - /// Set the default value - #[inline] - pub fn default_value(self, value: impl Into) -> Self { - Self { - default_value: Some(value.into()), - ..self - } - } - - pub(crate) fn to_meta_input_value(&self) -> MetaInputValue { - MetaInputValue { - name: self.name.clone(), - description: self.description.clone(), - ty: self.ty.to_string(), - deprecation: self.deprecation.clone(), - default_value: self - .default_value - .as_ref() - .map(std::string::ToString::to_string), - visible: None, - inaccessible: self.inaccessible, - tags: self.tags.clone(), - is_secret: false, - directive_invocations: to_meta_directive_invocation(self.directives.clone()), - } - } -} diff --git a/src/dynamic/interface.rs b/src/dynamic/interface.rs deleted file mode 100644 index e9cbe3e3c..000000000 --- a/src/dynamic/interface.rs +++ /dev/null @@ -1,455 +0,0 @@ -use indexmap::{IndexMap, IndexSet}; - -use super::{Directive, directive::to_meta_directive_invocation}; -use crate::{ - dynamic::{InputValue, SchemaError, TypeRef}, - registry::{Deprecation, MetaField, MetaType, Registry}, -}; - -/// A GraphQL interface field type -/// -/// # Examples -/// -/// ``` -/// use async_graphql::{dynamic::*, value, Value}; -/// -/// let obj_a = Object::new("MyObjA") -/// .implement("MyInterface") -/// .field(Field::new("a", TypeRef::named_nn(TypeRef::INT), |_| { -/// FieldFuture::new(async { Ok(Some(Value::from(100))) }) -/// })) -/// .field(Field::new("b", TypeRef::named_nn(TypeRef::INT), |_| { -/// FieldFuture::new(async { Ok(Some(Value::from(200))) }) -/// })); -/// -/// let obj_b = Object::new("MyObjB") -/// .implement("MyInterface") -/// .field(Field::new("a", TypeRef::named_nn(TypeRef::INT), |_| { -/// FieldFuture::new(async { Ok(Some(Value::from(300))) }) -/// })) -/// .field(Field::new("c", TypeRef::named_nn(TypeRef::INT), |_| { -/// FieldFuture::new(async { Ok(Some(Value::from(400))) }) -/// })); -/// -/// let interface = Interface::new("MyInterface").field(InterfaceField::new("a", TypeRef::named_nn(TypeRef::INT))); -/// -/// let query = Object::new("Query") -/// .field(Field::new("valueA", TypeRef::named_nn(interface.type_name()), |_| { -/// FieldFuture::new(async { -/// Ok(Some(FieldValue::with_type(FieldValue::NULL, "MyObjA"))) -/// }) -/// })) -/// .field(Field::new("valueB", TypeRef::named_nn(interface.type_name()), |_| { -/// FieldFuture::new(async { -/// Ok(Some(FieldValue::with_type(FieldValue::NULL, "MyObjB"))) -/// }) -/// })); -/// -/// # tokio::runtime::Runtime::new().unwrap().block_on(async move { -/// -/// let schema = Schema::build(query.type_name(), None, None) -/// .register(obj_a) -/// .register(obj_b) -/// .register(interface) -/// .register(query) -/// .finish()?; -/// -/// let query = r#" -/// fragment A on MyObjA { b } -/// -/// fragment B on MyObjB { c } -/// -/// { -/// valueA { a ...A ...B } -/// valueB { a ...A ...B } -/// } -/// "#; -/// -/// assert_eq!( -/// schema.execute(query).await.into_result().unwrap().data, -/// value!({ -/// "valueA": { -/// "a": 100, -/// "b": 200, -/// }, -/// "valueB": { -/// "a": 300, -/// "c": 400, -/// } -/// }) -/// ); -/// -/// # Ok::<_, SchemaError>(()) -/// # }).unwrap(); -/// ``` -#[derive(Debug)] -pub struct InterfaceField { - pub(crate) name: String, - pub(crate) description: Option, - pub(crate) arguments: IndexMap, - pub(crate) ty: TypeRef, - pub(crate) deprecation: Deprecation, - pub(crate) external: bool, - pub(crate) requires: Option, - pub(crate) provides: Option, - pub(crate) shareable: bool, - pub(crate) inaccessible: bool, - pub(crate) tags: Vec, - pub(crate) override_from: Option, - pub(crate) directives: Vec, - pub(crate) requires_scopes: Vec, -} - -impl InterfaceField { - /// Create a GraphQL interface field type - pub fn new(name: impl Into, ty: impl Into) -> Self { - Self { - name: name.into(), - description: None, - arguments: Default::default(), - ty: ty.into(), - deprecation: Deprecation::NoDeprecated, - external: false, - requires: None, - provides: None, - shareable: false, - inaccessible: false, - tags: Vec::new(), - override_from: None, - directives: Vec::new(), - requires_scopes: Vec::new(), - } - } - - impl_set_description!(); - impl_set_deprecation!(); - impl_set_external!(); - impl_set_requires!(); - impl_set_provides!(); - impl_set_shareable!(); - impl_set_inaccessible!(); - impl_set_tags!(); - impl_set_override_from!(); - impl_directive!(); - - /// Add an argument to the field - #[inline] - pub fn argument(mut self, input_value: InputValue) -> Self { - self.arguments.insert(input_value.name.clone(), input_value); - self - } -} - -/// A GraphQL interface type -#[derive(Debug)] -pub struct Interface { - pub(crate) name: String, - pub(crate) description: Option, - pub(crate) fields: IndexMap, - pub(crate) implements: IndexSet, - keys: Vec, - extends: bool, - inaccessible: bool, - tags: Vec, - pub(crate) directives: Vec, - requires_scopes: Vec, -} - -impl Interface { - /// Create a GraphQL interface type - #[inline] - pub fn new(name: impl Into) -> Self { - Self { - name: name.into(), - description: None, - fields: Default::default(), - implements: Default::default(), - keys: Vec::new(), - extends: false, - inaccessible: false, - tags: Vec::new(), - directives: Vec::new(), - requires_scopes: Vec::new(), - } - } - - impl_set_description!(); - impl_set_extends!(); - impl_set_inaccessible!(); - impl_set_tags!(); - impl_directive!(); - - /// Add a field to the interface type - #[inline] - pub fn field(mut self, field: InterfaceField) -> Self { - assert!( - !self.fields.contains_key(&field.name), - "Field `{}` already exists", - field.name - ); - self.fields.insert(field.name.clone(), field); - self - } - - /// Add an implement to the interface type - #[inline] - pub fn implement(mut self, interface: impl Into) -> Self { - let interface = interface.into(); - assert!( - !self.implements.contains(&interface), - "Implement `{}` already exists", - interface - ); - self.implements.insert(interface); - self - } - - /// Add an entity key - /// - /// See also: [`Object::key`](crate::dynamic::Object::key) - pub fn key(mut self, fields: impl Into) -> Self { - self.keys.push(fields.into()); - self - } - - /// Returns the type name - #[inline] - pub fn type_name(&self) -> &str { - &self.name - } - - #[inline] - pub(crate) fn is_entity(&self) -> bool { - !self.keys.is_empty() - } - - pub(crate) fn register(&self, registry: &mut Registry) -> Result<(), SchemaError> { - let mut fields = IndexMap::new(); - - for field in self.fields.values() { - let mut args = IndexMap::new(); - - for argument in field.arguments.values() { - args.insert(argument.name.clone(), argument.to_meta_input_value()); - } - - fields.insert( - field.name.clone(), - MetaField { - name: field.name.clone(), - description: field.description.clone(), - args, - ty: field.ty.to_string(), - deprecation: field.deprecation.clone(), - cache_control: Default::default(), - external: field.external, - requires: field.requires.clone(), - provides: field.provides.clone(), - visible: None, - shareable: field.shareable, - inaccessible: field.inaccessible, - tags: field.tags.clone(), - override_from: field.override_from.clone(), - compute_complexity: None, - directive_invocations: to_meta_directive_invocation(field.directives.clone()), - requires_scopes: field.requires_scopes.clone(), - }, - ); - } - - registry.types.insert( - self.name.clone(), - MetaType::Interface { - name: self.name.clone(), - description: self.description.clone(), - fields, - possible_types: Default::default(), - extends: self.extends, - keys: if !self.keys.is_empty() { - Some(self.keys.clone()) - } else { - None - }, - visible: None, - inaccessible: self.inaccessible, - tags: self.tags.clone(), - rust_typename: None, - directive_invocations: to_meta_directive_invocation(self.directives.clone()), - requires_scopes: self.requires_scopes.clone(), - }, - ); - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use async_graphql_parser::Pos; - - use crate::{PathSegment, ServerError, Value, dynamic::*, value}; - - #[tokio::test] - async fn basic_interface() { - let obj_a = Object::new("MyObjA") - .implement("MyInterface") - .field(Field::new("a", TypeRef::named(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(100))) }) - })) - .field(Field::new("b", TypeRef::named(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(200))) }) - })); - - let obj_b = Object::new("MyObjB") - .implement("MyInterface") - .field(Field::new("a", TypeRef::named(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(300))) }) - })) - .field(Field::new("c", TypeRef::named(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(400))) }) - })); - - let interface = Interface::new("MyInterface") - .field(InterfaceField::new("a", TypeRef::named(TypeRef::INT))); - - let query = Object::new("Query") - .field(Field::new( - "valueA", - TypeRef::named_nn(interface.type_name()), - |_| FieldFuture::new(async { Ok(Some(FieldValue::NULL.with_type("MyObjA"))) }), - )) - .field(Field::new( - "valueB", - TypeRef::named_nn(interface.type_name()), - |_| FieldFuture::new(async { Ok(Some(FieldValue::NULL.with_type("MyObjB"))) }), - )); - - let schema = Schema::build(query.type_name(), None, None) - .register(obj_a) - .register(obj_b) - .register(interface) - .register(query) - .finish() - .unwrap(); - - let query = r#" - fragment A on MyObjA { - b - } - - fragment B on MyObjB { - c - } - - { - valueA { __typename a ...A ...B } - valueB { __typename a ...A ...B } - } - "#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "valueA": { - "__typename": "MyObjA", - "a": 100, - "b": 200, - }, - "valueB": { - "__typename": "MyObjB", - "a": 300, - "c": 400, - } - }) - ); - } - - #[tokio::test] - async fn does_not_implement() { - let obj_a = Object::new("MyObjA") - .field(Field::new("a", TypeRef::named(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(100))) }) - })) - .field(Field::new("b", TypeRef::named(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(200))) }) - })); - - let interface = Interface::new("MyInterface") - .field(InterfaceField::new("a", TypeRef::named(TypeRef::INT))); - - let query = Object::new("Query").field(Field::new( - "valueA", - TypeRef::named_nn(interface.type_name()), - |_| FieldFuture::new(async { Ok(Some(FieldValue::NULL.with_type("MyObjA"))) }), - )); - - let schema = Schema::build(query.type_name(), None, None) - .register(obj_a) - .register(interface) - .register(query) - .finish() - .unwrap(); - - let query = r#" - { - valueA { a } - } - "#; - assert_eq!( - schema.execute(query).await.into_result().unwrap_err(), - vec![ServerError { - message: "internal: object \"MyObjA\" does not implement interface \"MyInterface\"" - .to_owned(), - source: None, - locations: vec![Pos { - column: 13, - line: 3 - }], - path: vec![PathSegment::Field("valueA".to_owned())], - extensions: None, - }] - ); - } - #[tokio::test] - async fn query_type_condition() { - struct MyObjA; - let obj_a = Object::new("MyObjA") - .implement("MyInterface") - .field(Field::new("a", TypeRef::named(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(100))) }) - })) - .field(Field::new("b", TypeRef::named(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(200))) }) - })); - let interface = Interface::new("MyInterface") - .field(InterfaceField::new("a", TypeRef::named(TypeRef::INT))); - let query = Object::new("Query"); - let query = query.field(Field::new( - "valueA", - TypeRef::named_nn(obj_a.type_name()), - |_| FieldFuture::new(async { Ok(Some(FieldValue::owned_any(MyObjA))) }), - )); - let schema = Schema::build(query.type_name(), None, None) - .register(obj_a) - .register(interface) - .register(query) - .finish() - .unwrap(); - let query = r#" - { - valueA { __typename - b - ... on MyInterface { a } } - } - "#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "valueA": { - "__typename": "MyObjA", - "b": 200, - "a": 100, - } - }) - ); - } -} diff --git a/src/dynamic/macros.rs b/src/dynamic/macros.rs deleted file mode 100644 index a6d44ca7e..000000000 --- a/src/dynamic/macros.rs +++ /dev/null @@ -1,175 +0,0 @@ -macro_rules! impl_set_description { - () => { - /// Set the description - #[inline] - pub fn description(self, description: impl Into) -> Self { - Self { - description: Some(description.into()), - ..self - } - } - }; -} - -macro_rules! impl_set_deprecation { - () => { - /// Set the description - #[inline] - pub fn deprecation(self, reason: Option<&str>) -> Self { - Self { - deprecation: Deprecation::Deprecated { - reason: reason.map(Into::into), - }, - ..self - } - } - }; -} - -macro_rules! impl_set_extends { - () => { - /// Indicates that an object or interface definition is an extension of another - /// definition of that same type. - #[inline] - pub fn extends(self) -> Self { - Self { - extends: true, - ..self - } - } - }; -} - -macro_rules! impl_set_inaccessible { - () => { - /// Indicate that an enum is not accessible from a supergraph when using - /// Apollo Federation - /// - /// Reference: - #[inline] - pub fn inaccessible(self) -> Self { - Self { - inaccessible: true, - ..self - } - } - }; -} - -macro_rules! impl_set_interface_object { - () => { - /// During composition, the fields of every `@interfaceObject` are added - /// both to their corresponding interface definition and to all - /// entity types that implement that interface. - /// - /// Reference: - #[inline] - pub fn interface_object(self) -> Self { - Self { - interface_object: true, - ..self - } - } - }; -} - -macro_rules! impl_set_tags { - () => { - /// Arbitrary string metadata that will be propagated to the supergraph - /// when using Apollo Federation. This attribute is repeatable - /// - /// Reference: - #[inline] - pub fn tags, T: Into>(self, tags: I) -> Self { - Self { - tags: tags.into_iter().map(Into::into).collect(), - ..self - } - } - }; -} - -macro_rules! impl_set_external { - () => { - /// Mark a field as owned by another service. This allows service A to use - /// fields from service B while also knowing at runtime the types of that - /// field. - #[inline] - pub fn external(self) -> Self { - Self { - external: true, - ..self - } - } - }; -} - -macro_rules! impl_set_requires { - () => { - /// Annotate the required input fieldset from a base type for a resolver. It - /// is used to develop a query plan where the required fields may not be - /// needed by the client, but the service may need additional information - /// from other services. - #[inline] - pub fn requires(self, fields: impl Into) -> Self { - Self { - requires: Some(fields.into()), - ..self - } - } - }; -} - -macro_rules! impl_set_provides { - () => { - /// Annotate the expected returned fieldset from a field on a base type that - /// is guaranteed to be selectable by the gateway. - #[inline] - pub fn provides(self, fields: impl Into) -> Self { - Self { - provides: Some(fields.into()), - ..self - } - } - }; -} - -macro_rules! impl_set_shareable { - () => { - /// Indicate that an object type's field is allowed to be resolved by - /// multiple subgraphs - #[inline] - pub fn shareable(self) -> Self { - Self { - shareable: true, - ..self - } - } - }; -} - -macro_rules! impl_set_override_from { - () => { - /// Indicate that an object type's field is allowed to be resolved by - /// multiple subgraphs - #[inline] - pub fn override_from(self, name: impl Into) -> Self { - Self { - override_from: Some(name.into()), - ..self - } - } - }; -} - -macro_rules! impl_directive { - () => { - /// Attach directive to the entity - #[inline] - pub fn directive(mut self, directive: Directive) -> Self { - self.directives.push(directive); - - self - } - }; -} diff --git a/src/dynamic/mod.rs b/src/dynamic/mod.rs deleted file mode 100644 index c3211a52f..000000000 --- a/src/dynamic/mod.rs +++ /dev/null @@ -1,71 +0,0 @@ -//! Support for dynamic schema -//! -//! # Create a simple GraphQL schema -//! -//! ``` -//! use async_graphql::{dynamic::*, value, Value}; -//! -//! let query = Object::new("Query").field(Field::new("value", TypeRef::named_nn(TypeRef::INT), |ctx| { -//! FieldFuture::new(async move { Ok(Some(Value::from(100))) }) -//! })); -//! -//! # tokio::runtime::Runtime::new().unwrap().block_on(async move { -//! -//! let schema = Schema::build(query.type_name(), None, None) -//! .register(query) -//! .finish()?; -//! -//! assert_eq!( -//! schema -//! .execute("{ value }") -//! .await -//! .into_result() -//! .unwrap() -//! .data, -//! value!({ "value": 100 }) -//! ); -//! -//! # Ok::<_, SchemaError>(()) -//! # }).unwrap(); -//! ``` - -#[macro_use] -mod macros; - -mod base; -mod check; -mod directive; -mod r#enum; -mod error; -mod field; -mod input_object; -mod input_value; -mod interface; -mod object; -mod request; -mod resolve; -mod scalar; -mod schema; -mod subscription; -mod r#type; -mod type_ref; -mod union; -mod value_accessor; - -pub use directive::Directive; -pub use r#enum::{Enum, EnumItem}; -pub use error::SchemaError; -pub use field::{Field, FieldFuture, FieldValue, ResolverContext}; -pub use indexmap; -pub use input_object::InputObject; -pub use input_value::InputValue; -pub use interface::{Interface, InterfaceField}; -pub use object::Object; -pub use request::{DynamicRequest, DynamicRequestExt}; -pub use scalar::Scalar; -pub use schema::{Schema, SchemaBuilder}; -pub use subscription::{Subscription, SubscriptionField, SubscriptionFieldFuture}; -pub use r#type::Type; -pub use type_ref::TypeRef; -pub use union::Union; -pub use value_accessor::{ListAccessor, ObjectAccessor, ValueAccessor}; diff --git a/src/dynamic/object.rs b/src/dynamic/object.rs deleted file mode 100644 index 7dd3b1847..000000000 --- a/src/dynamic/object.rs +++ /dev/null @@ -1,290 +0,0 @@ -use indexmap::{IndexMap, IndexSet}; - -use super::{Directive, directive::to_meta_directive_invocation}; -use crate::{ - dynamic::{Field, SchemaError}, - registry::{MetaField, MetaType, Registry}, -}; - -/// A GraphQL object type -/// -/// # Examples -/// -/// ``` -/// use async_graphql::{dynamic::*, value, Value}; -/// -/// let query = Object::new("Query").field(Field::new("value", TypeRef::named_nn(TypeRef::STRING), |ctx| { -/// FieldFuture::new(async move { Ok(Some(Value::from("abc"))) }) -/// })); -/// -/// # tokio::runtime::Runtime::new().unwrap().block_on(async move { -/// -/// let schema = Schema::build(query.type_name(), None, None) -/// .register(query) -/// .finish()?; -/// -/// assert_eq!( -/// schema -/// .execute("{ value }") -/// .await -/// .into_result() -/// .unwrap() -/// .data, -/// value!({ "value": "abc" }) -/// ); -/// -/// # Ok::<_, SchemaError>(()) -/// # }).unwrap(); -/// ``` -#[derive(Debug)] -pub struct Object { - pub(crate) name: String, - pub(crate) description: Option, - pub(crate) fields: IndexMap, - pub(crate) implements: IndexSet, - keys: Vec, - extends: bool, - shareable: bool, - resolvable: bool, - inaccessible: bool, - interface_object: bool, - tags: Vec, - pub(crate) directives: Vec, - requires_scopes: Vec, -} - -impl Object { - /// Create a GraphQL object type - #[inline] - pub fn new(name: impl Into) -> Self { - Self { - name: name.into(), - description: None, - fields: Default::default(), - implements: Default::default(), - keys: Vec::new(), - extends: false, - shareable: false, - resolvable: true, - inaccessible: false, - interface_object: false, - tags: Vec::new(), - directives: Vec::new(), - requires_scopes: Vec::new(), - } - } - - impl_set_description!(); - impl_set_extends!(); - impl_set_shareable!(); - impl_set_inaccessible!(); - impl_set_interface_object!(); - impl_set_tags!(); - impl_directive!(); - - /// Add an field to the object - #[inline] - pub fn field(mut self, field: Field) -> Self { - assert!( - !self.fields.contains_key(&field.name), - "Field `{}` already exists", - field.name - ); - self.fields.insert(field.name.clone(), field); - self - } - - /// Add an implement to the object - #[inline] - pub fn implement(mut self, interface: impl Into) -> Self { - let interface = interface.into(); - assert!( - !self.implements.contains(&interface), - "Implement `{}` already exists", - interface - ); - self.implements.insert(interface); - self - } - - /// Add an entity key - /// - /// # Examples - /// - /// ``` - /// use async_graphql::{Value, dynamic::*}; - /// - /// let obj = Object::new("MyObj") - /// .field(Field::new("a", TypeRef::named(TypeRef::INT), |_| { - /// FieldFuture::new(async move { Ok(Some(Value::from(10))) }) - /// })) - /// .field(Field::new("b", TypeRef::named(TypeRef::INT), |_| { - /// FieldFuture::new(async move { Ok(Some(Value::from(20))) }) - /// })) - /// .field(Field::new("c", TypeRef::named(TypeRef::INT), |_| { - /// FieldFuture::new(async move { Ok(Some(Value::from(30))) }) - /// })) - /// .key("a b") - /// .key("c"); - /// ``` - pub fn key(mut self, fields: impl Into) -> Self { - self.keys.push(fields.into()); - self - } - - /// Make the entity unresolvable by the current subgraph - /// - /// Most commonly used to reference an entity without contributing fields. - /// - /// # Examples - /// - /// ``` - /// use async_graphql::{Value, dynamic::*}; - /// - /// let obj = Object::new("MyObj") - /// .field(Field::new("a", TypeRef::named(TypeRef::INT), |_| { - /// FieldFuture::new(async move { Ok(Some(Value::from(10))) }) - /// })) - /// .unresolvable("a"); - /// ``` - /// - /// This references the `MyObj` entity with the key `a` that cannot be - /// resolved by the current subgraph. - pub fn unresolvable(mut self, fields: impl Into) -> Self { - self.resolvable = false; - self.keys.push(fields.into()); - self - } - - /// Returns the type name - #[inline] - pub fn type_name(&self) -> &str { - &self.name - } - - pub(crate) fn register(&self, registry: &mut Registry) -> Result<(), SchemaError> { - let mut fields = IndexMap::new(); - - for field in self.fields.values() { - let mut args = IndexMap::new(); - - for argument in field.arguments.values() { - args.insert(argument.name.clone(), argument.to_meta_input_value()); - } - - fields.insert( - field.name.clone(), - MetaField { - name: field.name.clone(), - description: field.description.clone(), - args, - ty: field.ty.to_string(), - deprecation: field.deprecation.clone(), - cache_control: Default::default(), - external: field.external, - requires: field.requires.clone(), - provides: field.provides.clone(), - visible: None, - shareable: field.shareable, - inaccessible: field.inaccessible, - tags: field.tags.clone(), - override_from: field.override_from.clone(), - compute_complexity: None, - directive_invocations: to_meta_directive_invocation(field.directives.clone()), - requires_scopes: field.requires_scopes.clone(), - }, - ); - } - - registry.types.insert( - self.name.clone(), - MetaType::Object { - name: self.name.clone(), - description: self.description.clone(), - fields, - cache_control: Default::default(), - extends: self.extends, - shareable: self.shareable, - resolvable: self.resolvable, - keys: if !self.keys.is_empty() { - Some(self.keys.clone()) - } else { - None - }, - visible: None, - inaccessible: self.inaccessible, - interface_object: self.interface_object, - tags: self.tags.clone(), - is_subscription: false, - rust_typename: None, - directive_invocations: to_meta_directive_invocation(self.directives.clone()), - requires_scopes: self.requires_scopes.clone(), - }, - ); - - for interface in &self.implements { - registry.add_implements(&self.name, interface); - } - - Ok(()) - } - - #[inline] - pub(crate) fn is_entity(&self) -> bool { - !self.keys.is_empty() - } -} - -#[cfg(test)] -mod tests { - use crate::{Value, dynamic::*, value}; - - #[tokio::test] - async fn borrow_context() { - struct MyObjData { - value: i32, - } - - let my_obj = - Object::new("MyObj").field(Field::new("value", TypeRef::named(TypeRef::INT), |ctx| { - FieldFuture::new(async move { - Ok(Some(Value::from( - ctx.parent_value.try_downcast_ref::()?.value, - ))) - }) - })); - - let query = Object::new("Query").field(Field::new( - "obj", - TypeRef::named_nn(my_obj.type_name()), - |ctx| { - FieldFuture::new(async move { - Ok(Some(FieldValue::borrowed_any( - ctx.data_unchecked::(), - ))) - }) - }, - )); - - let schema = Schema::build("Query", None, None) - .register(query) - .register(my_obj) - .data(MyObjData { value: 123 }) - .finish() - .unwrap(); - - assert_eq!( - schema - .execute("{ obj { value } }") - .await - .into_result() - .unwrap() - .data, - value!({ - "obj": { - "value": 123, - } - }) - ); - } -} diff --git a/src/dynamic/request.rs b/src/dynamic/request.rs deleted file mode 100644 index 035c77ca9..000000000 --- a/src/dynamic/request.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::{Request, dynamic::FieldValue}; - -/// GraphQL request for dynamic schema. -pub struct DynamicRequest { - pub(crate) inner: Request, - pub(crate) root_value: FieldValue<'static>, -} - -/// A trait for [`DynamicRequest`] -pub trait DynamicRequestExt { - /// Specify the root value for the request - fn root_value(self, value: FieldValue<'static>) -> DynamicRequest; -} - -impl> DynamicRequestExt for T { - fn root_value(self, value: FieldValue<'static>) -> DynamicRequest { - DynamicRequest { - inner: self.into(), - root_value: value, - } - } -} - -impl> From for DynamicRequest { - fn from(req: T) -> Self { - Self { - inner: req.into(), - root_value: FieldValue::NULL, - } - } -} diff --git a/src/dynamic/resolve.rs b/src/dynamic/resolve.rs deleted file mode 100644 index 027d54cdc..000000000 --- a/src/dynamic/resolve.rs +++ /dev/null @@ -1,664 +0,0 @@ -use std::{borrow::Cow, pin::Pin}; - -use async_graphql_derive::SimpleObject; -use async_graphql_parser::{Positioned, types::Field}; -use futures_util::{Future, FutureExt, future::BoxFuture}; -use indexmap::IndexMap; - -use crate::{ - Context, ContextSelectionSet, Error, IntrospectionMode, Name, SDLExportOptions, ServerError, - ServerResult, Value, - dynamic::{ - FieldFuture, FieldValue, Object, ObjectAccessor, ResolverContext, Schema, Type, TypeRef, - field::FieldValueInner, - }, - extensions::ResolveInfo, - parser::types::Selection, - resolver_utils::create_value_object, -}; - -/// Federation service -#[derive(SimpleObject)] -#[graphql(internal, name = "_Service")] -struct Service { - sdl: Option, -} - -type BoxFieldFuture<'a> = Pin> + 'a + Send>>; - -pub(crate) async fn resolve_container( - schema: &Schema, - object: &Object, - ctx: &ContextSelectionSet<'_>, - parent_value: &FieldValue<'_>, - serial: bool, -) -> ServerResult> { - let mut fields = Vec::new(); - collect_fields(&mut fields, schema, object, ctx, parent_value)?; - - let res = if !serial { - futures_util::future::try_join_all(fields).await? - } else { - let mut results = Vec::with_capacity(fields.len()); - for field in fields { - results.push(field.await?); - } - results - }; - - Ok(Some(create_value_object(res))) -} - -fn collect_typename_field<'a>( - fields: &mut Vec>, - object: &'a Object, - field: &'a Positioned, -) { - fields.push( - async move { - Ok(( - field.node.response_key().node.clone(), - Value::from(object.name.as_str()), - )) - } - .boxed(), - ) -} - -fn collect_schema_field<'a>( - fields: &mut Vec>, - ctx: &ContextSelectionSet<'a>, - field: &'a Positioned, -) { - let ctx = ctx.clone(); - fields.push( - async move { - let ctx_field = ctx.with_field(field); - let mut ctx_obj = ctx.with_selection_set(&ctx_field.item.node.selection_set); - ctx_obj.is_for_introspection = true; - let visible_types = ctx.schema_env.registry.find_visible_types(&ctx_field); - let value = crate::OutputType::resolve( - &crate::model::__Schema::new(&ctx.schema_env.registry, &visible_types), - &ctx_obj, - ctx_field.item, - ) - .await?; - Ok((field.node.response_key().node.clone(), value)) - } - .boxed(), - ); -} - -fn collect_type_field<'a>( - fields: &mut Vec>, - ctx: &ContextSelectionSet<'a>, - field: &'a Positioned, -) { - let ctx = ctx.clone(); - fields.push( - async move { - let ctx_field = ctx.with_field(field); - let (_, type_name) = ctx_field.param_value::("name", None)?; - let mut ctx_obj = ctx.with_selection_set(&ctx_field.item.node.selection_set); - ctx_obj.is_for_introspection = true; - let visible_types = ctx.schema_env.registry.find_visible_types(&ctx_field); - let value = crate::OutputType::resolve( - &ctx.schema_env - .registry - .types - .get(&type_name) - .filter(|_| visible_types.contains(type_name.as_str())) - .map(|ty| { - crate::model::__Type::new_simple( - &ctx.schema_env.registry, - &visible_types, - ty, - ) - }), - &ctx_obj, - ctx_field.item, - ) - .await?; - Ok((field.node.response_key().node.clone(), value)) - } - .boxed(), - ); -} - -fn collect_service_field<'a>( - fields: &mut Vec>, - ctx: &ContextSelectionSet<'a>, - field: &'a Positioned, -) { - let ctx = ctx.clone(); - fields.push( - async move { - let ctx_field = ctx.with_field(field); - let mut ctx_obj = ctx.with_selection_set(&ctx_field.item.node.selection_set); - ctx_obj.is_for_introspection = true; - - let output_type = crate::OutputType::resolve( - &Service { - sdl: Some( - ctx.schema_env - .registry - .export_sdl(SDLExportOptions::new().federation().compose_directive()), - ), - }, - &ctx_obj, - ctx_field.item, - ) - .await?; - - Ok((field.node.response_key().node.clone(), output_type)) - } - .boxed(), - ); -} - -fn collect_entities_field<'a>( - fields: &mut Vec>, - schema: &'a Schema, - ctx: &ContextSelectionSet<'a>, - parent_value: &'a FieldValue, - field: &'a Positioned, -) { - let ctx = ctx.clone(); - fields.push( - async move { - let ctx_field = ctx.with_field(field); - let entity_resolver = schema.0.entity_resolver.as_ref().ok_or_else(|| { - ctx_field.set_error_path( - Error::new("internal: missing entity resolver") - .into_server_error(ctx_field.item.pos), - ) - })?; - let entity_type = TypeRef::named_list_nn("_Entity"); - - let arguments = ObjectAccessor(Cow::Owned( - field - .node - .arguments - .iter() - .map(|(name, value)| { - ctx_field - .resolve_input_value(value.clone()) - .map(|value| (name.node.clone(), value)) - }) - .collect::>>()?, - )); - - let field_future = (entity_resolver)(ResolverContext { - ctx: &ctx_field, - args: arguments, - parent_value, - }); - - let field_value = match field_future { - FieldFuture::Future(fut) => { - fut.await.map_err(|err| err.into_server_error(field.pos))? - } - FieldFuture::Value(value) => value, - }; - let value = resolve(schema, &ctx_field, &entity_type, field_value.as_ref()) - .await? - .unwrap_or_default(); - Ok((field.node.response_key().node.clone(), value)) - } - .boxed(), - ); -} - -fn collect_field<'a>( - fields: &mut Vec>, - schema: &'a Schema, - object: &'a Object, - ctx: &ContextSelectionSet<'a>, - parent_value: &'a FieldValue, - field_def: &'a crate::dynamic::Field, - field: &'a Positioned, -) { - let ctx = ctx.clone(); - fields.push( - async move { - let ctx_field = ctx.with_field(field); - let arguments = ObjectAccessor(Cow::Owned({ - let mut args = field - .node - .arguments - .iter() - .map(|(name, value)| { - ctx_field - .resolve_input_value(value.clone()) - .map(|value| (name.node.clone(), value)) - }) - .collect::>>()?; - field_def.arguments.iter().for_each(|(name, arg)| { - if let Some(def) = &arg.default_value { - if !args.contains_key(name.as_str()) { - args.insert(Name::new(name), def.clone()); - } - } - }); - args - })); - - let resolve_info = ResolveInfo { - path_node: ctx_field.path_node.as_ref().unwrap(), - parent_type: &object.name, - return_type: &field_def.ty_str, - name: &field.node.name.node, - alias: field.node.alias.as_ref().map(|alias| &*alias.node), - is_for_introspection: ctx_field.is_for_introspection, - field: &field.node, - }; - let resolve_fut = async { - let field_future = (field_def.resolver_fn)(ResolverContext { - ctx: &ctx_field, - args: arguments, - parent_value, - }); - - let field_value = match field_future { - FieldFuture::Value(field_value) => field_value, - FieldFuture::Future(future) => future - .await - .map_err(|err| err.into_server_error(field.pos))?, - }; - - let value = - resolve(schema, &ctx_field, &field_def.ty, field_value.as_ref()).await?; - - Ok(value) - }; - futures_util::pin_mut!(resolve_fut); - - let res_value = ctx_field - .query_env - .extensions - .resolve(resolve_info, &mut resolve_fut) - .await? - .unwrap_or_default(); - Ok((field.node.response_key().node.clone(), res_value)) - } - .boxed(), - ); -} - -fn collect_fields<'a>( - fields: &mut Vec>, - schema: &'a Schema, - object: &'a Object, - ctx: &ContextSelectionSet<'a>, - parent_value: &'a FieldValue, -) -> ServerResult<()> { - for selection in &ctx.item.node.items { - match &selection.node { - Selection::Field(field) => { - if field.node.name.node == "__typename" { - collect_typename_field(fields, object, field); - continue; - } - - if object.name == schema.0.env.registry.query_type - && matches!( - ctx.schema_env.registry.introspection_mode, - IntrospectionMode::Enabled | IntrospectionMode::IntrospectionOnly - ) - && matches!( - ctx.query_env.introspection_mode, - IntrospectionMode::Enabled | IntrospectionMode::IntrospectionOnly, - ) - { - // is query root - if field.node.name.node == "__schema" { - collect_schema_field(fields, ctx, field); - continue; - } else if field.node.name.node == "__type" { - collect_type_field(fields, ctx, field); - continue; - } else if ctx.schema_env.registry.enable_federation - && field.node.name.node == "_service" - { - collect_service_field(fields, ctx, field); - continue; - } else if ctx.schema_env.registry.enable_federation - && field.node.name.node == "_entities" - { - collect_entities_field(fields, schema, ctx, parent_value, field); - continue; - } - } - - if ctx.schema_env.registry.introspection_mode - == IntrospectionMode::IntrospectionOnly - || ctx.query_env.introspection_mode == IntrospectionMode::IntrospectionOnly - { - fields.push( - async move { Ok((field.node.response_key().node.clone(), Value::Null)) } - .boxed(), - ); - continue; - } - - if let Some(field_def) = object.fields.get(field.node.name.node.as_str()) { - collect_field(fields, schema, object, ctx, parent_value, field_def, field); - } - } - selection => { - let (type_condition, selection_set) = match selection { - Selection::Field(_) => unreachable!(), - Selection::FragmentSpread(spread) => { - let fragment = ctx.query_env.fragments.get(&spread.node.fragment_name.node); - let fragment = match fragment { - Some(fragment) => fragment, - None => { - return Err(ServerError::new( - format!( - "Unknown fragment \"{}\".", - spread.node.fragment_name.node - ), - Some(spread.pos), - )); - } - }; - ( - Some(&fragment.node.type_condition), - &fragment.node.selection_set, - ) - } - Selection::InlineFragment(fragment) => ( - fragment.node.type_condition.as_ref(), - &fragment.node.selection_set, - ), - }; - - let type_condition = - type_condition.map(|condition| condition.node.on.node.as_str()); - let introspection_type_name = &object.name; - - let type_condition_matched = match type_condition { - None => true, - Some(type_condition) if type_condition == introspection_type_name => true, - Some(type_condition) if object.implements.contains(type_condition) => true, - _ => false, - }; - if type_condition_matched { - collect_fields( - fields, - schema, - object, - &ctx.with_selection_set(selection_set), - parent_value, - )?; - } - } - } - } - - Ok(()) -} - -pub(crate) fn resolve<'a>( - schema: &'a Schema, - ctx: &'a Context<'a>, - type_ref: &'a TypeRef, - value: Option<&'a FieldValue>, -) -> BoxFuture<'a, ServerResult>> { - async move { - match (type_ref, value) { - (TypeRef::Named(type_name), Some(value)) => { - resolve_value(schema, ctx, &schema.0.types[type_name.as_ref()], value).await - } - (TypeRef::Named(_), None) => Ok(None), - - (TypeRef::NonNull(type_ref), Some(value)) => { - resolve(schema, ctx, type_ref, Some(value)).await - } - (TypeRef::NonNull(_), None) => Err(ctx.set_error_path( - Error::new("internal: non-null types require a return value") - .into_server_error(ctx.item.pos), - )), - - (TypeRef::List(type_ref), Some(FieldValue(FieldValueInner::List(values)))) => { - resolve_list(schema, ctx, type_ref, values).await - } - ( - TypeRef::List(type_ref), - Some(FieldValue(FieldValueInner::Value(Value::List(values)))), - ) => { - let values = values - .iter() - .cloned() - .map(FieldValue::value) - .collect::>(); - resolve_list(schema, ctx, type_ref, &values).await - } - (TypeRef::List(_), Some(_)) => Err(ctx.set_error_path( - Error::new("internal: expects an array").into_server_error(ctx.item.pos), - )), - (TypeRef::List(_), None) => Ok(None), - } - } - .boxed() -} - -async fn resolve_list<'a>( - schema: &'a Schema, - ctx: &'a Context<'a>, - type_ref: &'a TypeRef, - values: &[FieldValue<'_>], -) -> ServerResult> { - let mut futures = Vec::with_capacity(values.len()); - for (idx, value) in values.iter().enumerate() { - let ctx_item = ctx.with_index(idx); - - futures.push(async move { - let parent_type = format!("[{}]", type_ref); - let return_type = type_ref.to_string(); - let resolve_info = ResolveInfo { - path_node: ctx_item.path_node.as_ref().unwrap(), - parent_type: &parent_type, - return_type: &return_type, - name: ctx.item.node.name.node.as_str(), - alias: ctx - .item - .node - .alias - .as_ref() - .map(|alias| alias.node.as_str()), - is_for_introspection: ctx_item.is_for_introspection, - field: &ctx_item.item.node, - }; - - let resolve_fut = async { resolve(schema, &ctx_item, type_ref, Some(value)).await }; - futures_util::pin_mut!(resolve_fut); - - let res_value = ctx_item - .query_env - .extensions - .resolve(resolve_info, &mut resolve_fut) - .await?; - Ok::<_, ServerError>(res_value.unwrap_or_default()) - }); - } - let values = futures_util::future::try_join_all(futures).await?; - Ok(Some(Value::List(values))) -} - -async fn resolve_value( - schema: &Schema, - ctx: &Context<'_>, - field_type: &Type, - value: &FieldValue<'_>, -) -> ServerResult> { - match (field_type, &value.0) { - (Type::Scalar(scalar), FieldValueInner::Value(value)) if scalar.validate(value) => { - Ok(Some(value.clone())) - } - (Type::Scalar(scalar), _) => Err(ctx.set_error_path( - Error::new(format!( - "internal: invalid value for scalar \"{}\", expected \"FieldValue::Value\"", - scalar.name - )) - .into_server_error(ctx.item.pos), - )), - - (Type::Object(object), _) => { - resolve_container( - schema, - object, - &ctx.with_selection_set(&ctx.item.node.selection_set), - value, - true, - ) - .await - } - - (Type::InputObject(obj), _) => Err(ctx.set_error_path( - Error::new(format!( - "internal: cannot use input object \"{}\" as output value", - obj.name - )) - .into_server_error(ctx.item.pos), - )), - - (Type::Enum(e), FieldValueInner::Value(Value::Enum(name))) => { - if !e.enum_values.contains_key(name.as_str()) { - return Err(ctx.set_error_path( - Error::new(format!("internal: invalid item for enum \"{}\"", e.name)) - .into_server_error(ctx.item.pos), - )); - } - Ok(Some(Value::Enum(name.clone()))) - } - (Type::Enum(e), FieldValueInner::Value(Value::String(name))) => { - if !e.enum_values.contains_key(name) { - return Err(ctx.set_error_path( - Error::new(format!("internal: invalid item for enum \"{}\"", e.name)) - .into_server_error(ctx.item.pos), - )); - } - Ok(Some(Value::Enum(Name::new(name)))) - } - (Type::Enum(e), _) => Err(ctx.set_error_path( - Error::new(format!("internal: invalid item for enum \"{}\"", e.name)) - .into_server_error(ctx.item.pos), - )), - - (Type::Interface(interface), FieldValueInner::WithType { value, ty }) => { - let is_contains_obj = schema - .0 - .env - .registry - .types - .get(&interface.name) - .and_then(|meta_type| { - meta_type - .possible_types() - .map(|possible_types| possible_types.contains(ty.as_ref())) - }) - .unwrap_or_default(); - if !is_contains_obj { - return Err(ctx.set_error_path( - Error::new(format!( - "internal: object \"{}\" does not implement interface \"{}\"", - ty, interface.name, - )) - .into_server_error(ctx.item.pos), - )); - } - - let object_type = schema - .0 - .types - .get(ty.as_ref()) - .ok_or_else(|| { - ctx.set_error_path( - Error::new(format!("internal: object \"{}\" does not registered", ty)) - .into_server_error(ctx.item.pos), - ) - })? - .as_object() - .ok_or_else(|| { - ctx.set_error_path( - Error::new(format!("internal: type \"{}\" is not object", ty)) - .into_server_error(ctx.item.pos), - ) - })?; - - resolve_container( - schema, - object_type, - &ctx.with_selection_set(&ctx.item.node.selection_set), - value, - true, - ) - .await - } - (Type::Interface(interface), _) => Err(ctx.set_error_path( - Error::new(format!( - "internal: invalid value for interface \"{}\", expected \"FieldValue::WithType\"", - interface.name - )) - .into_server_error(ctx.item.pos), - )), - - (Type::Union(union), FieldValueInner::WithType { value, ty }) => { - if !union.possible_types.contains(ty.as_ref()) { - return Err(ctx.set_error_path( - Error::new(format!( - "internal: union \"{}\" does not contain object \"{}\"", - union.name, ty, - )) - .into_server_error(ctx.item.pos), - )); - } - - let object_type = schema - .0 - .types - .get(ty.as_ref()) - .ok_or_else(|| { - ctx.set_error_path( - Error::new(format!("internal: object \"{}\" does not registered", ty)) - .into_server_error(ctx.item.pos), - ) - })? - .as_object() - .ok_or_else(|| { - ctx.set_error_path( - Error::new(format!("internal: type \"{}\" is not object", ty)) - .into_server_error(ctx.item.pos), - ) - })?; - - resolve_container( - schema, - object_type, - &ctx.with_selection_set(&ctx.item.node.selection_set), - value, - true, - ) - .await - } - (Type::Union(union), _) => Err(ctx.set_error_path( - Error::new(format!( - "internal: invalid value for union \"{}\", expected \"FieldValue::WithType\"", - union.name - )) - .into_server_error(ctx.item.pos), - )), - (Type::Subscription(subscription), _) => Err(ctx.set_error_path( - Error::new(format!( - "internal: cannot use subscription \"{}\" as output value", - subscription.name - )) - .into_server_error(ctx.item.pos), - )), - (Type::Upload, _) => Err(ctx.set_error_path( - Error::new("internal: cannot use upload as output value") - .into_server_error(ctx.item.pos), - )), - } -} diff --git a/src/dynamic/scalar.rs b/src/dynamic/scalar.rs deleted file mode 100644 index 877a63c2b..000000000 --- a/src/dynamic/scalar.rs +++ /dev/null @@ -1,213 +0,0 @@ -use std::{ - fmt::{self, Debug}, - sync::Arc, -}; - -use super::{Directive, directive::to_meta_directive_invocation}; -use crate::{ - Value, - dynamic::SchemaError, - registry::{MetaType, Registry, ScalarValidatorFn}, -}; - -/// A GraphQL scalar type -/// -/// # Examples -/// -/// ``` -/// use async_graphql::{dynamic::*, value, Value}; -/// -/// let my_scalar = Scalar::new("MyScalar"); -/// -/// let query = Object::new("Query").field(Field::new("value", TypeRef::named_nn(my_scalar.type_name()), |ctx| { -/// FieldFuture::new(async move { Ok(Some(Value::from("abc"))) }) -/// })); -/// -/// # tokio::runtime::Runtime::new().unwrap().block_on(async move { -/// -/// let schema = Schema::build(query.type_name(), None, None) -/// .register(my_scalar) -/// .register(query) -/// .finish()?; -/// -/// assert_eq!( -/// schema -/// .execute("{ value }") -/// .await -/// .into_result() -/// .unwrap() -/// .data, -/// value!({ "value": "abc" }) -/// ); -/// -/// # Ok::<_, SchemaError>(()) -/// # }).unwrap(); -/// ``` -pub struct Scalar { - pub(crate) name: String, - pub(crate) description: Option, - pub(crate) specified_by_url: Option, - pub(crate) validator: Option, - inaccessible: bool, - tags: Vec, - pub(crate) directives: Vec, - requires_scopes: Vec, -} - -impl Debug for Scalar { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Scalar") - .field("name", &self.name) - .field("description", &self.description) - .field("specified_by_url", &self.specified_by_url) - .field("inaccessible", &self.inaccessible) - .field("tags", &self.tags) - .field("requires_scopes", &self.requires_scopes) - .finish() - } -} - -impl Scalar { - /// Create a GraphQL scalar type - #[inline] - pub fn new(name: impl Into) -> Self { - Self { - name: name.into(), - description: None, - specified_by_url: None, - validator: None, - inaccessible: false, - tags: Vec::new(), - directives: Vec::new(), - requires_scopes: Vec::new(), - } - } - - impl_set_description!(); - impl_set_inaccessible!(); - impl_set_tags!(); - impl_directive!(); - - /// Set the validator - #[inline] - pub fn validator(self, validator: impl Fn(&Value) -> bool + Send + Sync + 'static) -> Self { - Self { - validator: Some(Arc::new(validator)), - ..self - } - } - - #[inline] - pub(crate) fn validate(&self, value: &Value) -> bool { - match &self.validator { - Some(validator) => (validator)(value), - None => true, - } - } - - /// Set the specified by url - #[inline] - pub fn specified_by_url(self, specified_by_url: impl Into) -> Self { - Self { - specified_by_url: Some(specified_by_url.into()), - ..self - } - } - - /// Returns the type name - #[inline] - pub fn type_name(&self) -> &str { - &self.name - } - - pub(crate) fn register(&self, registry: &mut Registry) -> Result<(), SchemaError> { - registry.types.insert( - self.name.clone(), - MetaType::Scalar { - name: self.name.clone(), - description: self.description.clone(), - is_valid: self.validator.clone(), - visible: None, - inaccessible: self.inaccessible, - tags: self.tags.clone(), - specified_by_url: self.specified_by_url.clone(), - directive_invocations: to_meta_directive_invocation(self.directives.clone()), - requires_scopes: self.requires_scopes.clone(), - }, - ); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use async_graphql_parser::Pos; - - use crate::{PathSegment, ServerError, dynamic::*, value}; - - #[tokio::test] - async fn custom_scalar() { - let scalar = Scalar::new("MyScalar"); - let query = Object::new("Query").field(Field::new( - "value", - TypeRef::named_nn(scalar.type_name()), - |_| { - FieldFuture::new(async move { - Ok(Some(value!({ - "a": 1, - "b": "abc", - }))) - }) - }, - )); - - let schema = Schema::build(query.type_name(), None, None) - .register(query) - .register(scalar) - .finish() - .unwrap(); - - assert_eq!( - schema - .execute("{ value }") - .await - .into_result() - .unwrap() - .data, - value!({ - "value": { - "a": 1, - "b": "abc", - } - }) - ); - } - - #[tokio::test] - async fn invalid_scalar_value() { - let scalar = Scalar::new("MyScalar"); - let query = Object::new("Query").field(Field::new( - "value", - TypeRef::named_nn(scalar.type_name()), - |_| FieldFuture::new(async move { Ok(Some(FieldValue::owned_any(10i32))) }), - )); - - let schema = Schema::build(query.type_name(), None, None) - .register(query) - .register(scalar) - .finish() - .unwrap(); - - assert_eq!( - schema.execute("{ value }").await.into_result().unwrap_err(), - vec![ServerError { - message: "internal: invalid value for scalar \"MyScalar\", expected \"FieldValue::Value\"" - .to_owned(), - source: None, - locations: vec![Pos { column: 3, line: 1 }], - path: vec![PathSegment::Field("value".to_owned())], - extensions: None, - }] - ); - } -} diff --git a/src/dynamic/schema.rs b/src/dynamic/schema.rs deleted file mode 100644 index 4c20d83dc..000000000 --- a/src/dynamic/schema.rs +++ /dev/null @@ -1,1119 +0,0 @@ -use std::{any::Any, collections::HashMap, fmt::Debug, sync::Arc}; - -use async_graphql_parser::types::OperationType; -use futures_util::{Stream, StreamExt, TryFutureExt, stream::BoxStream}; -use indexmap::IndexMap; - -use crate::{ - Data, Executor, IntrospectionMode, QueryEnv, Request, Response, SDLExportOptions, SchemaEnv, - ServerError, ServerResult, ValidationMode, - dynamic::{ - DynamicRequest, FieldFuture, FieldValue, Object, ResolverContext, Scalar, SchemaError, - Subscription, TypeRef, Union, field::BoxResolverFn, resolve::resolve_container, - r#type::Type, - }, - extensions::{ExtensionFactory, Extensions}, - registry::{MetaType, Registry}, - schema::{SchemaEnvInner, prepare_request}, -}; - -/// Dynamic schema builder -pub struct SchemaBuilder { - query_type: String, - mutation_type: Option, - subscription_type: Option, - types: IndexMap, - data: Data, - extensions: Vec>, - validation_mode: ValidationMode, - recursive_depth: usize, - max_directives: Option, - complexity: Option, - depth: Option, - enable_suggestions: bool, - introspection_mode: IntrospectionMode, - enable_federation: bool, - entity_resolver: Option, -} - -impl SchemaBuilder { - /// Register a GraphQL type - #[must_use] - pub fn register(mut self, ty: impl Into) -> Self { - let ty = ty.into(); - self.types.insert(ty.name().to_string(), ty); - self - } - - /// Enable uploading files (register Upload type). - pub fn enable_uploading(mut self) -> Self { - self.types.insert(TypeRef::UPLOAD.to_string(), Type::Upload); - self - } - - /// Add a global data that can be accessed in the `Schema`. You access it - /// with `Context::data`. - #[must_use] - pub fn data(mut self, data: D) -> Self { - self.data.insert(data); - self - } - - /// Add an extension to the schema. - #[must_use] - pub fn extension(mut self, extension: impl ExtensionFactory) -> Self { - self.extensions.push(Box::new(extension)); - self - } - - /// Set the maximum complexity a query can have. By default, there is no - /// limit. - #[must_use] - pub fn limit_complexity(mut self, complexity: usize) -> Self { - self.complexity = Some(complexity); - self - } - - /// Set the maximum depth a query can have. By default, there is no limit. - #[must_use] - pub fn limit_depth(mut self, depth: usize) -> Self { - self.depth = Some(depth); - self - } - - /// Set the maximum recursive depth a query can have. (default: 32) - /// - /// If the value is too large, stack overflow may occur, usually `32` is - /// enough. - #[must_use] - pub fn limit_recursive_depth(mut self, depth: usize) -> Self { - self.recursive_depth = depth; - self - } - - /// Set the maximum number of directives on a single field. (default: no - /// limit) - pub fn limit_directives(mut self, max_directives: usize) -> Self { - self.max_directives = Some(max_directives); - self - } - - /// Set the validation mode, default is `ValidationMode::Strict`. - #[must_use] - pub fn validation_mode(mut self, validation_mode: ValidationMode) -> Self { - self.validation_mode = validation_mode; - self - } - - /// Disable field suggestions. - #[must_use] - pub fn disable_suggestions(mut self) -> Self { - self.enable_suggestions = false; - self - } - - /// Disable introspection queries. - #[must_use] - pub fn disable_introspection(mut self) -> Self { - self.introspection_mode = IntrospectionMode::Disabled; - self - } - - /// Only process introspection queries, everything else is processed as an - /// error. - #[must_use] - pub fn introspection_only(mut self) -> Self { - self.introspection_mode = IntrospectionMode::IntrospectionOnly; - self - } - - /// Enable federation, which is automatically enabled if the Query has least - /// one entity definition. - #[must_use] - pub fn enable_federation(mut self) -> Self { - self.enable_federation = true; - self - } - - /// Set the entity resolver for federation - pub fn entity_resolver(self, resolver_fn: F) -> Self - where - F: for<'a> Fn(ResolverContext<'a>) -> FieldFuture<'a> + Send + Sync + 'static, - { - Self { - entity_resolver: Some(Box::new(resolver_fn)), - ..self - } - } - - /// Consumes this builder and returns a schema. - pub fn finish(mut self) -> Result { - let mut registry = Registry { - types: Default::default(), - directives: Default::default(), - implements: Default::default(), - query_type: self.query_type, - mutation_type: self.mutation_type, - subscription_type: self.subscription_type, - introspection_mode: self.introspection_mode, - enable_federation: false, - federation_subscription: false, - ignore_name_conflicts: Default::default(), - enable_suggestions: self.enable_suggestions, - }; - registry.add_system_types(); - - for ty in self.types.values() { - ty.register(&mut registry)?; - } - update_interface_possible_types(&mut self.types, &mut registry); - - // create system scalars - for ty in ["Int", "Float", "Boolean", "String", "ID"] { - self.types - .insert(ty.to_string(), Type::Scalar(Scalar::new(ty))); - } - - // create introspection types - if matches!( - self.introspection_mode, - IntrospectionMode::Enabled | IntrospectionMode::IntrospectionOnly - ) { - registry.create_introspection_types(); - } - - // create entity types - if self.enable_federation || registry.has_entities() { - registry.enable_federation = true; - registry.create_federation_types(); - - // create _Entity type - let entity = self - .types - .values() - .filter(|ty| match ty { - Type::Object(obj) => obj.is_entity(), - Type::Interface(interface) => interface.is_entity(), - _ => false, - }) - .fold(Union::new("_Entity"), |entity, ty| { - entity.possible_type(ty.name()) - }); - self.types - .insert("_Entity".to_string(), Type::Union(entity)); - } - - let inner = SchemaInner { - env: SchemaEnv(Arc::new(SchemaEnvInner { - registry, - data: self.data, - custom_directives: Default::default(), - })), - extensions: self.extensions, - types: self.types, - recursive_depth: self.recursive_depth, - max_directives: self.max_directives, - complexity: self.complexity, - depth: self.depth, - validation_mode: self.validation_mode, - entity_resolver: self.entity_resolver, - }; - inner.check()?; - Ok(Schema(Arc::new(inner))) - } -} - -/// Dynamic GraphQL schema. -/// -/// Cloning a schema is cheap, so it can be easily shared. -#[derive(Clone)] -pub struct Schema(pub(crate) Arc); - -impl Debug for Schema { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Schema").finish() - } -} - -pub struct SchemaInner { - pub(crate) env: SchemaEnv, - pub(crate) types: IndexMap, - extensions: Vec>, - recursive_depth: usize, - max_directives: Option, - complexity: Option, - depth: Option, - validation_mode: ValidationMode, - pub(crate) entity_resolver: Option, -} - -impl Schema { - /// Create a schema builder - pub fn build(query: &str, mutation: Option<&str>, subscription: Option<&str>) -> SchemaBuilder { - SchemaBuilder { - query_type: query.to_string(), - mutation_type: mutation.map(ToString::to_string), - subscription_type: subscription.map(ToString::to_string), - types: Default::default(), - data: Default::default(), - extensions: Default::default(), - validation_mode: ValidationMode::Strict, - recursive_depth: 32, - max_directives: None, - complexity: None, - depth: None, - enable_suggestions: true, - introspection_mode: IntrospectionMode::Enabled, - entity_resolver: None, - enable_federation: false, - } - } - - fn create_extensions(&self, session_data: Arc) -> Extensions { - Extensions::new( - self.0.extensions.iter().map(|f| f.create()), - self.0.env.clone(), - session_data, - ) - } - - fn query_root(&self) -> ServerResult<&Object> { - self.0 - .types - .get(&self.0.env.registry.query_type) - .and_then(Type::as_object) - .ok_or_else(|| ServerError::new("Query root not found", None)) - } - - fn mutation_root(&self) -> ServerResult<&Object> { - self.0 - .env - .registry - .mutation_type - .as_ref() - .and_then(|mutation_name| self.0.types.get(mutation_name)) - .and_then(Type::as_object) - .ok_or_else(|| ServerError::new("Mutation root not found", None)) - } - - fn subscription_root(&self) -> ServerResult<&Subscription> { - self.0 - .env - .registry - .subscription_type - .as_ref() - .and_then(|subscription_name| self.0.types.get(subscription_name)) - .and_then(Type::as_subscription) - .ok_or_else(|| ServerError::new("Subscription root not found", None)) - } - - /// Returns SDL(Schema Definition Language) of this schema. - pub fn sdl(&self) -> String { - self.0.env.registry.export_sdl(Default::default()) - } - - /// Returns SDL(Schema Definition Language) of this schema with options. - pub fn sdl_with_options(&self, options: SDLExportOptions) -> String { - self.0.env.registry.export_sdl(options) - } - - async fn execute_once( - &self, - env: QueryEnv, - root_value: &FieldValue<'static>, - execute_data: Option, - ) -> Response { - // execute - let ctx = env.create_context( - &self.0.env, - None, - &env.operation.node.selection_set, - execute_data.as_ref(), - ); - let res = match &env.operation.node.ty { - OperationType::Query => { - async move { self.query_root() } - .and_then(|query_root| { - resolve_container(self, query_root, &ctx, root_value, false) - }) - .await - } - OperationType::Mutation => { - async move { self.mutation_root() } - .and_then(|query_root| { - resolve_container(self, query_root, &ctx, root_value, true) - }) - .await - } - OperationType::Subscription => Err(ServerError::new( - "Subscriptions are not supported on this transport.", - None, - )), - }; - - let mut resp = match res { - Ok(value) => Response::new(value.unwrap_or_default()), - Err(err) => Response::from_errors(vec![err]), - } - .http_headers(std::mem::take(&mut *env.http_headers.lock().unwrap())); - - resp.errors - .extend(std::mem::take(&mut *env.errors.lock().unwrap())); - resp - } - - /// Execute a GraphQL query. - pub async fn execute(&self, request: impl Into) -> Response { - let request = request.into(); - let extensions = self.create_extensions(Default::default()); - let request_fut = { - let extensions = extensions.clone(); - async move { - match prepare_request( - extensions, - request.inner, - Default::default(), - &self.0.env.registry, - self.0.validation_mode, - self.0.recursive_depth, - self.0.max_directives, - self.0.complexity, - self.0.depth, - ) - .await - { - Ok((env, cache_control)) => { - let f = { - |execute_data| { - let env = env.clone(); - async move { - self.execute_once(env, &request.root_value, execute_data) - .await - .cache_control(cache_control) - } - } - }; - env.extensions - .execute(env.operation_name.as_deref(), f) - .await - } - Err(errors) => Response::from_errors(errors), - } - } - }; - futures_util::pin_mut!(request_fut); - extensions.request(&mut request_fut).await - } - - /// Execute a GraphQL subscription with session data. - pub fn execute_stream_with_session_data( - &self, - request: impl Into, - session_data: Arc, - ) -> impl Stream + Send + Unpin + 'static { - let schema = self.clone(); - let request = request.into(); - let extensions = self.create_extensions(session_data.clone()); - - let stream = { - let extensions = extensions.clone(); - - async_stream::stream! { - let subscription = match schema.subscription_root() { - Ok(subscription) => subscription, - Err(err) => { - yield Response::from_errors(vec![err]); - return; - } - }; - - let (env, _) = match prepare_request( - extensions, - request.inner, - session_data, - &schema.0.env.registry, - schema.0.validation_mode, - schema.0.recursive_depth, - schema.0.max_directives, - schema.0.complexity, - schema.0.depth, - ) - .await { - Ok(res) => res, - Err(errors) => { - yield Response::from_errors(errors); - return; - } - }; - - if env.operation.node.ty != OperationType::Subscription { - yield schema.execute_once(env, &request.root_value, None).await; - return; - } - - let ctx = env.create_context( - &schema.0.env, - None, - &env.operation.node.selection_set, - None, - ); - let mut streams = Vec::new(); - subscription.collect_streams(&schema, &ctx, &mut streams, &request.root_value); - - let mut stream = futures_util::stream::select_all(streams); - while let Some(resp) = stream.next().await { - yield resp; - } - } - }; - extensions.subscribe(stream.boxed()) - } - - /// Execute a GraphQL subscription. - pub fn execute_stream( - &self, - request: impl Into, - ) -> impl Stream + Send + Unpin { - self.execute_stream_with_session_data(request, Default::default()) - } - - /// Returns the registry of this schema. - pub fn registry(&self) -> &Registry { - &self.0.env.registry - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl Executor for Schema { - async fn execute(&self, request: Request) -> Response { - Schema::execute(self, request).await - } - - fn execute_stream( - &self, - request: Request, - session_data: Option>, - ) -> BoxStream<'static, Response> { - Schema::execute_stream_with_session_data(self, request, session_data.unwrap_or_default()) - .boxed() - } -} - -fn update_interface_possible_types(types: &mut IndexMap, registry: &mut Registry) { - let mut interfaces = registry - .types - .values_mut() - .filter_map(|ty| match ty { - MetaType::Interface { - name, - possible_types, - .. - } => Some((name, possible_types)), - _ => None, - }) - .collect::>(); - - let objs = types.values().filter_map(|ty| match ty { - Type::Object(obj) => Some((&obj.name, &obj.implements)), - _ => None, - }); - - for (obj_name, implements) in objs { - for interface in implements { - if let Some(possible_types) = interfaces.get_mut(interface) { - possible_types.insert(obj_name.clone()); - } - } - } -} - -#[cfg(test)] -mod tests { - use std::sync::Arc; - - use async_graphql_parser::{Pos, types::ExecutableDocument}; - use async_graphql_value::Variables; - use futures_util::{StreamExt, stream::BoxStream}; - use tokio::sync::Mutex; - - use crate::{ - PathSegment, Request, Response, ServerError, ServerResult, ValidationResult, Value, - dynamic::*, extensions::*, value, - }; - - #[tokio::test] - async fn basic_query() { - let myobj = Object::new("MyObj") - .field(Field::new("a", TypeRef::named(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(123))) }) - })) - .field(Field::new("b", TypeRef::named(TypeRef::STRING), |_| { - FieldFuture::new(async { Ok(Some(Value::from("abc"))) }) - })); - - let query = Object::new("Query") - .field(Field::new("value", TypeRef::named(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(100))) }) - })) - .field(Field::new( - "valueObj", - TypeRef::named_nn(myobj.type_name()), - |_| FieldFuture::new(async { Ok(Some(FieldValue::NULL)) }), - )); - let schema = Schema::build("Query", None, None) - .register(query) - .register(myobj) - .finish() - .unwrap(); - - assert_eq!( - schema - .execute("{ value valueObj { a b } }") - .await - .into_result() - .unwrap() - .data, - value!({ - "value": 100, - "valueObj": { - "a": 123, - "b": "abc", - } - }) - ); - } - - #[tokio::test] - async fn root_value() { - let query = - Object::new("Query").field(Field::new("value", TypeRef::named(TypeRef::INT), |ctx| { - FieldFuture::new(async { - Ok(Some(Value::Number( - (*ctx.parent_value.try_downcast_ref::()?).into(), - ))) - }) - })); - - let schema = Schema::build("Query", None, None) - .register(query) - .finish() - .unwrap(); - assert_eq!( - schema - .execute("{ value }".root_value(FieldValue::owned_any(100))) - .await - .into_result() - .unwrap() - .data, - value!({ "value": 100, }) - ); - } - - #[tokio::test] - async fn field_alias() { - let query = - Object::new("Query").field(Field::new("value", TypeRef::named(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(100))) }) - })); - let schema = Schema::build("Query", None, None) - .register(query) - .finish() - .unwrap(); - - assert_eq!( - schema - .execute("{ a: value }") - .await - .into_result() - .unwrap() - .data, - value!({ - "a": 100, - }) - ); - } - - #[tokio::test] - async fn fragment_spread() { - let myobj = Object::new("MyObj") - .field(Field::new("a", TypeRef::named(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(123))) }) - })) - .field(Field::new("b", TypeRef::named(TypeRef::STRING), |_| { - FieldFuture::new(async { Ok(Some(Value::from("abc"))) }) - })); - - let query = Object::new("Query").field(Field::new( - "valueObj", - TypeRef::named_nn(myobj.type_name()), - |_| FieldFuture::new(async { Ok(Some(Value::Null)) }), - )); - let schema = Schema::build("Query", None, None) - .register(query) - .register(myobj) - .finish() - .unwrap(); - - let query = r#" - fragment A on MyObj { - a b - } - - { valueObj { ... A } } - "#; - - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "valueObj": { - "a": 123, - "b": "abc", - } - }) - ); - } - - #[tokio::test] - async fn inline_fragment() { - let myobj = Object::new("MyObj") - .field(Field::new("a", TypeRef::named(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(123))) }) - })) - .field(Field::new("b", TypeRef::named(TypeRef::STRING), |_| { - FieldFuture::new(async { Ok(Some(Value::from("abc"))) }) - })); - - let query = Object::new("Query").field(Field::new( - "valueObj", - TypeRef::named_nn(myobj.type_name()), - |_| FieldFuture::new(async { Ok(Some(FieldValue::NULL)) }), - )); - let schema = Schema::build("Query", None, None) - .register(query) - .register(myobj) - .finish() - .unwrap(); - - let query = r#" - { - valueObj { - ... on MyObj { a } - ... { b } - } - } - "#; - - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "valueObj": { - "a": 123, - "b": "abc", - } - }) - ); - } - - #[tokio::test] - async fn non_null() { - let query = Object::new("Query") - .field(Field::new( - "valueA", - TypeRef::named_nn(TypeRef::INT), - |_| FieldFuture::new(async { Ok(FieldValue::none()) }), - )) - .field(Field::new( - "valueB", - TypeRef::named_nn(TypeRef::INT), - |_| FieldFuture::new(async { Ok(Some(Value::from(100))) }), - )) - .field(Field::new("valueC", TypeRef::named(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(FieldValue::none()) }) - })) - .field(Field::new("valueD", TypeRef::named(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(200))) }) - })); - let schema = Schema::build("Query", None, None) - .register(query) - .finish() - .unwrap(); - - assert_eq!( - schema - .execute("{ valueA }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "internal: non-null types require a return value".to_owned(), - source: None, - locations: vec![Pos { column: 3, line: 1 }], - path: vec![PathSegment::Field("valueA".to_owned())], - extensions: None, - }] - ); - - assert_eq!( - schema - .execute("{ valueB }") - .await - .into_result() - .unwrap() - .data, - value!({ - "valueB": 100 - }) - ); - - assert_eq!( - schema - .execute("{ valueC valueD }") - .await - .into_result() - .unwrap() - .data, - value!({ - "valueC": null, - "valueD": 200, - }) - ); - } - - #[tokio::test] - async fn list() { - let query = Object::new("Query") - .field(Field::new( - "values", - TypeRef::named_nn_list_nn(TypeRef::INT), - |_| { - FieldFuture::new(async { - Ok(Some(vec![Value::from(3), Value::from(6), Value::from(9)])) - }) - }, - )) - .field(Field::new( - "values2", - TypeRef::named_nn_list_nn(TypeRef::INT), - |_| { - FieldFuture::new(async { - Ok(Some(Value::List(vec![ - Value::from(3), - Value::from(6), - Value::from(9), - ]))) - }) - }, - )) - .field(Field::new( - "values3", - TypeRef::named_nn_list(TypeRef::INT), - |_| FieldFuture::new(async { Ok(None::>) }), - )); - let schema = Schema::build("Query", None, None) - .register(query) - .finish() - .unwrap(); - - assert_eq!( - schema - .execute("{ values values2 values3 }") - .await - .into_result() - .unwrap() - .data, - value!({ - "values": [3, 6, 9], - "values2": [3, 6, 9], - "values3": null, - }) - ); - } - - #[tokio::test] - async fn extensions() { - struct MyExtensionImpl { - calls: Arc>>, - } - - #[async_trait::async_trait] - #[allow(unused_variables)] - impl Extension for MyExtensionImpl { - async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response { - self.calls.lock().await.push("request_start"); - let res = next.run(ctx).await; - self.calls.lock().await.push("request_end"); - res - } - - fn subscribe<'s>( - &self, - ctx: &ExtensionContext<'_>, - mut stream: BoxStream<'s, Response>, - next: NextSubscribe<'_>, - ) -> BoxStream<'s, Response> { - let calls = self.calls.clone(); - next.run( - ctx, - Box::pin(async_stream::stream! { - calls.lock().await.push("subscribe_start"); - while let Some(item) = stream.next().await { - yield item; - } - calls.lock().await.push("subscribe_end"); - }), - ) - } - - async fn prepare_request( - &self, - ctx: &ExtensionContext<'_>, - request: Request, - next: NextPrepareRequest<'_>, - ) -> ServerResult { - self.calls.lock().await.push("prepare_request_start"); - let res = next.run(ctx, request).await; - self.calls.lock().await.push("prepare_request_end"); - res - } - - async fn parse_query( - &self, - ctx: &ExtensionContext<'_>, - query: &str, - variables: &Variables, - next: NextParseQuery<'_>, - ) -> ServerResult { - self.calls.lock().await.push("parse_query_start"); - let res = next.run(ctx, query, variables).await; - self.calls.lock().await.push("parse_query_end"); - res - } - - async fn validation( - &self, - ctx: &ExtensionContext<'_>, - next: NextValidation<'_>, - ) -> Result> { - self.calls.lock().await.push("validation_start"); - let res = next.run(ctx).await; - self.calls.lock().await.push("validation_end"); - res - } - - async fn execute( - &self, - ctx: &ExtensionContext<'_>, - operation_name: Option<&str>, - next: NextExecute<'_>, - ) -> Response { - assert_eq!(operation_name, Some("Abc")); - self.calls.lock().await.push("execute_start"); - let res = next.run(ctx, operation_name).await; - self.calls.lock().await.push("execute_end"); - res - } - - async fn resolve( - &self, - ctx: &ExtensionContext<'_>, - info: ResolveInfo<'_>, - next: NextResolve<'_>, - ) -> ServerResult> { - self.calls.lock().await.push("resolve_start"); - let res = next.run(ctx, info).await; - self.calls.lock().await.push("resolve_end"); - res - } - } - - struct MyExtension { - calls: Arc>>, - } - - impl ExtensionFactory for MyExtension { - fn create(&self) -> Arc { - Arc::new(MyExtensionImpl { - calls: self.calls.clone(), - }) - } - } - - { - let query = Object::new("Query") - .field(Field::new( - "value1", - TypeRef::named_nn(TypeRef::INT), - |_| FieldFuture::new(async { Ok(Some(Value::from(10))) }), - )) - .field(Field::new( - "value2", - TypeRef::named_nn(TypeRef::INT), - |_| FieldFuture::new(async { Ok(Some(Value::from(10))) }), - )); - - let calls: Arc>> = Default::default(); - let schema = Schema::build(query.type_name(), None, None) - .register(query) - .extension(MyExtension { - calls: calls.clone(), - }) - .finish() - .unwrap(); - - let _ = schema - .execute("query Abc { value1 value2 }") - .await - .into_result() - .unwrap(); - let calls = calls.lock().await; - assert_eq!( - &*calls, - &vec![ - "request_start", - "prepare_request_start", - "prepare_request_end", - "parse_query_start", - "parse_query_end", - "validation_start", - "validation_end", - "execute_start", - "resolve_start", - "resolve_end", - "resolve_start", - "resolve_end", - "execute_end", - "request_end", - ] - ); - } - - { - let query = Object::new("Query").field(Field::new( - "value1", - TypeRef::named_nn(TypeRef::INT), - |_| FieldFuture::new(async { Ok(Some(Value::from(10))) }), - )); - - let subscription = Subscription::new("Subscription").field(SubscriptionField::new( - "value", - TypeRef::named_nn(TypeRef::INT), - |_| { - SubscriptionFieldFuture::new(async { - Ok(futures_util::stream::iter([1, 2, 3]) - .map(|value| Ok(Value::from(value)))) - }) - }, - )); - - let calls: Arc>> = Default::default(); - let schema = Schema::build(query.type_name(), None, Some(subscription.type_name())) - .register(query) - .register(subscription) - .extension(MyExtension { - calls: calls.clone(), - }) - .finish() - .unwrap(); - - let mut stream = schema.execute_stream("subscription Abc { value }"); - while stream.next().await.is_some() {} - let calls = calls.lock().await; - assert_eq!( - &*calls, - &vec![ - "subscribe_start", - "prepare_request_start", - "prepare_request_end", - "parse_query_start", - "parse_query_end", - "validation_start", - "validation_end", - // push 1 - "execute_start", - "resolve_start", - "resolve_end", - "execute_end", - // push 2 - "execute_start", - "resolve_start", - "resolve_end", - "execute_end", - // push 3 - "execute_start", - "resolve_start", - "resolve_end", - "execute_end", - // end - "subscribe_end", - ] - ); - } - } - - #[tokio::test] - async fn federation() { - let user = Object::new("User") - .field(Field::new( - "name", - TypeRef::named_nn(TypeRef::STRING), - |_| FieldFuture::new(async { Ok(Some(FieldValue::value("test"))) }), - )) - .key("name"); - - let query = - Object::new("Query").field(Field::new("value", TypeRef::named(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(100))) }) - })); - - let schema = Schema::build("Query", None, None) - .register(query) - .register(user) - .entity_resolver(|ctx| { - FieldFuture::new(async move { - let representations = ctx.args.try_get("representations")?.list()?; - let mut values = Vec::new(); - - for item in representations.iter() { - let item = item.object()?; - let typename = item - .try_get("__typename") - .and_then(|value| value.string())?; - - if typename == "User" { - values.push(FieldValue::borrowed_any(&()).with_type("User")); - } - } - - Ok(Some(FieldValue::list(values))) - }) - }) - .finish() - .unwrap(); - - assert_eq!( - schema - .execute( - r#" - { - _entities(representations: [{__typename: "User", name: "test"}]) { - __typename - ... on User { - name - } - } - } - "# - ) - .await - .into_result() - .unwrap() - .data, - value!({ - "_entities": [{ - "__typename": "User", - "name": "test", - }], - }) - ); - } -} diff --git a/src/dynamic/subscription.rs b/src/dynamic/subscription.rs deleted file mode 100644 index 00a8ebb60..000000000 --- a/src/dynamic/subscription.rs +++ /dev/null @@ -1,397 +0,0 @@ -use std::{borrow::Cow, fmt, fmt::Debug, sync::Arc}; - -use futures_util::{ - Future, FutureExt, Stream, StreamExt, TryStreamExt, future::BoxFuture, stream::BoxStream, -}; -use indexmap::IndexMap; - -use crate::{ - ContextSelectionSet, Data, Name, QueryPathNode, QueryPathSegment, Response, Result, - ServerResult, Value, - dynamic::{ - FieldValue, InputValue, ObjectAccessor, ResolverContext, Schema, SchemaError, TypeRef, - resolve::resolve, - }, - extensions::ResolveInfo, - parser::types::Selection, - registry::{Deprecation, MetaField, MetaType, Registry}, - subscription::BoxFieldStream, -}; - -type BoxResolveFut<'a> = BoxFuture<'a, Result>>>>; - -/// A future that returned from field resolver -pub struct SubscriptionFieldFuture<'a>(pub(crate) BoxResolveFut<'a>); - -impl<'a> SubscriptionFieldFuture<'a> { - /// Create a ResolverFuture - pub fn new(future: Fut) -> Self - where - Fut: Future> + Send + 'a, - S: Stream> + Send + 'a, - T: Into> + Send + 'a, - { - Self( - async move { - let res = future.await?.map_ok(Into::into); - Ok(res.boxed()) - } - .boxed(), - ) - } -} - -type BoxResolverFn = - Arc<(dyn for<'a> Fn(ResolverContext<'a>) -> SubscriptionFieldFuture<'a> + Send + Sync)>; - -/// A GraphQL subscription field -pub struct SubscriptionField { - pub(crate) name: String, - pub(crate) description: Option, - pub(crate) arguments: IndexMap, - pub(crate) ty: TypeRef, - pub(crate) resolver_fn: BoxResolverFn, - pub(crate) deprecation: Deprecation, -} - -impl SubscriptionField { - /// Create a GraphQL subscription field - pub fn new(name: N, ty: T, resolver_fn: F) -> Self - where - N: Into, - T: Into, - F: for<'a> Fn(ResolverContext<'a>) -> SubscriptionFieldFuture<'a> + Send + Sync + 'static, - { - Self { - name: name.into(), - description: None, - arguments: Default::default(), - ty: ty.into(), - resolver_fn: Arc::new(resolver_fn), - deprecation: Deprecation::NoDeprecated, - } - } - - impl_set_description!(); - impl_set_deprecation!(); - - /// Add an argument to the subscription field - #[inline] - pub fn argument(mut self, input_value: InputValue) -> Self { - self.arguments.insert(input_value.name.clone(), input_value); - self - } -} - -impl Debug for SubscriptionField { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Field") - .field("name", &self.name) - .field("description", &self.description) - .field("arguments", &self.arguments) - .field("ty", &self.ty) - .field("deprecation", &self.deprecation) - .finish() - } -} - -/// A GraphQL subscription type -#[derive(Debug)] -pub struct Subscription { - pub(crate) name: String, - pub(crate) description: Option, - pub(crate) fields: IndexMap, -} - -impl Subscription { - /// Create a GraphQL object type - #[inline] - pub fn new(name: impl Into) -> Self { - Self { - name: name.into(), - description: None, - fields: Default::default(), - } - } - - impl_set_description!(); - - /// Add an field to the object - #[inline] - pub fn field(mut self, field: SubscriptionField) -> Self { - assert!( - !self.fields.contains_key(&field.name), - "Field `{}` already exists", - field.name - ); - self.fields.insert(field.name.clone(), field); - self - } - - /// Returns the type name - #[inline] - pub fn type_name(&self) -> &str { - &self.name - } - - pub(crate) fn register(&self, registry: &mut Registry) -> Result<(), SchemaError> { - let mut fields = IndexMap::new(); - - for field in self.fields.values() { - let mut args = IndexMap::new(); - - for argument in field.arguments.values() { - args.insert(argument.name.clone(), argument.to_meta_input_value()); - } - - fields.insert( - field.name.clone(), - MetaField { - name: field.name.clone(), - description: field.description.clone(), - args, - ty: field.ty.to_string(), - deprecation: field.deprecation.clone(), - cache_control: Default::default(), - external: false, - requires: None, - provides: None, - visible: None, - shareable: false, - inaccessible: false, - tags: vec![], - override_from: None, - compute_complexity: None, - directive_invocations: vec![], - requires_scopes: vec![], - }, - ); - } - - registry.types.insert( - self.name.clone(), - MetaType::Object { - name: self.name.clone(), - description: self.description.clone(), - fields, - cache_control: Default::default(), - extends: false, - shareable: false, - resolvable: true, - keys: None, - visible: None, - inaccessible: false, - interface_object: false, - tags: vec![], - is_subscription: true, - rust_typename: None, - directive_invocations: vec![], - requires_scopes: vec![], - }, - ); - - Ok(()) - } - - pub(crate) fn collect_streams<'a>( - &self, - schema: &Schema, - ctx: &ContextSelectionSet<'a>, - streams: &mut Vec>, - root_value: &'a FieldValue<'static>, - ) { - for selection in &ctx.item.node.items { - if let Selection::Field(field) = &selection.node { - if let Some(field_def) = self.fields.get(field.node.name.node.as_str()) { - let schema = schema.clone(); - let field_type = field_def.ty.clone(); - let resolver_fn = field_def.resolver_fn.clone(); - let ctx = ctx.clone(); - - streams.push( - async_stream::try_stream! { - let ctx_field = ctx.with_field(field); - let field_name = ctx_field.item.node.response_key().node.clone(); - let arguments = ObjectAccessor(Cow::Owned( - field - .node - .arguments - .iter() - .map(|(name, value)| { - ctx_field - .resolve_input_value(value.clone()) - .map(|value| (name.node.clone(), value)) - }) - .collect::>>()?, - )); - - let mut stream = resolver_fn(ResolverContext { - ctx: &ctx_field, - args: arguments, - parent_value: root_value, - }) - .0 - .await - .map_err(|err| ctx_field.set_error_path(err.into_server_error(ctx_field.item.pos)))?; - - while let Some(value) = stream.next().await.transpose().map_err(|err| ctx_field.set_error_path(err.into_server_error(ctx_field.item.pos)))? { - let f = |execute_data: Option| { - let schema = schema.clone(); - let field_name = field_name.clone(); - let field_type = field_type.clone(); - let ctx_field = ctx_field.clone(); - - async move { - let mut ctx_field = ctx_field.clone(); - ctx_field.execute_data = execute_data.as_ref(); - let ri = ResolveInfo { - path_node: &QueryPathNode { - parent: None, - segment: QueryPathSegment::Name(&field_name), - }, - parent_type: schema.0.env.registry.subscription_type.as_ref().unwrap(), - return_type: &field_type.to_string(), - name: field.node.name.node.as_str(), - alias: field.node.alias.as_ref().map(|alias| alias.node.as_str()), - is_for_introspection: false, - field: &field.node, - }; - let resolve_fut = resolve(&schema, &ctx_field, &field_type, Some(&value)); - futures_util::pin_mut!(resolve_fut); - let value = ctx_field.query_env.extensions.resolve(ri, &mut resolve_fut).await; - - match value { - Ok(value) => { - let mut map = IndexMap::new(); - map.insert(field_name.clone(), value.unwrap_or_default()); - Response::new(Value::Object(map)) - }, - Err(err) => Response::from_errors(vec![err]), - } - } - }; - let resp = ctx_field.query_env.extensions.execute(ctx_field.query_env.operation_name.as_deref(), f).await; - let is_err = !resp.errors.is_empty(); - yield resp; - if is_err { - break; - } - } - }.map(|res| { - res.unwrap_or_else(|err| Response::from_errors(vec![err])) - }) - .boxed(), - ); - } - } - } - } -} - -#[cfg(test)] -mod tests { - use std::time::Duration; - - use futures_util::StreamExt; - - use crate::{Value, dynamic::*, value}; - - #[tokio::test] - async fn subscription() { - struct MyObjData { - value: i32, - } - - let my_obj = Object::new("MyObject").field(Field::new( - "value", - TypeRef::named_nn(TypeRef::INT), - |ctx| { - FieldFuture::new(async { - Ok(Some(Value::from( - ctx.parent_value.try_downcast_ref::()?.value, - ))) - }) - }, - )); - - let query = Object::new("Query").field(Field::new( - "value", - TypeRef::named_nn(TypeRef::INT), - |_| FieldFuture::new(async { Ok(FieldValue::none()) }), - )); - - let subscription = Subscription::new("Subscription").field(SubscriptionField::new( - "obj", - TypeRef::named_nn(my_obj.type_name()), - |_| { - SubscriptionFieldFuture::new(async { - Ok(async_stream::try_stream! { - for i in 0..10 { - tokio::time::sleep(Duration::from_millis(100)).await; - yield FieldValue::owned_any(MyObjData { value: i }); - } - }) - }) - }, - )); - - let schema = Schema::build(query.type_name(), None, Some(subscription.type_name())) - .register(my_obj) - .register(query) - .register(subscription) - .finish() - .unwrap(); - - let mut stream = schema.execute_stream("subscription { obj { value } }"); - for i in 0..10 { - assert_eq!( - stream.next().await.unwrap().into_result().unwrap().data, - value!({ - "obj": { "value": i } - }) - ); - } - } - - #[tokio::test] - async fn borrow_context() { - struct State { - value: i32, - } - - let query = - Object::new("Query").field(Field::new("value", TypeRef::named(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(FieldValue::NONE) }) - })); - - let subscription = Subscription::new("Subscription").field(SubscriptionField::new( - "values", - TypeRef::named_nn(TypeRef::INT), - |ctx| { - SubscriptionFieldFuture::new(async move { - Ok(async_stream::try_stream! { - for i in 0..10 { - tokio::time::sleep(Duration::from_millis(100)).await; - yield FieldValue::value(ctx.data_unchecked::().value + i); - } - }) - }) - }, - )); - - let schema = Schema::build("Query", None, Some(subscription.type_name())) - .register(query) - .register(subscription) - .data(State { value: 123 }) - .finish() - .unwrap(); - - let mut stream = schema.execute_stream("subscription { values }"); - for i in 0..10 { - assert_eq!( - stream.next().await.unwrap().into_result().unwrap().data, - value!({ "values": i + 123 }) - ); - } - } -} diff --git a/src/dynamic/type.rs b/src/dynamic/type.rs deleted file mode 100644 index 8392b0a25..000000000 --- a/src/dynamic/type.rs +++ /dev/null @@ -1,172 +0,0 @@ -use crate::{ - Upload, - dynamic::{Enum, InputObject, Interface, Object, Scalar, SchemaError, Subscription, Union}, - registry::Registry, -}; - -/// A GraphQL type -#[derive(Debug)] -pub enum Type { - /// Scalar - Scalar(Scalar), - /// Object - Object(Object), - /// Input object - InputObject(InputObject), - /// Enum - Enum(Enum), - /// Interface - Interface(Interface), - /// Union - Union(Union), - /// Subscription - Subscription(Subscription), - /// Upload - Upload, -} - -impl Type { - pub(crate) fn name(&self) -> &str { - match self { - Type::Scalar(scalar) => &scalar.name, - Type::Object(object) => &object.name, - Type::InputObject(input_object) => &input_object.name, - Type::Enum(e) => &e.name, - Type::Interface(interface) => &interface.name, - Type::Union(union) => &union.name, - Type::Subscription(subscription) => &subscription.name, - Type::Upload => "Upload", - } - } - - #[inline] - pub(crate) fn as_object(&self) -> Option<&Object> { - if let Type::Object(obj) = self { - Some(obj) - } else { - None - } - } - - #[inline] - pub(crate) fn as_interface(&self) -> Option<&Interface> { - if let Type::Interface(interface) = self { - Some(interface) - } else { - None - } - } - - #[inline] - pub(crate) fn as_input_object(&self) -> Option<&InputObject> { - if let Type::InputObject(obj) = self { - Some(obj) - } else { - None - } - } - - #[inline] - pub(crate) fn as_subscription(&self) -> Option<&Subscription> { - if let Type::Subscription(subscription) = self { - Some(subscription) - } else { - None - } - } - - pub(crate) fn is_output_type(&self) -> bool { - match self { - Type::Scalar(_) => true, - Type::Object(_) => true, - Type::InputObject(_) => false, - Type::Enum(_) => true, - Type::Interface(_) => true, - Type::Union(_) => true, - Type::Subscription(_) => false, - Type::Upload => false, - } - } - - pub(crate) fn is_input_type(&self) -> bool { - match self { - Type::Scalar(_) => true, - Type::Object(_) => false, - Type::InputObject(_) => true, - Type::Enum(_) => true, - Type::Interface(_) => false, - Type::Union(_) => false, - Type::Subscription(_) => false, - Type::Upload => true, - } - } - - pub(crate) fn register(&self, registry: &mut Registry) -> Result<(), SchemaError> { - if registry.types.contains_key(self.name()) { - return Err(format!("Type \"{0}\" already exists", self.name()).into()); - } - - match self { - Type::Scalar(scalar) => scalar.register(registry), - Type::Object(object) => object.register(registry), - Type::InputObject(input_object) => input_object.register(registry), - Type::Enum(e) => e.register(registry), - Type::Interface(interface) => interface.register(registry), - Type::Union(union) => union.register(registry), - Type::Subscription(subscription) => subscription.register(registry), - Type::Upload => { - ::create_type_info(registry); - Ok(()) - } - } - } -} - -impl From for Type { - #[inline] - fn from(scalar: Scalar) -> Self { - Type::Scalar(scalar) - } -} - -impl From for Type { - #[inline] - fn from(obj: Object) -> Self { - Type::Object(obj) - } -} - -impl From for Type { - #[inline] - fn from(obj: InputObject) -> Self { - Type::InputObject(obj) - } -} - -impl From for Type { - #[inline] - fn from(e: Enum) -> Self { - Type::Enum(e) - } -} - -impl From for Type { - #[inline] - fn from(interface: Interface) -> Self { - Type::Interface(interface) - } -} - -impl From for Type { - #[inline] - fn from(union: Union) -> Self { - Type::Union(union) - } -} - -impl From for Type { - #[inline] - fn from(subscription: Subscription) -> Self { - Type::Subscription(subscription) - } -} diff --git a/src/dynamic/type_ref.rs b/src/dynamic/type_ref.rs deleted file mode 100644 index 5d96e0efc..000000000 --- a/src/dynamic/type_ref.rs +++ /dev/null @@ -1,153 +0,0 @@ -use std::{ - borrow::Cow, - fmt::{self, Display}, -}; - -/// A type reference -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum TypeRef { - /// Named type - Named(Cow<'static, str>), - /// Non-null type - NonNull(Box), - /// List type - List(Box), -} - -impl Display for TypeRef { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - TypeRef::Named(name) => write!(f, "{}", name), - TypeRef::NonNull(ty) => write!(f, "{}!", ty), - TypeRef::List(ty) => write!(f, "[{}]", ty), - } - } -} - -impl TypeRef { - /// A int scalar type - pub const INT: &'static str = "Int"; - - /// A float scalar type - pub const FLOAT: &'static str = "Float"; - - /// A string scalar type - pub const STRING: &'static str = "String"; - - /// A boolean scalar type - pub const BOOLEAN: &'static str = "Boolean"; - - /// A ID scalar type - pub const ID: &'static str = "ID"; - - /// A Upload type - pub const UPLOAD: &'static str = "Upload"; - - /// Returns the nullable type reference - /// - /// GraphQL Type: `T` - #[inline] - pub fn named(type_name: impl Into) -> TypeRef { - TypeRef::Named(type_name.into().into()) - } - - /// Returns the non-null type reference - /// - /// GraphQL Type: `T!` - #[inline] - pub fn named_nn(type_name: impl Into) -> TypeRef { - TypeRef::NonNull(Box::new(TypeRef::Named(type_name.into().into()))) - } - - /// Returns a nullable list of nullable members type reference - /// - /// GraphQL Type: `[T]` - #[inline] - pub fn named_list(type_name: impl Into) -> TypeRef { - TypeRef::List(Box::new(TypeRef::Named(type_name.into().into()))) - } - - /// Returns a nullable list of non-null members type reference - /// - /// GraphQL Type: `[T!]` - #[inline] - pub fn named_nn_list(type_name: impl Into) -> TypeRef { - TypeRef::List(Box::new(TypeRef::NonNull(Box::new(TypeRef::Named( - type_name.into().into(), - ))))) - } - - /// Returns a non-null list of nullable members type reference - /// - /// GraphQL Type: `[T]!` - #[inline] - pub fn named_list_nn(type_name: impl Into) -> TypeRef { - TypeRef::NonNull(Box::new(TypeRef::List(Box::new(TypeRef::Named( - type_name.into().into(), - ))))) - } - - /// Returns a non-null list of non-null members type reference - /// - /// GraphQL Type: `[T!]!` - #[inline] - pub fn named_nn_list_nn(type_name: impl Into) -> TypeRef { - TypeRef::NonNull(Box::new(TypeRef::List(Box::new(TypeRef::NonNull( - Box::new(TypeRef::Named(type_name.into().into())), - ))))) - } - - /// Returns the type name - /// - /// `[Foo!]` -> `Foo` - #[inline(always)] - pub fn type_name(&self) -> &str { - match self { - TypeRef::Named(name) => name, - TypeRef::NonNull(inner) => inner.type_name(), - TypeRef::List(inner) => inner.type_name(), - } - } - - #[inline] - pub(crate) fn is_nullable(&self) -> bool { - match self { - TypeRef::Named(_) => true, - TypeRef::NonNull(_) => false, - TypeRef::List(_) => true, - } - } - - pub(crate) fn is_subtype(&self, sub: &TypeRef) -> bool { - fn is_subtype(cur: &TypeRef, sub: &TypeRef) -> bool { - match (cur, sub) { - (TypeRef::NonNull(super_type), TypeRef::NonNull(sub_type)) => { - is_subtype(&super_type, &sub_type) - } - (_, TypeRef::NonNull(sub_type)) => is_subtype(cur, &sub_type), - (TypeRef::Named(super_type), TypeRef::Named(sub_type)) => super_type == sub_type, - (TypeRef::List(super_type), TypeRef::List(sub_type)) => { - is_subtype(super_type, sub_type) - } - _ => false, - } - } - - is_subtype(self, sub) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn create() { - assert_eq!(TypeRef::named("MyObj").to_string(), "MyObj"); - assert_eq!(TypeRef::named_nn("MyObj").to_string(), "MyObj!"); - assert_eq!(TypeRef::named_list("MyObj").to_string(), "[MyObj]"); - assert_eq!(TypeRef::named_list_nn("MyObj").to_string(), "[MyObj]!"); - assert_eq!(TypeRef::named_nn_list("MyObj").to_string(), "[MyObj!]"); - assert_eq!(TypeRef::named_nn_list_nn("MyObj").to_string(), "[MyObj!]!"); - } -} diff --git a/src/dynamic/union.rs b/src/dynamic/union.rs deleted file mode 100644 index 70e567062..000000000 --- a/src/dynamic/union.rs +++ /dev/null @@ -1,398 +0,0 @@ -use indexmap::IndexSet; - -use super::{Directive, directive::to_meta_directive_invocation}; -use crate::{ - dynamic::SchemaError, - registry::{MetaType, Registry}, -}; - -/// A GraphQL union type -/// -/// # Examples -/// -/// ``` -/// use async_graphql::{dynamic::*, value, Value}; -/// -/// let obj_a = Object::new("MyObjA") -/// .field(Field::new("a", TypeRef::named_nn(TypeRef::INT), |_| { -/// FieldFuture::new(async { Ok(Some(Value::from(100))) }) -/// })) -/// .field(Field::new("b", TypeRef::named_nn(TypeRef::INT), |_| { -/// FieldFuture::new(async { Ok(Some(Value::from(200))) }) -/// })); -/// -/// let obj_b = Object::new("MyObjB") -/// .field(Field::new("c", TypeRef::named_nn(TypeRef::INT), |_| { -/// FieldFuture::new(async { Ok(Some(Value::from(300))) }) -/// })) -/// .field(Field::new("d", TypeRef::named_nn(TypeRef::INT), |_| { -/// FieldFuture::new(async { Ok(Some(Value::from(400))) }) -/// })); -/// -/// let union = Union::new("MyUnion") -/// .possible_type(obj_a.type_name()) -/// .possible_type(obj_b.type_name()); -/// -/// let query = Object::new("Query") -/// .field(Field::new("valueA", TypeRef::named_nn(union.type_name()), |_| { -/// FieldFuture::new(async { -/// Ok(Some(FieldValue::with_type(FieldValue::NULL, "MyObjA"))) -/// }) -/// })) -/// .field(Field::new("valueB", TypeRef::named_nn(union.type_name()), |_| { -/// FieldFuture::new(async { -/// Ok(Some(FieldValue::with_type(FieldValue::NULL, "MyObjB"))) -/// }) -/// })); -/// -/// # tokio::runtime::Runtime::new().unwrap().block_on(async move { -/// -/// let schema = Schema::build(query.type_name(), None, None) -/// .register(obj_a) -/// .register(obj_b) -/// .register(union) -/// .register(query) -/// .finish()?; -/// -/// let query = r#" -/// { -/// valueA { ... on MyObjA { a b } ... on MyObjB { c d } } -/// valueB { ... on MyObjA { a b } ... on MyObjB { c d } } -/// } -/// "#; -/// -/// assert_eq!( -/// schema.execute(query).await.into_result().unwrap().data, -/// value!({ -/// "valueA": { -/// "a": 100, -/// "b": 200, -/// }, -/// "valueB": { -/// "c": 300, -/// "d": 400, -/// } -/// }) -/// ); -/// -/// # Ok::<_, SchemaError>(()) -/// # }).unwrap(); -/// ``` -#[derive(Debug)] -pub struct Union { - pub(crate) name: String, - pub(crate) description: Option, - pub(crate) possible_types: IndexSet, - inaccessible: bool, - tags: Vec, - pub(crate) directives: Vec, -} - -impl Union { - /// Create a GraphQL union type - #[inline] - pub fn new(name: impl Into) -> Self { - Self { - name: name.into(), - description: None, - possible_types: Default::default(), - inaccessible: false, - tags: Vec::new(), - directives: Vec::new(), - } - } - - impl_set_description!(); - impl_set_inaccessible!(); - impl_set_tags!(); - impl_directive!(); - - /// Add a possible type to the union that must be an object - #[inline] - pub fn possible_type(mut self, ty: impl Into) -> Self { - self.possible_types.insert(ty.into()); - self - } - - /// Returns the type name - #[inline] - pub fn type_name(&self) -> &str { - &self.name - } - - pub(crate) fn register(&self, registry: &mut Registry) -> Result<(), SchemaError> { - registry.types.insert( - self.name.clone(), - MetaType::Union { - name: self.name.clone(), - description: self.description.clone(), - possible_types: self.possible_types.clone(), - visible: None, - inaccessible: self.inaccessible, - tags: self.tags.clone(), - rust_typename: None, - directive_invocations: to_meta_directive_invocation(self.directives.clone()), - }, - ); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use async_graphql_parser::Pos; - - use crate::{PathSegment, Request, ServerError, Value, dynamic::*, value}; - - #[tokio::test] - async fn basic_union() { - let obj_a = Object::new("MyObjA") - .field(Field::new("a", TypeRef::named_nn(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(100))) }) - })) - .field(Field::new("b", TypeRef::named_nn(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(200))) }) - })); - - let obj_b = Object::new("MyObjB") - .field(Field::new("c", TypeRef::named_nn(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(300))) }) - })) - .field(Field::new("d", TypeRef::named_nn(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(400))) }) - })); - - let union = Union::new("MyUnion") - .possible_type(obj_a.type_name()) - .possible_type(obj_b.type_name()); - - let query = Object::new("Query") - .field(Field::new( - "valueA", - TypeRef::named_nn(union.type_name()), - |_| FieldFuture::new(async { Ok(Some(FieldValue::NULL.with_type("MyObjA"))) }), - )) - .field(Field::new( - "valueB", - TypeRef::named_nn(union.type_name()), - |_| FieldFuture::new(async { Ok(Some(FieldValue::NULL.with_type("MyObjB"))) }), - )); - - let schema = Schema::build(query.type_name(), None, None) - .register(obj_a) - .register(obj_b) - .register(union) - .register(query) - .finish() - .unwrap(); - - let query = r#" - { - valueA { __typename ... on MyObjA { a b } ... on MyObjB { c d } } - valueB { __typename ... on MyObjA { a b } ... on MyObjB { c d } } - } - "#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "valueA": { - "__typename": "MyObjA", - "a": 100, - "b": 200, - }, - "valueB": { - "__typename": "MyObjB", - "c": 300, - "d": 400, - } - }) - ); - } - - #[tokio::test] - async fn does_not_contain() { - let obj_a = Object::new("MyObjA") - .field(Field::new("a", TypeRef::named_nn(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(100))) }) - })) - .field(Field::new("b", TypeRef::named_nn(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(200))) }) - })); - - let obj_b = Object::new("MyObjB") - .field(Field::new("c", TypeRef::named_nn(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(300))) }) - })) - .field(Field::new("d", TypeRef::named_nn(TypeRef::INT), |_| { - FieldFuture::new(async { Ok(Some(Value::from(400))) }) - })); - - let union = Union::new("MyUnion").possible_type(obj_a.type_name()); - - let query = Object::new("Query").field(Field::new( - "valueA", - TypeRef::named_nn(union.type_name()), - |_| FieldFuture::new(async { Ok(Some(FieldValue::NULL.with_type("MyObjB"))) }), - )); - - let schema = Schema::build(query.type_name(), None, None) - .register(obj_a) - .register(obj_b) - .register(union) - .register(query) - .finish() - .unwrap(); - - let query = r#" - { - valueA { ... on MyObjA { a b } } - } - "#; - assert_eq!( - schema.execute(query).await.into_result().unwrap_err(), - vec![ServerError { - message: "internal: union \"MyUnion\" does not contain object \"MyObjB\"" - .to_owned(), - source: None, - locations: vec![Pos { - column: 17, - line: 3 - }], - path: vec![PathSegment::Field("valueA".to_owned())], - extensions: None, - }] - ); - } - - #[tokio::test] - async fn test_query() { - struct Dog; - struct Cat; - struct Snake; - // enum - #[allow(dead_code)] - enum Animal { - Dog(Dog), - Cat(Cat), - Snake(Snake), - } - struct Query { - pet: Animal, - } - - impl Animal { - fn to_field_value(&self) -> FieldValue { - match self { - Animal::Dog(dog) => FieldValue::borrowed_any(dog).with_type("Dog"), - Animal::Cat(cat) => FieldValue::borrowed_any(cat).with_type("Cat"), - Animal::Snake(snake) => FieldValue::borrowed_any(snake).with_type("Snake"), - } - } - } - fn create_schema() -> Schema { - // interface - let named = Interface::new("Named"); - let named = named.field(InterfaceField::new( - "name", - TypeRef::named_nn(TypeRef::STRING), - )); - // dog - let dog = Object::new("Dog"); - let dog = dog.field(Field::new( - "name", - TypeRef::named_nn(TypeRef::STRING), - |_ctx| FieldFuture::new(async move { Ok(Some(Value::from("dog"))) }), - )); - let dog = dog.field(Field::new( - "power", - TypeRef::named_nn(TypeRef::INT), - |_ctx| FieldFuture::new(async move { Ok(Some(Value::from(100))) }), - )); - let dog = dog.implement("Named"); - // cat - let cat = Object::new("Cat"); - let cat = cat.field(Field::new( - "name", - TypeRef::named_nn(TypeRef::STRING), - |_ctx| FieldFuture::new(async move { Ok(Some(Value::from("cat"))) }), - )); - let cat = cat.field(Field::new( - "life", - TypeRef::named_nn(TypeRef::INT), - |_ctx| FieldFuture::new(async move { Ok(Some(Value::from(9))) }), - )); - let cat = cat.implement("Named"); - // snake - let snake = Object::new("Snake"); - let snake = snake.field(Field::new( - "length", - TypeRef::named_nn(TypeRef::INT), - |_ctx| FieldFuture::new(async move { Ok(Some(Value::from(200))) }), - )); - // animal - let animal = Union::new("Animal"); - let animal = animal.possible_type("Dog"); - let animal = animal.possible_type("Cat"); - let animal = animal.possible_type("Snake"); - // query - - let query = Object::new("Query"); - let query = query.field(Field::new("pet", TypeRef::named_nn("Animal"), |ctx| { - FieldFuture::new(async move { - let query = ctx.parent_value.try_downcast_ref::()?; - Ok(Some(query.pet.to_field_value())) - }) - })); - - let schema = Schema::build(query.type_name(), None, None); - let schema = schema - .register(query) - .register(named) - .register(dog) - .register(cat) - .register(snake) - .register(animal); - - schema.finish().unwrap() - } - - let schema = create_schema(); - let query = r#" - query { - dog: pet { - ... on Dog { - __dog_typename: __typename - name - power - } - } - named: pet { - ... on Named { - __named_typename: __typename - name - } - } - } - "#; - let root = Query { - pet: Animal::Dog(Dog), - }; - let req = Request::new(query).root_value(FieldValue::owned_any(root)); - let res = schema.execute(req).await; - - assert_eq!( - res.data.into_json().unwrap(), - serde_json::json!({ - "dog": { - "__dog_typename": "Dog", - "name": "dog", - "power": 100 - }, - "named": { - "__named_typename": "Dog", - "name": "dog" - } - }) - ); - } -} diff --git a/src/dynamic/value_accessor.rs b/src/dynamic/value_accessor.rs deleted file mode 100644 index 97354749e..000000000 --- a/src/dynamic/value_accessor.rs +++ /dev/null @@ -1,230 +0,0 @@ -use std::borrow::Cow; - -use indexmap::IndexMap; -use serde::de::DeserializeOwned; - -use crate::{Error, Name, Result, Upload, Value}; - -/// A value accessor -pub struct ValueAccessor<'a>(&'a Value); - -impl<'a> ValueAccessor<'a> { - /// Returns `true` if the value is null, otherwise returns `false` - #[inline] - pub fn is_null(&self) -> bool { - matches!(self.0, Value::Null) - } - - /// Returns the boolean - pub fn boolean(&self) -> Result { - match self.0 { - Value::Boolean(b) => Ok(*b), - _ => Err(Error::new("internal: not a boolean")), - } - } - - /// Returns the enum name - pub fn enum_name(&self) -> Result<&str> { - match self.0 { - Value::Enum(s) => Ok(s), - Value::String(s) => Ok(s.as_str()), - _ => Err(Error::new("internal: not an enum name")), - } - } - - /// Returns the number as `i64` - pub fn i64(&self) -> Result { - if let Value::Number(number) = self.0 { - if let Some(value) = number.as_i64() { - return Ok(value); - } - } - Err(Error::new("internal: not an signed integer")) - } - - /// Returns the number as `u64` - pub fn u64(&self) -> Result { - if let Value::Number(number) = self.0 { - if let Some(value) = number.as_u64() { - return Ok(value); - } - } - Err(Error::new("internal: not an unsigned integer")) - } - - /// Returns the number as `f32` - pub fn f32(&self) -> Result { - if let Value::Number(number) = self.0 { - if let Some(value) = number.as_f64() { - return Ok(value as f32); - } - } - Err(Error::new("internal: not a float")) - } - - /// Returns the number as `f64` - pub fn f64(&self) -> Result { - if let Value::Number(number) = self.0 { - if let Some(value) = number.as_f64() { - return Ok(value); - } - } - Err(Error::new("internal: not a float")) - } - - /// Returns the string value - pub fn string(&self) -> Result<&'a str> { - if let Value::String(value) = self.0 { - Ok(value) - } else { - Err(Error::new("internal: not a string")) - } - } - - /// Returns the object accessor - pub fn object(&self) -> Result> { - if let Value::Object(obj) = self.0 { - Ok(ObjectAccessor(Cow::Borrowed(obj))) - } else { - Err(Error::new("internal: not an object")) - } - } - - /// Returns the list accessor - pub fn list(&self) -> Result> { - if let Value::List(list) = self.0 { - Ok(ListAccessor(list)) - } else { - Err(Error::new("internal: not a list")) - } - } - - /// Deserialize the value to `T` - pub fn deserialize(&self) -> Result { - T::deserialize(self.0.clone()).map_err(|err| format!("internal: {}", err).into()) - } - - /// Returns a reference to the underlying `Value` - #[inline] - pub fn as_value(&self) -> &'a Value { - self.0 - } - - /// Returns a upload object - pub fn upload(&self) -> Result { - ::parse(Some(self.0.clone())) - .map_err(|_| Error::new("internal: not a upload")) - } -} - -/// A object accessor -pub struct ObjectAccessor<'a>(pub(crate) Cow<'a, IndexMap>); - -impl<'a> ObjectAccessor<'a> { - /// Return a reference to the value stored for `key`, if it is present, - /// else `None`. - #[inline] - pub fn get(&self, name: &str) -> Option> { - self.0.get(name).map(ValueAccessor) - } - - /// Like [`ObjectAccessor::get`], returns `Err` if the index does not exist - #[inline] - pub fn try_get(&self, name: &str) -> Result> { - self.0 - .get(name) - .map(ValueAccessor) - .ok_or_else(|| Error::new(format!("internal: key \"{}\" not found", name))) - } - - /// Return an iterator over the key-value pairs of the object, in their - /// order - #[inline] - pub fn iter(&self) -> impl Iterator)> + '_ { - self.0 - .iter() - .map(|(name, value)| (name, ValueAccessor(value))) - } - - /// Return an iterator over the keys of the object, in their order - #[inline] - pub fn keys(&self) -> impl Iterator + '_ { - self.0.keys() - } - - /// Return an iterator over the values of the object, in their order - #[inline] - pub fn values(&self) -> impl Iterator> + '_ { - self.0.values().map(ValueAccessor) - } - - /// Returns the number of elements in the object - #[inline] - pub fn len(&self) -> usize { - self.0.len() - } - - /// Returns `true` if the object has no members - #[must_use] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Returns a reference to the underlying IndexMap - #[inline] - pub fn as_index_map(&'a self) -> &'a IndexMap { - &self.0 - } -} - -/// A list accessor -pub struct ListAccessor<'a>(pub(crate) &'a [Value]); - -impl<'a> ListAccessor<'a> { - /// Returns the number of elements in the list - #[inline] - pub fn len(&self) -> usize { - self.0.len() - } - - /// Returns `true` if the list has a length of 0 - #[inline] - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// Returns an iterator over the list - #[inline] - pub fn iter(&self) -> impl Iterator> + '_ { - self.0.iter().map(ValueAccessor) - } - - /// Returns a reference to an element depending on the index - #[inline] - pub fn get(&self, idx: usize) -> Option> { - self.0.get(idx).map(ValueAccessor) - } - - /// Like [`ListAccessor::get`], returns `Err` if the index does not exist - #[inline] - pub fn try_get(&self, idx: usize) -> Result> { - self.get(idx) - .ok_or_else(|| Error::new(format!("internal: index \"{}\" not found", idx))) - } - - /// Returns a new ListAccessor that represents a slice of the original - #[inline] - pub fn as_slice(&self, start: usize, end: usize) -> Result> { - if start <= end && end <= self.len() { - Ok(ListAccessor(&self.0[start..end])) - } else { - Err(Error::new("internal: invalid slice indices")) - } - } - - /// Returns a reference to the underlying `&[Value]` - #[inline] - pub fn as_values_slice(&self) -> &'a [Value] { - self.0 - } -} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 537acf198..000000000 --- a/src/error.rs +++ /dev/null @@ -1,500 +0,0 @@ -use std::{ - any::Any, - collections::BTreeMap, - fmt::{self, Debug, Display, Formatter}, - marker::PhantomData, - sync::Arc, -}; - -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -use crate::{InputType, Pos, Value, parser}; - -/// Extensions to the error. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] -#[serde(transparent)] -pub struct ErrorExtensionValues(BTreeMap); - -impl ErrorExtensionValues { - /// Set an extension value. - pub fn set(&mut self, name: impl AsRef, value: impl Into) { - self.0.insert(name.as_ref().to_string(), value.into()); - } - - /// Unset an extension value. - pub fn unset(&mut self, name: impl AsRef) { - self.0.remove(name.as_ref()); - } - - /// Get an extension value. - pub fn get(&self, name: impl AsRef) -> Option<&Value> { - self.0.get(name.as_ref()) - } -} - -/// An error in a GraphQL server. -#[derive(Clone, Serialize, Deserialize)] -pub struct ServerError { - /// An explanatory message of the error. - pub message: String, - /// The source of the error. - #[serde(skip)] - pub source: Option>, - /// Where the error occurred. - #[serde(skip_serializing_if = "Vec::is_empty", default)] - pub locations: Vec, - /// If the error occurred in a resolver, the path to the error. - #[serde(skip_serializing_if = "Vec::is_empty", default)] - pub path: Vec, - /// Extensions to the error. - #[serde(skip_serializing_if = "error_extensions_is_empty", default)] - pub extensions: Option, -} - -fn error_extensions_is_empty(values: &Option) -> bool { - values.as_ref().is_none_or(|values| values.0.is_empty()) -} - -impl Debug for ServerError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("ServerError") - .field("message", &self.message) - .field("locations", &self.locations) - .field("path", &self.path) - .field("extensions", &self.extensions) - .finish() - } -} - -impl PartialEq for ServerError { - fn eq(&self, other: &Self) -> bool { - self.message.eq(&other.message) - && self.locations.eq(&other.locations) - && self.path.eq(&other.path) - && self.extensions.eq(&other.extensions) - } -} - -impl ServerError { - /// Create a new server error with the message. - pub fn new(message: impl Into, pos: Option) -> Self { - Self { - message: message.into(), - source: None, - locations: pos.map(|pos| vec![pos]).unwrap_or_default(), - path: Vec::new(), - extensions: None, - } - } - - /// Get the source of the error. - /// - /// # Examples - /// - /// ```rust - /// use std::io::ErrorKind; - /// - /// use async_graphql::*; - /// - /// struct Query; - /// - /// #[Object] - /// impl Query { - /// async fn value(&self) -> Result { - /// Err(Error::new_with_source(std::io::Error::other("my error"))) - /// } - /// } - /// - /// let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - /// - /// # tokio::runtime::Runtime::new().unwrap().block_on(async move { - /// let err = schema - /// .execute("{ value }") - /// .await - /// .into_result() - /// .unwrap_err() - /// .remove(0); - /// assert!(err.source::().is_some()); - /// # }); - /// ``` - pub fn source(&self) -> Option<&T> { - self.source.as_ref().map(|err| err.downcast_ref()).flatten() - } - - #[doc(hidden)] - #[must_use] - pub fn with_path(self, path: Vec) -> Self { - Self { path, ..self } - } -} - -impl Display for ServerError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str(&self.message) - } -} - -impl From for Vec { - fn from(single: ServerError) -> Self { - vec![single] - } -} - -impl From for ServerError { - fn from(e: parser::Error) -> Self { - Self { - message: e.to_string(), - source: None, - locations: e.positions().collect(), - path: Vec::new(), - extensions: None, - } - } -} - -/// A segment of path to a resolver. -/// -/// This is like [`QueryPathSegment`](enum.QueryPathSegment.html), but owned and -/// used as a part of errors instead of during execution. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(untagged)] -pub enum PathSegment { - /// A field in an object. - Field(String), - /// An index in a list. - Index(usize), -} - -/// Alias for `Result`. -pub type ServerResult = std::result::Result; - -/// An error parsing an input value. -/// -/// This type is generic over T as it uses T's type name when converting to a -/// regular error. -#[derive(Debug)] -pub struct InputValueError { - message: String, - extensions: Option, - phantom: PhantomData, -} - -impl InputValueError { - fn new(message: String, extensions: Option) -> Self { - Self { - message, - extensions, - phantom: PhantomData, - } - } - - /// The expected input type did not match the actual input type. - #[must_use] - pub fn expected_type(actual: Value) -> Self { - Self::new( - format!( - r#"Expected input type "{}", found {}."#, - T::type_name(), - actual - ), - None, - ) - } - - /// A custom error message. - /// - /// Any type that implements `Display` is automatically converted to this if - /// you use the `?` operator. - #[must_use] - pub fn custom(msg: impl Display) -> Self { - Self::new( - format!(r#"Failed to parse "{}": {}"#, T::type_name(), msg), - None, - ) - } - - /// Propagate the error message to a different type. - pub fn propagate(self) -> InputValueError { - if T::type_name() != U::type_name() { - InputValueError::new( - format!( - r#"{} (occurred while parsing "{}")"#, - self.message, - U::type_name() - ), - self.extensions, - ) - } else { - InputValueError::new(self.message, self.extensions) - } - } - - /// Set an extension value. - pub fn with_extension(mut self, name: impl AsRef, value: impl Into) -> Self { - self.extensions - .get_or_insert_with(ErrorExtensionValues::default) - .set(name, value); - self - } - - /// Convert the error into a server error. - pub fn into_server_error(self, pos: Pos) -> ServerError { - let mut err = ServerError::new(self.message, Some(pos)); - err.extensions = self.extensions; - err - } -} - -impl From for InputValueError { - fn from(error: E) -> Self { - Self::custom(error) - } -} - -/// An error parsing a value of type `T`. -pub type InputValueResult = Result>; - -/// An error with a message and optional extensions. -#[derive(Clone, Serialize)] -pub struct Error { - /// The error message. - pub message: String, - /// The source of the error. - #[serde(skip)] - pub source: Option>, - /// Extensions to the error. - #[serde(skip_serializing_if = "error_extensions_is_empty")] - pub extensions: Option, -} - -impl Debug for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("Error") - .field("message", &self.message) - .field("extensions", &self.extensions) - .finish() - } -} - -impl PartialEq for Error { - fn eq(&self, other: &Self) -> bool { - self.message.eq(&other.message) && self.extensions.eq(&other.extensions) - } -} - -impl Error { - /// Create an error from the given error message. - pub fn new(message: impl Into) -> Self { - Self { - message: message.into(), - source: None, - extensions: None, - } - } - - /// Create an error with a type that implements `Display`, and it will also - /// set the `source` of the error to this value. - pub fn new_with_source(source: impl Display + Send + Sync + 'static) -> Self { - Self { - message: source.to_string(), - source: Some(Arc::new(source)), - extensions: None, - } - } - - /// Convert the error to a server error. - #[must_use] - pub fn into_server_error(self, pos: Pos) -> ServerError { - ServerError { - message: self.message, - source: self.source, - locations: vec![pos], - path: Vec::new(), - extensions: self.extensions, - } - } -} - -#[cfg(not(feature = "custom-error-conversion"))] -impl From for Error { - fn from(e: T) -> Self { - Self { - message: e.to_string(), - source: Some(Arc::new(e)), - extensions: None, - } - } -} - -#[cfg(feature = "custom-error-conversion")] -impl From<&'static str> for Error { - fn from(e: &'static str) -> Self { - Self { - message: e.to_string(), - source: None, - extensions: None, - } - } -} - -#[cfg(feature = "custom-error-conversion")] -impl From for Error { - fn from(e: String) -> Self { - Self { - message: e, - source: None, - extensions: None, - } - } -} - -/// An alias for `Result`. -pub type Result = std::result::Result; - -/// An error parsing the request. -#[derive(Debug, Error)] -#[non_exhaustive] -pub enum ParseRequestError { - /// An IO error occurred. - #[error("{0}")] - Io(#[from] std::io::Error), - - /// The request's syntax was invalid. - #[error("Invalid request: {0}")] - InvalidRequest(Box), - - /// The request's files map was invalid. - #[error("Invalid files map: {0}")] - InvalidFilesMap(Box), - - /// The request's multipart data was invalid. - #[error("Invalid multipart data")] - InvalidMultipart(multer::Error), - - /// Missing "operators" part for multipart request. - #[error("Missing \"operators\" part")] - MissingOperatorsPart, - - /// Missing "map" part for multipart request. - #[error("Missing \"map\" part")] - MissingMapPart, - - /// It's not an upload operation - #[error("It's not an upload operation")] - NotUpload, - - /// Files were missing the request. - #[error("Missing files")] - MissingFiles, - - /// The request's payload is too large, and this server rejected it. - #[error("Payload too large")] - PayloadTooLarge, - - /// The request is a batch request, but the server does not support batch - /// requests. - #[error("Batch requests are not supported")] - UnsupportedBatch, -} - -impl From for ParseRequestError { - fn from(err: multer::Error) -> Self { - match err { - multer::Error::FieldSizeExceeded { .. } | multer::Error::StreamSizeExceeded { .. } => { - ParseRequestError::PayloadTooLarge - } - _ => ParseRequestError::InvalidMultipart(err), - } - } -} - -impl From for ParseRequestError { - fn from(e: mime::FromStrError) -> Self { - Self::InvalidRequest(Box::new(e)) - } -} - -/// An error which can be extended into a `Error`. -pub trait ErrorExtensions: Sized { - /// Convert the error to a `Error`. - fn extend(&self) -> Error; - - /// Add extensions to the error, using a callback to make the extensions. - fn extend_with(self, cb: C) -> Error - where - C: FnOnce(&Self, &mut ErrorExtensionValues), - { - let mut new_extensions = Default::default(); - cb(&self, &mut new_extensions); - - let Error { - message, - source, - extensions, - } = self.extend(); - - let mut extensions = extensions.unwrap_or_default(); - extensions.0.extend(new_extensions.0); - - Error { - message, - source, - extensions: Some(extensions), - } - } -} - -impl ErrorExtensions for Error { - fn extend(&self) -> Error { - self.clone() - } -} - -// implementing for &E instead of E gives the user the possibility to implement -// for E which does not conflict with this implementation acting as a fallback. -impl ErrorExtensions for &E { - fn extend(&self) -> Error { - Error { - message: self.to_string(), - source: None, - extensions: None, - } - } -} - -/// Extend a `Result`'s error value with -/// [`ErrorExtensions`](trait.ErrorExtensions.html). -pub trait ResultExt: Sized { - /// Extend the error value of the result with the callback. - fn extend_err(self, cb: C) -> Result - where - C: FnOnce(&E, &mut ErrorExtensionValues); - - /// Extend the result to a `Result`. - fn extend(self) -> Result; -} - -// This is implemented on E and not &E which means it cannot be used on foreign -// types. (see example). -impl ResultExt for std::result::Result -where - E: ErrorExtensions + Send + Sync + 'static, -{ - fn extend_err(self, cb: C) -> Result - where - C: FnOnce(&E, &mut ErrorExtensionValues), - { - match self { - Err(err) => Err(err.extend_with(|e, ee| cb(e, ee))), - Ok(value) => Ok(value), - } - } - - fn extend(self) -> Result { - match self { - Err(err) => Err(err.extend()), - Ok(value) => Ok(value), - } - } -} diff --git a/src/executor.rs b/src/executor.rs deleted file mode 100644 index e7d99d469..000000000 --- a/src/executor.rs +++ /dev/null @@ -1,61 +0,0 @@ -#[cfg(not(feature = "boxed-trait"))] -use std::future::Future; -use std::sync::Arc; - -use futures_util::stream::{BoxStream, FuturesOrdered, StreamExt}; - -use crate::{BatchRequest, BatchResponse, Data, Request, Response}; - -/// Represents a GraphQL executor -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -pub trait Executor: Unpin + Clone + Send + Sync + 'static { - /// Execute a GraphQL query. - #[cfg(feature = "boxed-trait")] - async fn execute(&self, request: Request) -> Response; - - /// Execute a GraphQL query. - #[cfg(not(feature = "boxed-trait"))] - fn execute(&self, request: Request) -> impl Future + Send; - - /// Execute a GraphQL batch query. - #[cfg(feature = "boxed-trait")] - async fn execute_batch(&self, batch_request: BatchRequest) -> BatchResponse { - match batch_request { - BatchRequest::Single(request) => BatchResponse::Single(self.execute(request).await), - BatchRequest::Batch(requests) => BatchResponse::Batch( - FuturesOrdered::from_iter( - requests.into_iter().map(|request| self.execute(request)), - ) - .collect() - .await, - ), - } - } - - /// Execute a GraphQL batch query. - #[cfg(not(feature = "boxed-trait"))] - fn execute_batch( - &self, - batch_request: BatchRequest, - ) -> impl Future + Send { - async { - match batch_request { - BatchRequest::Single(request) => BatchResponse::Single(self.execute(request).await), - BatchRequest::Batch(requests) => BatchResponse::Batch( - FuturesOrdered::from_iter( - requests.into_iter().map(|request| self.execute(request)), - ) - .collect() - .await, - ), - } - } - } - - /// Execute a GraphQL subscription with session data. - fn execute_stream( - &self, - request: Request, - session_data: Option>, - ) -> BoxStream<'static, Response>; -} diff --git a/src/extensions/analyzer.rs b/src/extensions/analyzer.rs deleted file mode 100644 index c90c386b9..000000000 --- a/src/extensions/analyzer.rs +++ /dev/null @@ -1,122 +0,0 @@ -use std::sync::Arc; - -use futures_util::lock::Mutex; - -use crate::{ - Response, ServerError, ValidationResult, - extensions::{Extension, ExtensionContext, ExtensionFactory, NextRequest, NextValidation}, - value, -}; - -/// Analyzer extension -/// -/// This extension will output the `analyzer` field containing `complexity` and -/// `depth` in the response extension of each query. -pub struct Analyzer; - -impl ExtensionFactory for Analyzer { - fn create(&self) -> Arc { - Arc::new(AnalyzerExtension::default()) - } -} - -#[derive(Default)] -struct AnalyzerExtension { - validation_result: Mutex>, -} - -#[async_trait::async_trait] -impl Extension for AnalyzerExtension { - async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response { - let mut resp = next.run(ctx).await; - let validation_result = self.validation_result.lock().await.take(); - if let Some(validation_result) = validation_result { - resp = resp.extension( - "analyzer", - value! ({ - "complexity": validation_result.complexity, - "depth": validation_result.depth, - }), - ); - } - resp - } - - async fn validation( - &self, - ctx: &ExtensionContext<'_>, - next: NextValidation<'_>, - ) -> Result> { - let res = next.run(ctx).await?; - *self.validation_result.lock().await = Some(res); - Ok(res) - } -} - -#[cfg(test)] -mod tests { - use crate::*; - - struct Query; - - #[derive(Copy, Clone)] - struct MyObj; - - #[Object(internal)] - impl MyObj { - async fn value(&self) -> i32 { - 1 - } - - async fn obj(&self) -> MyObj { - MyObj - } - } - - #[Object(internal)] - impl Query { - async fn value(&self) -> i32 { - 1 - } - - async fn obj(&self) -> MyObj { - MyObj - } - - #[graphql(complexity = "count * child_complexity")] - async fn objs(&self, count: usize) -> Vec { - vec![MyObj; count] - } - } - - #[tokio::test] - async fn analyzer() { - let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .extension(extensions::Analyzer) - .finish(); - - let res = schema - .execute( - r#"{ - value obj { - value obj { - value - } - } - objs(count: 10) { value } - }"#, - ) - .await - .into_result() - .unwrap() - .extensions - .remove("analyzer"); - assert_eq!( - res, - Some(value!({ - "complexity": 5 + 10, - "depth": 3, - })) - ); - } -} diff --git a/src/extensions/apollo_persisted_queries.rs b/src/extensions/apollo_persisted_queries.rs deleted file mode 100644 index 0906b5cb4..000000000 --- a/src/extensions/apollo_persisted_queries.rs +++ /dev/null @@ -1,203 +0,0 @@ -//! Apollo persisted queries extension. - -use std::{num::NonZeroUsize, sync::Arc}; - -use async_graphql_parser::types::ExecutableDocument; -use futures_util::lock::Mutex; -use serde::Deserialize; -use sha2::{Digest, Sha256}; - -use crate::{ - Request, ServerError, ServerResult, - extensions::{Extension, ExtensionContext, ExtensionFactory, NextPrepareRequest}, - from_value, -}; - -#[derive(Deserialize)] -struct PersistedQuery { - version: i32, - #[serde(rename = "sha256Hash")] - sha256_hash: String, -} - -/// Cache storage for persisted queries. -#[async_trait::async_trait] -pub trait CacheStorage: Send + Sync + Clone + 'static { - /// Load the query by `key`. - async fn get(&self, key: String) -> Option; - - /// Save the query by `key`. - async fn set(&self, key: String, query: ExecutableDocument); -} - -/// Memory-based LRU cache. -#[derive(Clone)] -pub struct LruCacheStorage(Arc>>); - -impl LruCacheStorage { - /// Creates a new LRU Cache that holds at most `cap` items. - pub fn new(cap: usize) -> Self { - Self(Arc::new(Mutex::new(lru::LruCache::new( - NonZeroUsize::new(cap).unwrap(), - )))) - } -} - -#[async_trait::async_trait] -impl CacheStorage for LruCacheStorage { - async fn get(&self, key: String) -> Option { - let mut cache = self.0.lock().await; - cache.get(&key).cloned() - } - - async fn set(&self, key: String, query: ExecutableDocument) { - let mut cache = self.0.lock().await; - cache.put(key, query); - } -} - -/// Apollo persisted queries extension. -/// -/// [Reference](https://www.apollographql.com/docs/react/api/link/persisted-queries/) -#[cfg_attr(docsrs, doc(cfg(feature = "apollo_persisted_queries")))] -pub struct ApolloPersistedQueries(T); - -impl ApolloPersistedQueries { - /// Creates an apollo persisted queries extension. - pub fn new(cache_storage: T) -> ApolloPersistedQueries { - Self(cache_storage) - } -} - -impl ExtensionFactory for ApolloPersistedQueries { - fn create(&self) -> Arc { - Arc::new(ApolloPersistedQueriesExtension { - storage: self.0.clone(), - }) - } -} - -struct ApolloPersistedQueriesExtension { - storage: T, -} - -#[async_trait::async_trait] -impl Extension for ApolloPersistedQueriesExtension { - async fn prepare_request( - &self, - ctx: &ExtensionContext<'_>, - mut request: Request, - next: NextPrepareRequest<'_>, - ) -> ServerResult { - let res = if let Some(value) = request.extensions.remove("persistedQuery") { - let persisted_query: PersistedQuery = from_value(value).map_err(|_| { - ServerError::new("Invalid \"PersistedQuery\" extension configuration.", None) - })?; - if persisted_query.version != 1 { - return Err(ServerError::new( - format!( - "Only the \"PersistedQuery\" extension of version \"1\" is supported, and the current version is \"{}\".", - persisted_query.version - ), - None, - )); - } - - if request.query.is_empty() { - if let Some(doc) = self.storage.get(persisted_query.sha256_hash).await { - Ok(Request { - parsed_query: Some(doc), - ..request - }) - } else { - Err(ServerError::new("PersistedQueryNotFound", None)) - } - } else { - let sha256_hash = format!("{:x}", Sha256::digest(request.query.as_bytes())); - - if persisted_query.sha256_hash != sha256_hash { - Err(ServerError::new("provided sha does not match query", None)) - } else { - let doc = async_graphql_parser::parse_query(&request.query)?; - self.storage.set(sha256_hash, doc.clone()).await; - Ok(Request { - query: String::new(), - parsed_query: Some(doc), - ..request - }) - } - } - } else { - Ok(request) - }; - next.run(ctx, res?).await - } -} - -#[cfg(test)] -mod tests { - #[tokio::test] - async fn test() { - use super::*; - use crate::*; - - struct Query; - - #[Object(internal)] - impl Query { - async fn value(&self) -> i32 { - 100 - } - } - - let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .extension(ApolloPersistedQueries::new(LruCacheStorage::new(256))) - .finish(); - - let mut request = Request::new("{ value }"); - request.extensions.insert( - "persistedQuery".to_string(), - value!({ - "version": 1, - "sha256Hash": "854174ebed716fe24fd6659c30290aecd9bc1d17dc4f47939a1848a1b8ed3c6b", - }), - ); - - assert_eq!( - schema.execute(request).await.into_result().unwrap().data, - value!({ - "value": 100 - }) - ); - - let mut request = Request::new(""); - request.extensions.insert( - "persistedQuery".to_string(), - value!({ - "version": 1, - "sha256Hash": "854174ebed716fe24fd6659c30290aecd9bc1d17dc4f47939a1848a1b8ed3c6b", - }), - ); - - assert_eq!( - schema.execute(request).await.into_result().unwrap().data, - value!({ - "value": 100 - }) - ); - - let mut request = Request::new(""); - request.extensions.insert( - "persistedQuery".to_string(), - value!({ - "version": 1, - "sha256Hash": "def", - }), - ); - - assert_eq!( - schema.execute(request).await.into_result().unwrap_err(), - vec![ServerError::new("PersistedQueryNotFound", None)] - ); - } -} diff --git a/src/extensions/apollo_tracing.rs b/src/extensions/apollo_tracing.rs deleted file mode 100644 index 83a8b4041..000000000 --- a/src/extensions/apollo_tracing.rs +++ /dev/null @@ -1,133 +0,0 @@ -use std::sync::Arc; - -use chrono::{DateTime, Utc}; -use futures_util::lock::Mutex; -use serde::{Serialize, Serializer, ser::SerializeMap}; - -use crate::{ - Response, ServerResult, Value, - extensions::{ - Extension, ExtensionContext, ExtensionFactory, NextExecute, NextResolve, ResolveInfo, - }, - value, -}; - -struct ResolveState { - path: Vec, - field_name: String, - parent_type: String, - return_type: String, - start_time: DateTime, - end_time: DateTime, - start_offset: i64, -} - -impl Serialize for ResolveState { - fn serialize(&self, serializer: S) -> Result { - let mut map = serializer.serialize_map(None)?; - map.serialize_entry("path", &self.path)?; - map.serialize_entry("fieldName", &self.field_name)?; - map.serialize_entry("parentType", &self.parent_type)?; - map.serialize_entry("returnType", &self.return_type)?; - map.serialize_entry("startOffset", &self.start_offset)?; - map.serialize_entry( - "duration", - &(self.end_time - self.start_time).num_nanoseconds(), - )?; - map.end() - } -} - -/// Apollo tracing extension for performance tracing -/// -/// Apollo Tracing works by including data in the extensions field of the -/// GraphQL response, which is reserved by the GraphQL spec for extra -/// information that a server wants to return. That way, you have access to -/// performance traces alongside the data returned by your query. It's already -/// supported by `Apollo Engine`, and we're excited to see what other kinds of -/// integrations people can build on top of this format. -#[cfg_attr(docsrs, doc(cfg(feature = "apollo_tracing")))] -pub struct ApolloTracing; - -impl ExtensionFactory for ApolloTracing { - fn create(&self) -> Arc { - Arc::new(ApolloTracingExtension { - inner: Mutex::new(Inner { - start_time: Utc::now(), - end_time: Utc::now(), - resolves: Default::default(), - }), - }) - } -} - -struct Inner { - start_time: DateTime, - end_time: DateTime, - resolves: Vec, -} - -struct ApolloTracingExtension { - inner: Mutex, -} - -#[async_trait::async_trait] -impl Extension for ApolloTracingExtension { - async fn execute( - &self, - ctx: &ExtensionContext<'_>, - operation_name: Option<&str>, - next: NextExecute<'_>, - ) -> Response { - self.inner.lock().await.start_time = Utc::now(); - let resp = next.run(ctx, operation_name).await; - - let mut inner = self.inner.lock().await; - inner.end_time = Utc::now(); - inner - .resolves - .sort_by(|a, b| a.start_offset.cmp(&b.start_offset)); - resp.extension( - "tracing", - value!({ - "version": 1, - "startTime": inner.start_time.to_rfc3339(), - "endTime": inner.end_time.to_rfc3339(), - "duration": (inner.end_time - inner.start_time).num_nanoseconds(), - "execution": { - "resolvers": inner.resolves - } - }), - ) - } - - async fn resolve( - &self, - ctx: &ExtensionContext<'_>, - info: ResolveInfo<'_>, - next: NextResolve<'_>, - ) -> ServerResult> { - let path = info.path_node.to_string_vec(); - let field_name = info.path_node.field_name().to_string(); - let parent_type = info.parent_type.to_string(); - let return_type = info.return_type.to_string(); - let start_time = Utc::now(); - let start_offset = (start_time - self.inner.lock().await.start_time) - .num_nanoseconds() - .unwrap(); - - let res = next.run(ctx, info).await; - let end_time = Utc::now(); - - self.inner.lock().await.resolves.push(ResolveState { - path, - field_name, - parent_type, - return_type, - start_time, - end_time, - start_offset, - }); - res - } -} diff --git a/src/extensions/logger.rs b/src/extensions/logger.rs deleted file mode 100644 index cb9caeb7d..000000000 --- a/src/extensions/logger.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::{fmt::Write, sync::Arc}; - -use crate::{ - PathSegment, Response, ServerResult, Variables, - extensions::{Extension, ExtensionContext, ExtensionFactory, NextExecute, NextParseQuery}, - parser::types::{ExecutableDocument, OperationType, Selection}, -}; - -/// Logger extension -#[cfg_attr(docsrs, doc(cfg(feature = "log")))] -pub struct Logger; - -impl ExtensionFactory for Logger { - fn create(&self) -> Arc { - Arc::new(LoggerExtension) - } -} - -struct LoggerExtension; - -#[async_trait::async_trait] -impl Extension for LoggerExtension { - async fn parse_query( - &self, - ctx: &ExtensionContext<'_>, - query: &str, - variables: &Variables, - next: NextParseQuery<'_>, - ) -> ServerResult { - let document = next.run(ctx, query, variables).await?; - let is_schema = document - .operations - .iter() - .filter(|(_, operation)| operation.node.ty == OperationType::Query) - .any(|(_, operation)| operation.node.selection_set.node.items.iter().any(|selection| matches!(&selection.node, Selection::Field(field) if field.node.name.node == "__schema"))); - if !is_schema { - log::info!( - target: "async-graphql", - "[Execute] {}", ctx.stringify_execute_doc(&document, variables) - ); - } - Ok(document) - } - - async fn execute( - &self, - ctx: &ExtensionContext<'_>, - operation_name: Option<&str>, - next: NextExecute<'_>, - ) -> Response { - let resp = next.run(ctx, operation_name).await; - if resp.is_err() { - for err in &resp.errors { - if !err.path.is_empty() { - let mut path = String::new(); - for (idx, s) in err.path.iter().enumerate() { - if idx > 0 { - path.push('.'); - } - match s { - PathSegment::Index(idx) => { - let _ = write!(&mut path, "{}", idx); - } - PathSegment::Field(name) => { - let _ = write!(&mut path, "{}", name); - } - } - } - - log::info!( - target: "async-graphql", - "[Error] path={} message={}", path, err.message, - ); - } else { - log::info!( - target: "async-graphql", - "[Error] message={}", err.message, - ); - } - } - } - resp - } -} diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs deleted file mode 100644 index 6baa67962..000000000 --- a/src/extensions/mod.rs +++ /dev/null @@ -1,575 +0,0 @@ -//! Extensions for schema - -mod analyzer; -#[cfg(feature = "apollo_persisted_queries")] -pub mod apollo_persisted_queries; -#[cfg(feature = "apollo_tracing")] -mod apollo_tracing; -#[cfg(feature = "log")] -mod logger; -#[cfg(feature = "opentelemetry")] -mod opentelemetry; -#[cfg(feature = "tracing")] -mod tracing; - -use std::{ - any::{Any, TypeId}, - future::Future, - sync::Arc, -}; - -use futures_util::{FutureExt, future::BoxFuture, stream::BoxStream}; - -pub use self::analyzer::Analyzer; -#[cfg(feature = "apollo_tracing")] -pub use self::apollo_tracing::ApolloTracing; -#[cfg(feature = "log")] -pub use self::logger::Logger; -#[cfg(feature = "opentelemetry")] -pub use self::opentelemetry::OpenTelemetry; -#[cfg(feature = "tracing")] -pub use self::tracing::Tracing; -use crate::{ - Data, DataContext, Error, QueryPathNode, Request, Response, Result, SDLExportOptions, - SchemaEnv, ServerError, ServerResult, ValidationResult, Value, Variables, - parser::types::{ExecutableDocument, Field}, -}; - -/// Context for extension -pub struct ExtensionContext<'a> { - /// Schema-scope context data, [`Registry`], and custom directives. - pub schema_env: &'a SchemaEnv, - - /// Extension-scoped context data shared across all extensions. - /// - /// Can be accessed only from hooks that implement the [`Extension`] trait. - /// - /// It is created with each new [`Request`] and is empty by default. - /// - /// For subscriptions, the session ends when the subscription is closed. - pub session_data: &'a Data, - - /// Request-scoped context data shared across all resolvers. - /// - /// This is a reference to [`Request::data`](Request) field. - /// If the request has not initialized yet, the value is seen as `None` - /// inside the [`Extension::request`], [`Extension::subscribe`], and - /// [`Extension::prepare_request`] hooks. - pub query_data: Option<&'a Data>, -} - -impl<'a> DataContext<'a> for ExtensionContext<'a> { - fn data(&self) -> Result<&'a D> { - ExtensionContext::data::(self) - } - - fn data_unchecked(&self) -> &'a D { - ExtensionContext::data_unchecked::(self) - } - - fn data_opt(&self) -> Option<&'a D> { - ExtensionContext::data_opt::(self) - } -} - -impl<'a> ExtensionContext<'a> { - /// Convert the specified [ExecutableDocument] into a query string. - /// - /// Usually used for log extension, it can hide secret arguments. - pub fn stringify_execute_doc(&self, doc: &ExecutableDocument, variables: &Variables) -> String { - self.schema_env - .registry - .stringify_exec_doc(variables, doc) - .unwrap_or_default() - } - - /// Returns SDL(Schema Definition Language) of this schema. - pub fn sdl(&self) -> String { - self.schema_env.registry.export_sdl(Default::default()) - } - - /// Returns SDL(Schema Definition Language) of this schema with options. - pub fn sdl_with_options(&self, options: SDLExportOptions) -> String { - self.schema_env.registry.export_sdl(options) - } - - /// Gets the global data defined in the `Context` or `Schema`. - /// - /// If both `Schema` and `Query` have the same data type, the data in the - /// `Query` is obtained. - /// - /// # Errors - /// - /// Returns a `Error` if the specified type data does not exist. - pub fn data(&self) -> Result<&'a D> { - self.data_opt::().ok_or_else(|| { - Error::new(format!( - "Data `{}` does not exist.", - std::any::type_name::() - )) - }) - } - - /// Gets the global data defined in the `Context` or `Schema`. - /// - /// # Panics - /// - /// It will panic if the specified data type does not exist. - pub fn data_unchecked(&self) -> &'a D { - self.data_opt::() - .unwrap_or_else(|| panic!("Data `{}` does not exist.", std::any::type_name::())) - } - - /// Gets the global data defined in the `Context` or `Schema` or `None` if - /// the specified type data does not exist. - pub fn data_opt(&self) -> Option<&'a D> { - self.query_data - .and_then(|query_data| query_data.get(&TypeId::of::())) - .or_else(|| self.session_data.get(&TypeId::of::())) - .or_else(|| self.schema_env.data.get(&TypeId::of::())) - .and_then(|d| d.downcast_ref::()) - } -} - -/// Parameters for `Extension::resolve_field_start` -pub struct ResolveInfo<'a> { - /// Current path node, You can go through the entire path. - pub path_node: &'a QueryPathNode<'a>, - - /// Parent type - pub parent_type: &'a str, - - /// Current return type, is qualified name. - pub return_type: &'a str, - - /// Current field name - pub name: &'a str, - - /// Current field alias - pub alias: Option<&'a str>, - - /// If `true` means the current field is for introspection. - pub is_for_introspection: bool, - - /// Current field - pub field: &'a Field, -} - -type RequestFut<'a> = &'a mut (dyn Future + Send + Unpin); - -type ParseFut<'a> = &'a mut (dyn Future> + Send + Unpin); - -type ValidationFut<'a> = - &'a mut (dyn Future>> + Send + Unpin); - -type ExecuteFutFactory<'a> = Box) -> BoxFuture<'a, Response> + Send + 'a>; - -/// A future type used to resolve the field -pub type ResolveFut<'a> = &'a mut (dyn Future>> + Send + Unpin); - -/// The remainder of a extension chain for request. -pub struct NextRequest<'a> { - chain: &'a [Arc], - request_fut: RequestFut<'a>, -} - -impl NextRequest<'_> { - /// Call the [Extension::request] function of next extension. - pub async fn run(self, ctx: &ExtensionContext<'_>) -> Response { - if let Some((first, next)) = self.chain.split_first() { - first - .request( - ctx, - NextRequest { - chain: next, - request_fut: self.request_fut, - }, - ) - .await - } else { - self.request_fut.await - } - } -} - -/// The remainder of a extension chain for subscribe. -pub struct NextSubscribe<'a> { - chain: &'a [Arc], -} - -impl NextSubscribe<'_> { - /// Call the [Extension::subscribe] function of next extension. - pub fn run<'s>( - self, - ctx: &ExtensionContext<'_>, - stream: BoxStream<'s, Response>, - ) -> BoxStream<'s, Response> { - if let Some((first, next)) = self.chain.split_first() { - first.subscribe(ctx, stream, NextSubscribe { chain: next }) - } else { - stream - } - } -} - -/// The remainder of a extension chain for subscribe. -pub struct NextPrepareRequest<'a> { - chain: &'a [Arc], -} - -impl NextPrepareRequest<'_> { - /// Call the [Extension::prepare_request] function of next extension. - pub async fn run(self, ctx: &ExtensionContext<'_>, request: Request) -> ServerResult { - if let Some((first, next)) = self.chain.split_first() { - first - .prepare_request(ctx, request, NextPrepareRequest { chain: next }) - .await - } else { - Ok(request) - } - } -} - -/// The remainder of a extension chain for parse query. -pub struct NextParseQuery<'a> { - chain: &'a [Arc], - parse_query_fut: ParseFut<'a>, -} - -impl NextParseQuery<'_> { - /// Call the [Extension::parse_query] function of next extension. - pub async fn run( - self, - ctx: &ExtensionContext<'_>, - query: &str, - variables: &Variables, - ) -> ServerResult { - if let Some((first, next)) = self.chain.split_first() { - first - .parse_query( - ctx, - query, - variables, - NextParseQuery { - chain: next, - parse_query_fut: self.parse_query_fut, - }, - ) - .await - } else { - self.parse_query_fut.await - } - } -} - -/// The remainder of a extension chain for validation. -pub struct NextValidation<'a> { - chain: &'a [Arc], - validation_fut: ValidationFut<'a>, -} - -impl NextValidation<'_> { - /// Call the [Extension::validation] function of next extension. - pub async fn run( - self, - ctx: &ExtensionContext<'_>, - ) -> Result> { - if let Some((first, next)) = self.chain.split_first() { - first - .validation( - ctx, - NextValidation { - chain: next, - validation_fut: self.validation_fut, - }, - ) - .await - } else { - self.validation_fut.await - } - } -} - -/// The remainder of a extension chain for execute. -pub struct NextExecute<'a> { - chain: &'a [Arc], - execute_fut_factory: ExecuteFutFactory<'a>, - execute_data: Option, -} - -impl NextExecute<'_> { - async fn internal_run( - self, - ctx: &ExtensionContext<'_>, - operation_name: Option<&str>, - data: Option, - ) -> Response { - let execute_data = match (self.execute_data, data) { - (Some(mut data1), Some(data2)) => { - data1.merge(data2); - Some(data1) - } - (Some(data), None) => Some(data), - (None, Some(data)) => Some(data), - (None, None) => None, - }; - - if let Some((first, next)) = self.chain.split_first() { - first - .execute( - ctx, - operation_name, - NextExecute { - chain: next, - execute_fut_factory: self.execute_fut_factory, - execute_data, - }, - ) - .await - } else { - (self.execute_fut_factory)(execute_data).await - } - } - - /// Call the [Extension::execute] function of next extension. - pub async fn run(self, ctx: &ExtensionContext<'_>, operation_name: Option<&str>) -> Response { - self.internal_run(ctx, operation_name, None).await - } - - /// Call the [Extension::execute] function of next extension with context - /// data. - pub async fn run_with_data( - self, - ctx: &ExtensionContext<'_>, - operation_name: Option<&str>, - data: Data, - ) -> Response { - self.internal_run(ctx, operation_name, Some(data)).await - } -} - -/// The remainder of a extension chain for resolve. -pub struct NextResolve<'a> { - chain: &'a [Arc], - resolve_fut: ResolveFut<'a>, -} - -impl NextResolve<'_> { - /// Call the [Extension::resolve] function of next extension. - pub async fn run( - self, - ctx: &ExtensionContext<'_>, - info: ResolveInfo<'_>, - ) -> ServerResult> { - if let Some((first, next)) = self.chain.split_first() { - first - .resolve( - ctx, - info, - NextResolve { - chain: next, - resolve_fut: self.resolve_fut, - }, - ) - .await - } else { - self.resolve_fut.await - } - } -} - -/// Represents a GraphQL extension -#[async_trait::async_trait] -pub trait Extension: Sync + Send + 'static { - /// Called at start query/mutation request. - async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response { - next.run(ctx).await - } - - /// Called at subscribe request. - fn subscribe<'s>( - &self, - ctx: &ExtensionContext<'_>, - stream: BoxStream<'s, Response>, - next: NextSubscribe<'_>, - ) -> BoxStream<'s, Response> { - next.run(ctx, stream) - } - - /// Called at prepare request. - async fn prepare_request( - &self, - ctx: &ExtensionContext<'_>, - request: Request, - next: NextPrepareRequest<'_>, - ) -> ServerResult { - next.run(ctx, request).await - } - - /// Called at parse query. - async fn parse_query( - &self, - ctx: &ExtensionContext<'_>, - query: &str, - variables: &Variables, - next: NextParseQuery<'_>, - ) -> ServerResult { - next.run(ctx, query, variables).await - } - - /// Called at validation query. - async fn validation( - &self, - ctx: &ExtensionContext<'_>, - next: NextValidation<'_>, - ) -> Result> { - next.run(ctx).await - } - - /// Called at execute query. - async fn execute( - &self, - ctx: &ExtensionContext<'_>, - operation_name: Option<&str>, - next: NextExecute<'_>, - ) -> Response { - next.run(ctx, operation_name).await - } - - /// Called at resolve field. - async fn resolve( - &self, - ctx: &ExtensionContext<'_>, - info: ResolveInfo<'_>, - next: NextResolve<'_>, - ) -> ServerResult> { - next.run(ctx, info).await - } -} - -/// Extension factory -/// -/// Used to create an extension instance. -pub trait ExtensionFactory: Send + Sync + 'static { - /// Create an extended instance. - fn create(&self) -> Arc; -} - -#[derive(Clone)] -#[doc(hidden)] -pub struct Extensions { - extensions: Vec>, - schema_env: SchemaEnv, - session_data: Arc, - query_data: Option>, -} - -#[doc(hidden)] -impl Extensions { - pub(crate) fn new( - extensions: impl IntoIterator>, - schema_env: SchemaEnv, - session_data: Arc, - ) -> Self { - Extensions { - extensions: extensions.into_iter().collect(), - schema_env, - session_data, - query_data: None, - } - } - - #[inline] - pub(crate) fn attach_query_data(&mut self, data: Arc) { - self.query_data = Some(data); - } - - #[inline] - pub(crate) fn is_empty(&self) -> bool { - self.extensions.is_empty() - } - - #[inline] - fn create_context(&self) -> ExtensionContext { - ExtensionContext { - schema_env: &self.schema_env, - session_data: &self.session_data, - query_data: self.query_data.as_deref(), - } - } - - pub async fn request(&self, request_fut: RequestFut<'_>) -> Response { - let next = NextRequest { - chain: &self.extensions, - request_fut, - }; - next.run(&self.create_context()).await - } - - pub fn subscribe<'s>(&self, stream: BoxStream<'s, Response>) -> BoxStream<'s, Response> { - let next = NextSubscribe { - chain: &self.extensions, - }; - next.run(&self.create_context(), stream) - } - - pub async fn prepare_request(&self, request: Request) -> ServerResult { - let next = NextPrepareRequest { - chain: &self.extensions, - }; - next.run(&self.create_context(), request).await - } - - pub async fn parse_query( - &self, - query: &str, - variables: &Variables, - parse_query_fut: ParseFut<'_>, - ) -> ServerResult { - let next = NextParseQuery { - chain: &self.extensions, - parse_query_fut, - }; - next.run(&self.create_context(), query, variables).await - } - - pub async fn validation( - &self, - validation_fut: ValidationFut<'_>, - ) -> Result> { - let next = NextValidation { - chain: &self.extensions, - validation_fut, - }; - next.run(&self.create_context()).await - } - - pub async fn execute<'a, 'b, F, T>( - &'a self, - operation_name: Option<&str>, - execute_fut_factory: F, - ) -> Response - where - F: FnOnce(Option) -> T + Send + 'a, - T: Future + Send + 'a, - { - let next = NextExecute { - chain: &self.extensions, - execute_fut_factory: Box::new(|data| execute_fut_factory(data).boxed()), - execute_data: None, - }; - next.run(&self.create_context(), operation_name).await - } - - pub async fn resolve( - &self, - info: ResolveInfo<'_>, - resolve_fut: ResolveFut<'_>, - ) -> ServerResult> { - let next = NextResolve { - chain: &self.extensions, - resolve_fut, - }; - next.run(&self.create_context(), info).await - } -} diff --git a/src/extensions/opentelemetry.rs b/src/extensions/opentelemetry.rs deleted file mode 100644 index 5f73d8880..000000000 --- a/src/extensions/opentelemetry.rs +++ /dev/null @@ -1,206 +0,0 @@ -use std::sync::Arc; - -use async_graphql_parser::types::ExecutableDocument; -use async_graphql_value::Variables; -use futures_util::{TryFutureExt, stream::BoxStream}; -use opentelemetry::{ - Context as OpenTelemetryContext, Key, KeyValue, - trace::{FutureExt, SpanKind, TraceContextExt, Tracer}, -}; - -use crate::{ - Response, ServerError, ServerResult, ValidationResult, Value, - extensions::{ - Extension, ExtensionContext, ExtensionFactory, NextExecute, NextParseQuery, NextRequest, - NextResolve, NextSubscribe, NextValidation, ResolveInfo, - }, -}; - -const KEY_SOURCE: Key = Key::from_static_str("graphql.source"); -const KEY_VARIABLES: Key = Key::from_static_str("graphql.variables"); -const KEY_PARENT_TYPE: Key = Key::from_static_str("graphql.parentType"); -const KEY_RETURN_TYPE: Key = Key::from_static_str("graphql.returnType"); -const KEY_ERROR: Key = Key::from_static_str("graphql.error"); -const KEY_COMPLEXITY: Key = Key::from_static_str("graphql.complexity"); -const KEY_DEPTH: Key = Key::from_static_str("graphql.depth"); - -/// OpenTelemetry extension -#[cfg_attr(docsrs, doc(cfg(feature = "opentelemetry")))] -pub struct OpenTelemetry { - tracer: Arc, -} - -impl OpenTelemetry { - /// Use `tracer` to create an OpenTelemetry extension. - pub fn new(tracer: T) -> OpenTelemetry - where - T: Tracer + Send + Sync + 'static, - ::Span: Sync + Send, - { - Self { - tracer: Arc::new(tracer), - } - } -} - -impl ExtensionFactory for OpenTelemetry -where - T: Tracer + Send + Sync + 'static, - ::Span: Sync + Send, -{ - fn create(&self) -> Arc { - Arc::new(OpenTelemetryExtension { - tracer: self.tracer.clone(), - }) - } -} - -struct OpenTelemetryExtension { - tracer: Arc, -} - -#[async_trait::async_trait] -impl Extension for OpenTelemetryExtension -where - T: Tracer + Send + Sync + 'static, - ::Span: Sync + Send, -{ - async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response { - next.run(ctx) - .with_context(OpenTelemetryContext::current_with_span( - self.tracer - .span_builder("request") - .with_kind(SpanKind::Server) - .start(&*self.tracer), - )) - .await - } - - fn subscribe<'s>( - &self, - ctx: &ExtensionContext<'_>, - stream: BoxStream<'s, Response>, - next: NextSubscribe<'_>, - ) -> BoxStream<'s, Response> { - Box::pin( - next.run(ctx, stream) - .with_context(OpenTelemetryContext::current_with_span( - self.tracer - .span_builder("subscribe") - .with_kind(SpanKind::Server) - .start(&*self.tracer), - )), - ) - } - - async fn parse_query( - &self, - ctx: &ExtensionContext<'_>, - query: &str, - variables: &Variables, - next: NextParseQuery<'_>, - ) -> ServerResult { - let attributes = vec![ - KeyValue::new(KEY_SOURCE, query.to_string()), - KeyValue::new(KEY_VARIABLES, serde_json::to_string(variables).unwrap()), - ]; - let span = self - .tracer - .span_builder("parse") - .with_kind(SpanKind::Server) - .with_attributes(attributes) - .start(&*self.tracer); - - async move { - let res = next.run(ctx, query, variables).await; - if let Ok(doc) = &res { - OpenTelemetryContext::current() - .span() - .set_attribute(KeyValue::new( - KEY_SOURCE, - ctx.stringify_execute_doc(doc, variables), - )); - } - res - } - .with_context(OpenTelemetryContext::current_with_span(span)) - .await - } - - async fn validation( - &self, - ctx: &ExtensionContext<'_>, - next: NextValidation<'_>, - ) -> Result> { - let span = self - .tracer - .span_builder("validation") - .with_kind(SpanKind::Server) - .start(&*self.tracer); - next.run(ctx) - .with_context(OpenTelemetryContext::current_with_span(span)) - .map_ok(|res| { - let current_cx = OpenTelemetryContext::current(); - let span = current_cx.span(); - span.set_attribute(KeyValue::new(KEY_COMPLEXITY, res.complexity as i64)); - span.set_attribute(KeyValue::new(KEY_DEPTH, res.depth as i64)); - res - }) - .await - } - - async fn execute( - &self, - ctx: &ExtensionContext<'_>, - operation_name: Option<&str>, - next: NextExecute<'_>, - ) -> Response { - let span = self - .tracer - .span_builder("execute") - .with_kind(SpanKind::Server) - .start(&*self.tracer); - next.run(ctx, operation_name) - .with_context(OpenTelemetryContext::current_with_span(span)) - .await - } - - async fn resolve( - &self, - ctx: &ExtensionContext<'_>, - info: ResolveInfo<'_>, - next: NextResolve<'_>, - ) -> ServerResult> { - let span = if !info.is_for_introspection { - let attributes = vec![ - KeyValue::new(KEY_PARENT_TYPE, info.parent_type.to_string()), - KeyValue::new(KEY_RETURN_TYPE, info.return_type.to_string()), - ]; - Some( - self.tracer - .span_builder(info.path_node.to_string()) - .with_kind(SpanKind::Server) - .with_attributes(attributes) - .start(&*self.tracer), - ) - } else { - None - }; - - let fut = next.run(ctx, info).inspect_err(|err| { - let current_cx = OpenTelemetryContext::current(); - current_cx.span().add_event( - "error".to_string(), - vec![KeyValue::new(KEY_ERROR, err.to_string())], - ); - }); - - match span { - Some(span) => { - fut.with_context(OpenTelemetryContext::current_with_span(span)) - .await - } - None => fut.await, - } - } -} diff --git a/src/extensions/tracing.rs b/src/extensions/tracing.rs deleted file mode 100644 index 066066f26..000000000 --- a/src/extensions/tracing.rs +++ /dev/null @@ -1,159 +0,0 @@ -use std::sync::Arc; - -use futures_util::{TryFutureExt, stream::BoxStream}; -use tracing_futures::Instrument; -use tracinglib::{Level, span}; - -use crate::{ - Response, ServerError, ServerResult, ValidationResult, Value, Variables, - extensions::{ - Extension, ExtensionContext, ExtensionFactory, NextExecute, NextParseQuery, NextRequest, - NextResolve, NextSubscribe, NextValidation, ResolveInfo, - }, - parser::types::ExecutableDocument, -}; - -/// Tracing extension -/// -/// # References -/// -/// -/// -/// # Examples -/// -/// ```no_run -/// use async_graphql::{extensions::Tracing, *}; -/// -/// #[derive(SimpleObject)] -/// struct Query { -/// value: i32, -/// } -/// -/// let schema = Schema::build(Query { value: 100 }, EmptyMutation, EmptySubscription) -/// .extension(Tracing) -/// .finish(); -/// -/// # tokio::runtime::Runtime::new().unwrap().block_on(async { -/// schema.execute(Request::new("{ value }")).await; -/// # }); -/// ``` -#[cfg_attr(docsrs, doc(cfg(feature = "tracing")))] -pub struct Tracing; - -impl ExtensionFactory for Tracing { - fn create(&self) -> Arc { - Arc::new(TracingExtension) - } -} - -struct TracingExtension; - -#[async_trait::async_trait] -impl Extension for TracingExtension { - async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response { - next.run(ctx) - .instrument(span!( - target: "async_graphql::graphql", - Level::INFO, - "request", - )) - .await - } - - fn subscribe<'s>( - &self, - ctx: &ExtensionContext<'_>, - stream: BoxStream<'s, Response>, - next: NextSubscribe<'_>, - ) -> BoxStream<'s, Response> { - Box::pin(next.run(ctx, stream).instrument(span!( - target: "async_graphql::graphql", - Level::INFO, - "subscribe", - ))) - } - - async fn parse_query( - &self, - ctx: &ExtensionContext<'_>, - query: &str, - variables: &Variables, - next: NextParseQuery<'_>, - ) -> ServerResult { - let span = span!( - target: "async_graphql::graphql", - Level::INFO, - "parse", - source = tracinglib::field::Empty - ); - async move { - let res = next.run(ctx, query, variables).await; - if let Ok(doc) = &res { - tracinglib::Span::current() - .record("source", ctx.stringify_execute_doc(doc, variables).as_str()); - } - res - } - .instrument(span) - .await - } - - async fn validation( - &self, - ctx: &ExtensionContext<'_>, - next: NextValidation<'_>, - ) -> Result> { - let span = span!( - target: "async_graphql::graphql", - Level::INFO, - "validation" - ); - next.run(ctx).instrument(span).await - } - - async fn execute( - &self, - ctx: &ExtensionContext<'_>, - operation_name: Option<&str>, - next: NextExecute<'_>, - ) -> Response { - let span = span!( - target: "async_graphql::graphql", - Level::INFO, - "execute" - ); - next.run(ctx, operation_name).instrument(span).await - } - - async fn resolve( - &self, - ctx: &ExtensionContext<'_>, - info: ResolveInfo<'_>, - next: NextResolve<'_>, - ) -> ServerResult> { - let span = if !info.is_for_introspection { - Some(span!( - target: "async_graphql::graphql", - Level::INFO, - "field", - path = %info.path_node, - parent_type = %info.parent_type, - return_type = %info.return_type, - )) - } else { - None - }; - - let fut = next.run(ctx, info).inspect_err(|err| { - tracinglib::info!( - target: "async_graphql::graphql", - error = %err.message, - "error", - ); - }); - match span { - Some(span) => fut.instrument(span).await, - None => fut.await, - } - } -} diff --git a/src/guard.rs b/src/guard.rs deleted file mode 100644 index 24e5f5661..000000000 --- a/src/guard.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! Field guards -#[cfg(not(feature = "boxed-trait"))] -use std::future::Future; - -use crate::{Context, Result}; - -/// Field guard -/// -/// Guard is a pre-condition for a field that is resolved if `Ok(())` is -/// returned, otherwise an error is returned. -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -pub trait Guard { - /// Check whether the guard will allow access to the field. - #[cfg(feature = "boxed-trait")] - async fn check(&self, ctx: &Context<'_>) -> Result<()>; - - /// Check whether the guard will allow access to the field. - #[cfg(not(feature = "boxed-trait"))] - fn check(&self, ctx: &Context<'_>) -> impl Future> + Send; -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl Guard for T -where - T: Fn(&Context<'_>) -> Result<()> + Send + Sync + 'static, -{ - async fn check(&self, ctx: &Context<'_>) -> Result<()> { - self(ctx) - } -} - -/// An extension trait for `Guard`. -pub trait GuardExt: Guard + Sized { - /// Perform `and` operator on two rules - fn and(self, other: R) -> And { - And(self, other) - } - - /// Perform `or` operator on two rules - fn or(self, other: R) -> Or { - Or(self, other) - } -} - -impl GuardExt for T {} - -/// Guard for [`GuardExt::and`](trait.GuardExt.html#method.and). -pub struct And(A, B); - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl Guard for And { - async fn check(&self, ctx: &Context<'_>) -> Result<()> { - self.0.check(ctx).await?; - self.1.check(ctx).await - } -} - -/// Guard for [`GuardExt::or`](trait.GuardExt.html#method.or). -pub struct Or(A, B); - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl Guard for Or { - async fn check(&self, ctx: &Context<'_>) -> Result<()> { - if self.0.check(ctx).await.is_ok() { - return Ok(()); - } - self.1.check(ctx).await - } -} diff --git a/src/http/altair_source.hbs b/src/http/altair_source.hbs deleted file mode 100644 index 907d8f6bd..000000000 --- a/src/http/altair_source.hbs +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - {{#if title}} - {{ title }} - {{else}} - Altair - {{/if}} - - - - - - - - - - - - -
-
-
- Altair -
-
- - - -
-
-
-
- - - - - - \ No newline at end of file diff --git a/src/http/altair_source.rs b/src/http/altair_source.rs deleted file mode 100644 index ead4e0197..000000000 --- a/src/http/altair_source.rs +++ /dev/null @@ -1,675 +0,0 @@ -use std::collections::HashMap; - -use handlebars::Handlebars; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -/// A builder for constructing an Altair HTML page. -#[derive(Default, Serialize)] -pub struct AltairSource<'a> { - #[serde(default, skip_serializing_if = "Option::is_none")] - title: Option<&'a str>, - #[serde(default, skip_serializing_if = "Option::is_none")] - options: Option, -} -impl<'a> AltairSource<'a> { - /// Creates a builder for constructing an Altair HTML page. - pub fn build() -> AltairSource<'a> { - Default::default() - } - - /// Sets the html document title. - pub fn title(self, title: &'a str) -> AltairSource<'a> { - AltairSource { - title: Some(title), - ..self - } - } - - /// Sets the [Altair options](https://github.com/altair-graphql/altair?tab=readme-ov-file#configuration-options). - /// - /// # Examples - /// - /// With on-the-fly options: - /// ```rust - /// use async_graphql::http::*; - /// use serde_json::json; - /// - /// AltairSource::build() - /// .options(json!({ - /// "endpointURL": "/", - /// "subscriptionsEndpoint": "/ws", - /// "subscriptionsProtocol": "wss", - /// })) - /// .finish(); - /// ``` - /// - /// With strongly-typed [AltairConfigOptions], useful when reading options - /// from config files: ```rust - /// use async_graphql::http::*; - /// - /// AltairSource::build() - /// .options(AltairConfigOptions { - /// window_options: Some(AltairWindowOptions { - /// endpoint_url: Some("/".to_owned()), - /// subscriptions_endpoint: Some("/ws".to_owned()), - /// subscriptions_protocol: Some("wss".to_owned()), - /// ..Default::default() - /// }), - /// ..Default::default() - /// }) - /// .finish(); - /// ``` - pub fn options(self, options: T) -> AltairSource<'a> { - AltairSource { - options: Some(serde_json::to_value(options).expect("Failed to serialize options")), - ..self - } - } - - /// Returns an Altair HTML page. - pub fn finish(self) -> String { - let mut handlebars = Handlebars::new(); - - handlebars.register_helper("toJson", Box::new(ToJsonHelper)); - - handlebars - .register_template_string("altair_source", include_str!("./altair_source.hbs")) - .expect("Failed to register template"); - - handlebars - .render("altair_source", &self) - .expect("Failed to render template") - } -} - -/// Altair window [options](https://github.com/altair-graphql/altair/blob/master/packages/altair-core/src/config.ts#L10) -#[derive(Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "camelCase")] -pub struct AltairWindowOptions { - /// Initial name of the window - #[serde(default, skip_serializing_if = "Option::is_none")] - pub initial_name: Option, - /// URL to set as the server endpoint - #[serde( - rename = "endpointURL", - default, - skip_serializing_if = "Option::is_none" - )] - pub endpoint_url: Option, - /// URL to set as the subscription endpoint. This can be relative or - /// absolute. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub subscriptions_endpoint: Option, - /// URL protocol for the subscription endpoint. This is used if the - /// specified subscriptions endpoint is relative. - /// - /// e.g. wss - #[serde(default, skip_serializing_if = "Option::is_none")] - pub subscriptions_protocol: Option, - /// Initial query to be added - #[serde(default, skip_serializing_if = "Option::is_none")] - pub initial_query: Option, - /// Initial variables to be added - #[serde(default, skip_serializing_if = "Option::is_none")] - pub initial_variables: Option, - /// Initial pre-request script to be added - #[serde(default, skip_serializing_if = "Option::is_none")] - pub initial_pre_request_script: Option, - /// Initial post-request script to be added - #[serde(default, skip_serializing_if = "Option::is_none")] - pub initial_post_request_script: Option, - /// Initial authorization type and data - #[serde(default, skip_serializing_if = "Option::is_none")] - pub initial_authorization: Option, - /// Initial headers object to be added - /// ```js - /// { - /// 'X-GraphQL-Token': 'asd7-237s-2bdk-nsdk4' - /// } - /// ``` - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - pub initial_headers: HashMap, - /// Initial subscriptions connection params - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - pub initial_subscriptions_payload: HashMap, - /// HTTP method to use for making requests - #[serde(default, skip_serializing_if = "Option::is_none")] - pub initial_http_method: Option, -} - -/// Altair config [options](https://github.com/altair-graphql/altair/blob/master/packages/altair-core/src/config.ts#L79) -#[derive(Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "camelCase")] -pub struct AltairConfigOptions { - /// Options to be applied on every new window (including the initial) - #[serde(default, flatten, skip_serializing_if = "Option::is_none")] - pub window_options: Option, - /// Initial Environments to be added - /// ```js - /// { - /// base: { - /// title: 'Environment', - /// variables: {} - /// }, - /// subEnvironments: [ - /// { - /// title: 'sub-1', - /// variables: {} - /// } - /// ] - /// } - /// ``` - #[serde(default, skip_serializing_if = "Option::is_none")] - pub initial_environments: Option, - /// Namespace for storing the data for the altair instance. - /// - /// Use this when you have multiple altair instances running on the same - /// domain. - /// - /// e.g. altair_dev_ - #[serde(default, skip_serializing_if = "Option::is_none")] - pub instance_storage_namespace: Option, - /// Initial app settings to use - #[serde(default, skip_serializing_if = "Option::is_none")] - pub initial_settings: Option, - /// Indicates if the state should be preserved for subsequent app loads - /// (default true) - #[serde(default, skip_serializing_if = "Option::is_none")] - pub preserve_state: Option, - /// List of options for windows to be loaded - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub initial_windows: Vec, - /// Persisted settings for the app. The settings will be merged with the app - /// settings. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub persisted_settings: Option, - /// Disable the account and remote syncing functionality - #[serde(default, skip_serializing_if = "Option::is_none")] - pub disable_account: Option, -} - -/// Altair supported HTTP verbs -#[derive(Serialize, Deserialize, JsonSchema)] -#[allow(missing_docs)] -pub enum AltairHttpVerb { - POST, - GET, - PUT, - DELETE, -} - -/// Altair initial environments setup -#[derive(Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "camelCase")] -pub struct AltairInitialEnvironments { - /// Base environment - #[serde(default, skip_serializing_if = "Option::is_none")] - pub base: Option, - /// Other sub environments - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub sub_environments: Vec, -} - -/// Altair initial environment state -#[derive(Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "camelCase")] -pub struct AltairInitialEnvironmentState { - /// Environment identifier - #[serde(default, skip_serializing_if = "Option::is_none")] - pub id: Option, - /// Environment title - #[serde(default, skip_serializing_if = "Option::is_none")] - pub title: Option, - /// Environment variables - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - pub variables: HashMap, -} - -/// Altair authorization provider input -#[derive(Serialize, Deserialize, JsonSchema)] -#[serde(tag = "type", content = "data")] -pub enum AltairAuthorizationProviderInput { - /// Api key authorization - #[serde(rename = "api-key")] - ApiKey { - /// Header name - header_name: String, - /// Header value - header_value: String, - }, - /// Basic authorization - #[serde(rename = "basic")] - Basic { - /// Password - password: String, - /// Username - username: String, - }, - /// Bearer token authorization - #[serde(rename = "bearer")] - Bearer { - /// Token - token: String, - }, - /// OAuth2 access token authorization - #[serde(rename = "oauth2")] - OAuth2 { - /// Access token response - access_token_response: String, - }, -} - -/// Altair application settings state -#[derive(Default, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "camelCase")] -pub struct AltairSettingsState { - /// Theme - #[serde(default, skip_serializing_if = "Option::is_none")] - pub theme: Option, - /// Theme for dark mode - #[serde( - rename = "theme.dark", - default, - skip_serializing_if = "Option::is_none" - )] - pub theme_dark: Option, - /// Language - #[serde(default, skip_serializing_if = "Option::is_none")] - pub language: Option, - /// 'Add query' functionality depth - #[serde(default, skip_serializing_if = "Option::is_none")] - pub add_query_depth_limit: Option, - /// Editor tab size - #[serde(default, skip_serializing_if = "Option::is_none")] - pub tab_size: Option, - /// Enable experimental features. - /// - /// Note: Might be unstable - #[serde(default, skip_serializing_if = "Option::is_none")] - pub enable_experimental: Option, - /// Base Font Size - /// - /// default: 24 - #[serde( - rename = "theme.fontsize", - default, - skip_serializing_if = "Option::is_none" - )] - pub theme_font_size: Option, - /// Editor Font Family - #[serde( - rename = "theme.editorFontFamily", - default, - skip_serializing_if = "Option::is_none" - )] - pub theme_editor_font_family: Option, - /// Editor Font Size - #[serde( - rename = "theme.editorFontSize", - default, - skip_serializing_if = "Option::is_none" - )] - pub theme_editor_font_size: Option, - /// Disable push notifications - #[serde(default, skip_serializing_if = "Option::is_none")] - pub disable_push_notification: Option, - /// Enabled plugins - #[serde(rename = "plugin.list", default, skip_serializing_if = "Vec::is_empty")] - pub plugin_list: Vec, - /// Send requests with credentials (cookies) - #[serde( - rename = "request.withCredentials", - default, - skip_serializing_if = "Option::is_none" - )] - pub request_with_credentials: Option, - /// Reload schema on app start - #[serde( - rename = "schema.reloadOnStart", - default, - skip_serializing_if = "Option::is_none" - )] - pub schema_reload_on_start: Option, - /// Disable update notification - #[serde( - rename = "alert.disableUpdateNotification", - default, - skip_serializing_if = "Option::is_none" - )] - pub alert_disable_update_notification: Option, - /// Disable warning alerts - #[serde( - rename = "alert.disableWarnings", - default, - skip_serializing_if = "Option::is_none" - )] - pub alert_disable_warnings: Option, - /// Number of items allowed in history pane - #[serde(default, skip_serializing_if = "Option::is_none")] - pub history_depth: Option, - /// Disable line numbers - #[serde(default, skip_serializing_if = "Option::is_none")] - pub disable_line_numbers: Option, - /// Theme config object - #[serde(default, skip_serializing_if = "Option::is_none")] - pub theme_config: Option, - /// Theme config object for dark mode - #[serde( - rename = "themeConfig.dark", - default, - skip_serializing_if = "Option::is_none" - )] - pub theme_config_dark: Option, - /// Hides extensions object - #[serde( - rename = "response.hideExtensions", - default, - skip_serializing_if = "Option::is_none" - )] - pub response_hide_extensions: Option, - /// Contains shortcut to action mapping - #[serde( - rename = "editor.shortcuts", - default, - skip_serializing_if = "HashMap::is_empty" - )] - pub editor_shortcuts: HashMap, - /// Disable new editor beta - #[serde( - rename = "beta.disable.newEditor", - default, - skip_serializing_if = "Option::is_none" - )] - pub beta_disable_new_editor: Option, - /// Disable new script beta - #[serde( - rename = "beta.disable.newScript", - default, - skip_serializing_if = "Option::is_none" - )] - pub beta_disable_new_script: Option, - /// List of cookies to be accessible in the pre-request script - /// - /// e.g. ['cookie1', 'cookie2'] - #[serde( - rename = "script.allowedCookies", - default, - skip_serializing_if = "Vec::is_empty" - )] - pub script_allowed_cookies: Vec, - /// Enable the scrollbar in the tab list - #[serde(default, skip_serializing_if = "Option::is_none")] - pub enable_tablist_scrollbar: Option, -} - -/// Altair supported languages -#[derive(Serialize, Deserialize, JsonSchema)] -#[allow(missing_docs)] -pub enum AltairSettingsLanguage { - #[serde(rename = "en-US")] - English, - #[serde(rename = "fr-FR")] - French, - #[serde(rename = "es-ES")] - Español, - #[serde(rename = "cs-CZ")] - Czech, - #[serde(rename = "de-DE")] - German, - #[serde(rename = "pt-BR")] - Brazilian, - #[serde(rename = "ru-RU")] - Russian, - #[serde(rename = "uk-UA")] - Ukrainian, - #[serde(rename = "zh-CN")] - ChineseSimplified, - #[serde(rename = "ja-JP")] - Japanese, - #[serde(rename = "sr-SP")] - Serbian, - #[serde(rename = "it-IT")] - Italian, - #[serde(rename = "pl-PL")] - Polish, - #[serde(rename = "ko-KR")] - Korean, - #[serde(rename = "ro-RO")] - Romanian, - #[serde(rename = "vi-VN")] - Vietnamese, -} - -struct ToJsonHelper; -impl handlebars::HelperDef for ToJsonHelper { - #[allow(unused_assignments)] - fn call_inner<'reg: 'rc, 'rc>( - &self, - h: &handlebars::Helper<'rc>, - r: &'reg handlebars::Handlebars<'reg>, - _: &'rc handlebars::Context, - _: &mut handlebars::RenderContext<'reg, 'rc>, - ) -> std::result::Result, handlebars::RenderError> { - let mut param_idx = 0; - let obj = h - .param(param_idx) - .and_then(|x| { - if r.strict_mode() && x.is_value_missing() { - None - } else { - Some(x.value()) - } - }) - .ok_or_else(|| { - handlebars::RenderErrorReason::ParamNotFoundForName("toJson", "obj".to_string()) - }) - .and_then(|x| { - x.as_object().ok_or_else(|| { - handlebars::RenderErrorReason::ParamTypeMismatchForName( - "toJson", - "obj".to_string(), - "object".to_string(), - ) - }) - })?; - param_idx += 1; - let result = if obj.is_empty() { - "{}".to_owned() - } else { - serde_json::to_string(&obj).expect("Failed to serialize json") - }; - Ok(handlebars::ScopedJson::Derived( - handlebars::JsonValue::from(result), - )) - } -} - -#[cfg(test)] -mod tests { - use serde_json::json; - - use super::*; - - #[test] - fn test_without_options() { - let altair_source = AltairSource::build().title("Custom Title").finish(); - - assert_eq!( - altair_source, - r#" - - - - - - Custom Title - - - - - - - - - - - - -
-
-
- Altair -
-
- - - -
-
-
-
- - - - - -"# - ) - } - - #[test] - fn test_with_dynamic() { - let altair_source = AltairSource::build() - .options(json!({ - "endpointURL": "/", - "subscriptionsEndpoint": "/ws", - })) - .finish(); - - assert_eq!( - altair_source, - r#" - - - - - - Altair - - - - - - - - - - - - -
-
-
- Altair -
-
- - - -
-
-
-
- - - - - -"# - ) - } - - #[test] - fn test_with_static() { - let altair_source = AltairSource::build() - .options(AltairConfigOptions { - window_options: Some(AltairWindowOptions { - endpoint_url: Some("/".to_owned()), - subscriptions_endpoint: Some("/ws".to_owned()), - ..Default::default() - }), - ..Default::default() - }) - .finish(); - - assert_eq!( - altair_source, - r#" - - - - - - Altair - - - - - - - - - - - - -
-
-
- Altair -
-
- - - -
-
-
-
- - - - - -"# - ) - } -} diff --git a/src/http/graphiql_plugin.rs b/src/http/graphiql_plugin.rs deleted file mode 100644 index 884b0ce10..000000000 --- a/src/http/graphiql_plugin.rs +++ /dev/null @@ -1,112 +0,0 @@ -//! A simplified html for GraphiQL v2 with explorer plugin -//! -//! ```html -//! -//! -//! -//! GraphiQL -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! -//! ``` -//! -//! Example for explorer plugin -//! -//! ```rust, ignore -//! GraphiQLPlugin { -//! name: "GraphiQLPluginExplorer", -//! constructor: "GraphiQLPluginExplorer.explorerPlugin", -//! head_assets: Some( -//! r#""#, -//! ), -//! body_assets: Some( -//! r#""#, -//! ), -//! ..Default::default() -//! } -//! ``` - -use serde::Serialize; - -#[allow(missing_docs)] -#[derive(Debug, Default, Serialize)] -pub struct GraphiQLPlugin<'a> { - pub name: &'a str, - pub constructor: &'a str, - /// assets which would be placed in head - pub head_assets: Option<&'a str>, - /// assets which would be placed in body - pub body_assets: Option<&'a str>, - /// related configs which would be placed before loading plugin - pub pre_configs: Option<&'a str>, - /// props which would be passed to the plugin's constructor - pub props: Option<&'a str>, -} - -/// Generate simple explorer plugin for GraphiQL (v2) -pub fn graphiql_plugin_explorer<'a>() -> GraphiQLPlugin<'a> { - GraphiQLPlugin { - name: "GraphiQLPluginExplorer", - constructor: "GraphiQLPluginExplorer.explorerPlugin", - head_assets: Some( - r#""#, - ), - body_assets: Some( - r#""#, - ), - ..Default::default() - } -} diff --git a/src/http/graphiql_source.rs b/src/http/graphiql_source.rs deleted file mode 100644 index 5f284bda4..000000000 --- a/src/http/graphiql_source.rs +++ /dev/null @@ -1,60 +0,0 @@ -/// Generate the page for GraphIQL -pub fn graphiql_source(graphql_endpoint_url: &str, subscription_endpoint: Option<&str>) -> String { - r#" - - - Simple GraphiQL Example - - - -
- - - - - - - - - - - "# - .replace("GRAPHQL_URL", graphql_endpoint_url) - .replace( - "GRAPHQL_SUBSCRIPTION_URL", - &match subscription_endpoint { - Some(url) => format!("'{}'", url), - None => "null".to_string(), - }, - ) -} diff --git a/src/http/graphiql_v2_source.hbs b/src/http/graphiql_v2_source.hbs deleted file mode 100644 index eee8871d3..000000000 --- a/src/http/graphiql_v2_source.hbs +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - {{#if title}} - {{ title }} - {{else}} - GraphiQL IDE - {{/if}} - - - - - - - {{! plugins head assets }} - {{#if plugins}} - {{#each plugins}} - {{#if head_assets}} - {{&head_assets}} - {{/if}} - {{/each}} - {{/if}} - {{! end plugins head assets }} - - - -
Loading...
- - {{! plugins body assets }} - {{#if plugins}} - {{#each plugins}} - {{#if body_assets}} - {{&body_assets}} - {{/if}} - {{/each}} - {{/if}} - {{! end plugins body assets }} - - - \ No newline at end of file diff --git a/src/http/graphiql_v2_source.rs b/src/http/graphiql_v2_source.rs deleted file mode 100644 index 4c5a79c91..000000000 --- a/src/http/graphiql_v2_source.rs +++ /dev/null @@ -1,400 +0,0 @@ -use std::collections::HashMap; - -use handlebars::Handlebars; -use serde::Serialize; - -use crate::http::graphiql_plugin::GraphiQLPlugin; - -/// Indicates whether the user agent should send or receive user credentials -/// (cookies, basic http auth, etc.) from the other domain in the case of -/// cross-origin requests. -#[derive(Debug, Serialize, Default)] -#[serde(rename_all = "kebab-case")] -pub enum Credentials { - /// Send user credentials if the URL is on the same origin as the calling - /// script. This is the default value. - #[default] - SameOrigin, - /// Always send user credentials, even for cross-origin calls. - Include, - /// Never send or receive user credentials. - Omit, -} - -#[derive(Serialize)] -struct GraphiQLVersion<'a>(&'a str); - -impl Default for GraphiQLVersion<'_> { - fn default() -> Self { - Self("4") - } -} - -/// A builder for constructing a GraphiQL (v2) HTML page. -/// -/// # Example -/// -/// ```rust -/// use async_graphql::http::*; -/// -/// GraphiQLSource::build() -/// .endpoint("/") -/// .subscription_endpoint("/ws") -/// .header("Authorization", "Bearer [token]") -/// .ws_connection_param("token", "[token]") -/// .credentials(Credentials::Include) -/// .finish(); -/// ``` -#[derive(Default, Serialize)] -pub struct GraphiQLSource<'a> { - endpoint: &'a str, - subscription_endpoint: Option<&'a str>, - version: GraphiQLVersion<'a>, - headers: Option>, - ws_connection_params: Option>, - title: Option<&'a str>, - credentials: Credentials, - plugins: &'a [GraphiQLPlugin<'a>], -} - -impl<'a> GraphiQLSource<'a> { - /// Creates a builder for constructing a GraphiQL (v2) HTML page. - pub fn build() -> GraphiQLSource<'a> { - Default::default() - } - - /// Sets the endpoint of the server GraphiQL will connect to. - #[must_use] - pub fn endpoint(self, endpoint: &'a str) -> GraphiQLSource<'a> { - GraphiQLSource { endpoint, ..self } - } - - /// Sets the subscription endpoint of the server GraphiQL will connect to. - pub fn subscription_endpoint(self, endpoint: &'a str) -> GraphiQLSource<'a> { - GraphiQLSource { - subscription_endpoint: Some(endpoint), - ..self - } - } - - /// Sets a header to be sent with requests GraphiQL will send. - pub fn header(self, name: &'a str, value: &'a str) -> GraphiQLSource<'a> { - let mut headers = self.headers.unwrap_or_default(); - headers.insert(name, value); - GraphiQLSource { - headers: Some(headers), - ..self - } - } - - /// Sets the version of GraphiQL to be fetched. - pub fn version(self, value: &'a str) -> GraphiQLSource<'a> { - GraphiQLSource { - version: GraphiQLVersion(value), - ..self - } - } - - /// Sets a WS connection param to be sent during GraphiQL WS connections. - pub fn ws_connection_param(self, name: &'a str, value: &'a str) -> GraphiQLSource<'a> { - let mut ws_connection_params = self.ws_connection_params.unwrap_or_default(); - ws_connection_params.insert(name, value); - GraphiQLSource { - ws_connection_params: Some(ws_connection_params), - ..self - } - } - - /// Sets the html document title. - pub fn title(self, title: &'a str) -> GraphiQLSource<'a> { - GraphiQLSource { - title: Some(title), - ..self - } - } - - /// Sets credentials option for the fetch requests. - pub fn credentials(self, credentials: Credentials) -> GraphiQLSource<'a> { - GraphiQLSource { - credentials, - ..self - } - } - - /// Sets plugins - pub fn plugins(self, plugins: &'a [GraphiQLPlugin]) -> GraphiQLSource<'a> { - GraphiQLSource { plugins, ..self } - } - - /// Returns a GraphiQL (v2) HTML page. - pub fn finish(self) -> String { - let mut handlebars = Handlebars::new(); - handlebars - .register_template_string( - "graphiql_v2_source", - include_str!("./graphiql_v2_source.hbs"), - ) - .expect("Failed to register template"); - - handlebars - .render("graphiql_v2_source", &self) - .expect("Failed to render template") - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_with_only_url() { - let graphiql_source = GraphiQLSource::build().endpoint("/").finish(); - - assert_eq!( - graphiql_source, - r#" - - - - - - - - GraphiQL IDE - - - - - - - - - -
Loading...
- - - -"# - ) - } - - #[test] - fn test_with_both_urls() { - let graphiql_source = GraphiQLSource::build() - .endpoint("/") - .subscription_endpoint("/ws") - .finish(); - - assert_eq!( - graphiql_source, - r#" - - - - - - - - GraphiQL IDE - - - - - - - - - -
Loading...
- - - -"# - ) - } - - #[test] - fn test_with_all_options() { - use crate::http::graphiql_plugin_explorer; - let graphiql_source = GraphiQLSource::build() - .endpoint("/") - .subscription_endpoint("/ws") - .header("Authorization", "Bearer [token]") - .version("3.9.0") - .ws_connection_param("token", "[token]") - .title("Awesome GraphiQL IDE Test") - .credentials(Credentials::Include) - .plugins(&[graphiql_plugin_explorer()]) - .finish(); - - assert_eq!( - graphiql_source, - r#" - - - - - - - - Awesome GraphiQL IDE Test - - - - - - - - - - -
Loading...
- - - - -"# - ) - } -} diff --git a/src/http/mod.rs b/src/http/mod.rs deleted file mode 100644 index bfd721cbf..000000000 --- a/src/http/mod.rs +++ /dev/null @@ -1,201 +0,0 @@ -//! A helper module that supports HTTP - -#[cfg(feature = "altair")] -mod altair_source; -#[cfg(feature = "graphiql")] -mod graphiql_plugin; -#[cfg(feature = "graphiql")] -mod graphiql_source; -#[cfg(feature = "graphiql")] -mod graphiql_v2_source; -mod multipart; -mod multipart_subscribe; -#[cfg(feature = "playground")] -mod playground_source; -mod websocket; - -#[cfg(feature = "altair")] -pub use altair_source::*; -use futures_util::io::{AsyncRead, AsyncReadExt}; -#[cfg(feature = "graphiql")] -pub use graphiql_plugin::{GraphiQLPlugin, graphiql_plugin_explorer}; -#[cfg(feature = "graphiql")] -pub use graphiql_source::graphiql_source; -#[cfg(feature = "graphiql")] -pub use graphiql_v2_source::{Credentials, GraphiQLSource}; -pub use multipart::MultipartOptions; -pub use multipart_subscribe::{create_multipart_mixed_stream, is_accept_multipart_mixed}; -#[cfg(feature = "playground")] -pub use playground_source::{GraphQLPlaygroundConfig, playground_source}; -use serde::Deserialize; -pub use websocket::{ - ALL_WEBSOCKET_PROTOCOLS, ClientMessage, DefaultOnConnInitType, DefaultOnPingType, - Protocols as WebSocketProtocols, WebSocket, WsMessage, default_on_connection_init, - default_on_ping, -}; - -use crate::{BatchRequest, ParseRequestError, Request}; - -/// Parse a GraphQL request from a query string. -pub fn parse_query_string(input: &str) -> Result { - #[derive(Deserialize)] - struct RequestSerde { - #[serde(default)] - pub query: String, - pub operation_name: Option, - pub variables: Option, - pub extensions: Option, - } - - let request: RequestSerde = serde_urlencoded::from_str(input).map_err(std::io::Error::other)?; - let variables = request - .variables - .map(|data| serde_json::from_str(&data)) - .transpose() - .map_err(|err| std::io::Error::other(format!("invalid variables: {}", err)))? - .unwrap_or_default(); - let extensions = request - .extensions - .map(|data| serde_json::from_str(&data)) - .transpose() - .map_err(|err| std::io::Error::other(format!("invalid extensions: {}", err)))? - .unwrap_or_default(); - - Ok(Request { - operation_name: request.operation_name, - variables, - extensions, - ..Request::new(request.query) - }) -} - -/// Receive a GraphQL request from a content type and body. -pub async fn receive_body( - content_type: Option>, - body: impl AsyncRead + Send, - opts: MultipartOptions, -) -> Result { - receive_batch_body(content_type, body, opts) - .await? - .into_single() -} - -/// Receive a GraphQL request from a content type and body. -pub async fn receive_batch_body( - content_type: Option>, - body: impl AsyncRead + Send, - opts: MultipartOptions, -) -> Result { - // if no content-type header is set, we default to json - let content_type = content_type - .as_ref() - .map(AsRef::as_ref) - .unwrap_or("application/graphql-response+json"); - - let content_type: mime::Mime = content_type.parse()?; - - match (content_type.type_(), content_type.subtype()) { - // try to use multipart - (mime::MULTIPART, _) => { - if let Some(boundary) = content_type.get_param("boundary") { - multipart::receive_batch_multipart(body, boundary.to_string(), opts).await - } else { - Err(ParseRequestError::InvalidMultipart( - multer::Error::NoBoundary, - )) - } - } - // application/json or cbor (currently) - // cbor is in application/octet-stream. - // Note: cbor will only match if feature ``cbor`` is active - // TODO: wait for mime to add application/cbor and match against that too - _ => receive_batch_body_no_multipart(&content_type, body).await, - } -} - -/// Receives a GraphQL query which is either cbor or json but NOT multipart -/// This method is only to avoid recursive calls with [``receive_batch_body``] -/// and [``multipart::receive_batch_multipart``] -pub(super) async fn receive_batch_body_no_multipart( - content_type: &mime::Mime, - body: impl AsyncRead + Send, -) -> Result { - assert_ne!(content_type.type_(), mime::MULTIPART, "received multipart"); - match (content_type.type_(), content_type.subtype()) { - #[cfg(feature = "cbor")] - // cbor is in application/octet-stream. - // TODO: wait for mime to add application/cbor and match against that too - (mime::OCTET_STREAM, _) | (mime::APPLICATION, mime::OCTET_STREAM) => { - receive_batch_cbor(body).await - } - // default to json - _ => receive_batch_json(body).await, - } -} -/// Receive a GraphQL request from a body as JSON. -pub async fn receive_json(body: impl AsyncRead) -> Result { - receive_batch_json(body).await?.into_single() -} - -/// Receive a GraphQL batch request from a body as JSON. -pub async fn receive_batch_json(body: impl AsyncRead) -> Result { - let mut data = Vec::new(); - futures_util::pin_mut!(body); - body.read_to_end(&mut data) - .await - .map_err(ParseRequestError::Io)?; - serde_json::from_slice::(&data) - .map_err(|e| ParseRequestError::InvalidRequest(Box::new(e))) -} - -/// Receive a GraphQL request from a body as CBOR. -#[cfg(feature = "cbor")] -#[cfg_attr(docsrs, doc(cfg(feature = "cbor")))] -pub async fn receive_cbor(body: impl AsyncRead) -> Result { - receive_batch_cbor(body).await?.into_single() -} - -/// Receive a GraphQL batch request from a body as CBOR -#[cfg(feature = "cbor")] -#[cfg_attr(docsrs, doc(cfg(feature = "cbor")))] -pub async fn receive_batch_cbor(body: impl AsyncRead) -> Result { - let mut data = Vec::new(); - futures_util::pin_mut!(body); - body.read_to_end(&mut data) - .await - .map_err(ParseRequestError::Io)?; - serde_cbor::from_slice::(&data) - .map_err(|e| ParseRequestError::InvalidRequest(Box::new(e))) -} - -#[cfg(test)] -mod tests { - use std::collections::HashMap; - - use async_graphql_value::Extensions; - - use super::*; - use crate::{Variables, value}; - - #[test] - fn test_parse_query_string() { - let request = parse_query_string("variables=%7B%7D&extensions=%7B%22persistedQuery%22%3A%7B%22sha256Hash%22%3A%22cde5de0a350a19c59f8ddcd9646e5f260b2a7d5649ff6be8e63e9462934542c3%22%2C%22version%22%3A1%7D%7D").unwrap(); - assert_eq!(request.query.as_str(), ""); - assert_eq!(request.variables, Variables::default()); - assert_eq!(request.extensions, { - let mut extensions = HashMap::new(); - extensions.insert("persistedQuery".to_string(), value!({ - "sha256Hash": "cde5de0a350a19c59f8ddcd9646e5f260b2a7d5649ff6be8e63e9462934542c3", - "version": 1, - })); - Extensions(extensions) - }); - - let request = parse_query_string("query={a}&variables=%7B%22a%22%3A10%7D").unwrap(); - assert_eq!(request.query.as_str(), "{a}"); - assert_eq!( - request.variables, - Variables::from_value(value!({ "a" : 10 })) - ); - } -} diff --git a/src/http/multipart.rs b/src/http/multipart.rs deleted file mode 100644 index cdc804c63..000000000 --- a/src/http/multipart.rs +++ /dev/null @@ -1,237 +0,0 @@ -use std::{ - collections::HashMap, - io, - pin::Pin, - task::{Context, Poll}, -}; - -use futures_util::{io::AsyncRead, stream::Stream}; -use multer::{Constraints, Multipart, SizeLimit}; -use pin_project_lite::pin_project; - -use crate::{BatchRequest, ParseRequestError, UploadValue}; - -/// Options for `receive_multipart`. -#[derive(Default, Clone, Copy)] -#[non_exhaustive] -pub struct MultipartOptions { - /// The maximum file size. - pub max_file_size: Option, - /// The maximum number of files. - pub max_num_files: Option, -} - -impl MultipartOptions { - /// Set maximum file size. - #[must_use] - pub fn max_file_size(self, size: usize) -> Self { - MultipartOptions { - max_file_size: Some(size), - ..self - } - } - - /// Set maximum number of files. - #[must_use] - pub fn max_num_files(self, n: usize) -> Self { - MultipartOptions { - max_num_files: Some(n), - ..self - } - } -} - -pub(super) async fn receive_batch_multipart( - body: impl AsyncRead + Send, - boundary: impl Into, - opts: MultipartOptions, -) -> Result { - let mut multipart = Multipart::with_constraints( - ReaderStream::new(body), - boundary, - Constraints::new().size_limit({ - let mut limit = SizeLimit::new(); - if let (Some(max_file_size), Some(max_num_files)) = - (opts.max_file_size, opts.max_num_files) - { - limit = limit.whole_stream((max_file_size * max_num_files) as u64); - } - if let Some(max_file_size) = opts.max_file_size { - limit = limit.per_field(max_file_size as u64); - } - limit - }), - ); - - let mut request = None; - let mut map = None; - let mut files = Vec::new(); - - while let Some(field) = multipart.next_field().await? { - // in multipart, each field / file can actually have a own Content-Type. - // We use this to determine the encoding of the graphql query - let content_type = field - .content_type() - // default to json - .unwrap_or(&mime::APPLICATION_JSON) - .clone(); - match field.name() { - Some("operations") => { - let body = field.bytes().await?; - request = Some( - super::receive_batch_body_no_multipart(&content_type, body.as_ref()).await?, - ) - } - Some("map") => { - let map_bytes = field.bytes().await?; - - match (content_type.type_(), content_type.subtype()) { - // cbor is in application/octet-stream. - // TODO: wait for mime to add application/cbor and match against that too - // Note: we actually differ here from the inoffical spec for this: - // (https://github.com/jaydenseric/graphql-multipart-request-spec#multipart-form-field-structure) - // It says: "map: A JSON encoded map of where files occurred in the operations. - // For each file, the key is the file multipart form field name and the value is - // an array of operations paths." However, I think, that - // since we accept CBOR as operation, which is valid, we should also accept it - // as the mapping for the files. - #[cfg(feature = "cbor")] - (mime::OCTET_STREAM, _) | (mime::APPLICATION, mime::OCTET_STREAM) => { - map = Some( - serde_cbor::from_slice::>>(&map_bytes) - .map_err(|e| ParseRequestError::InvalidFilesMap(Box::new(e)))?, - ); - } - // default to json - _ => { - map = Some( - serde_json::from_slice::>>(&map_bytes) - .map_err(|e| ParseRequestError::InvalidFilesMap(Box::new(e)))?, - ); - } - } - } - _ => { - if let Some(name) = field.name().map(ToString::to_string) { - if let Some(filename) = field.file_name().map(ToString::to_string) { - let content_type = field.content_type().map(ToString::to_string); - - #[cfg(feature = "tempfile")] - let content = { - let mut field = field; - - #[cfg(feature = "unblock")] - { - use std::io::SeekFrom; - - use blocking::Unblock; - use futures_util::{AsyncSeekExt, AsyncWriteExt}; - - let mut file = Unblock::new( - tempfile::tempfile().map_err(ParseRequestError::Io)?, - ); - while let Some(chunk) = field.chunk().await? { - file.write_all(&chunk) - .await - .map_err(ParseRequestError::Io)?; - } - file.seek(SeekFrom::Start(0)) - .await - .map_err(ParseRequestError::Io)?; - file.into_inner().await - } - - #[cfg(not(feature = "unblock"))] - { - use std::io::{Seek, Write}; - - let mut file = - tempfile::tempfile().map_err(ParseRequestError::Io)?; - while let Some(chunk) = field.chunk().await? { - file.write_all(&chunk).map_err(ParseRequestError::Io)?; - } - file.rewind()?; - file - } - }; - - #[cfg(not(feature = "tempfile"))] - let content = field.bytes().await?; - - files.push((name, filename, content_type, content)); - } - } - } - } - } - - let mut request: BatchRequest = request.ok_or(ParseRequestError::MissingOperatorsPart)?; - let map = map.as_mut().ok_or(ParseRequestError::MissingMapPart)?; - - for (name, filename, content_type, file) in files { - if let Some(var_paths) = map.remove(&name) { - let upload = UploadValue { - filename, - content_type, - content: file, - }; - - for var_path in var_paths { - match &mut request { - BatchRequest::Single(request) => { - request.set_upload(&var_path, upload.try_clone()?); - } - BatchRequest::Batch(requests) => { - let mut s = var_path.splitn(2, '.'); - let idx = s.next().and_then(|idx| idx.parse::().ok()); - let path = s.next(); - - if let (Some(idx), Some(path)) = (idx, path) { - if let Some(request) = requests.get_mut(idx) { - request.set_upload(path, upload.try_clone()?); - } - } - } - } - } - } - } - - if !map.is_empty() { - return Err(ParseRequestError::MissingFiles); - } - - Ok(request) -} - -pin_project! { - pub(crate) struct ReaderStream { - buf: [u8; 2048], - #[pin] - reader: T, - } -} - -impl ReaderStream { - pub(crate) fn new(reader: T) -> Self { - Self { - buf: [0; 2048], - reader, - } - } -} - -impl Stream for ReaderStream { - type Item = io::Result>; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - let this = self.project(); - - Poll::Ready( - match futures_util::ready!(this.reader.poll_read(cx, this.buf)?) { - 0 => None, - size => Some(Ok(this.buf[..size].to_vec())), - }, - ) - } -} diff --git a/src/http/multipart_subscribe.rs b/src/http/multipart_subscribe.rs deleted file mode 100644 index 76a2c711f..000000000 --- a/src/http/multipart_subscribe.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::time::Duration; - -use bytes::{BufMut, Bytes, BytesMut}; -use futures_timer::Delay; -use futures_util::{FutureExt, Stream, StreamExt, stream::BoxStream}; -use mime::Mime; - -use crate::Response; - -static PART_HEADER: Bytes = - Bytes::from_static(b"--graphql\r\nContent-Type: application/json\r\n\r\n"); -static EOF: Bytes = Bytes::from_static(b"--graphql--\r\n"); -static CRLF: Bytes = Bytes::from_static(b"\r\n"); -static HEARTBEAT: Bytes = Bytes::from_static(b"{}\r\n"); - -/// Create a stream for `multipart/mixed` responses. -/// -/// Reference: -pub fn create_multipart_mixed_stream<'a>( - input: impl Stream + Send + Unpin + 'a, - heartbeat_interval: Duration, -) -> BoxStream<'a, Bytes> { - let mut input = input.fuse(); - let mut heartbeat_timer = Delay::new(heartbeat_interval).fuse(); - - async_stream::stream! { - loop { - futures_util::select! { - item = input.next() => { - match item { - Some(resp) => { - let data = BytesMut::new(); - let mut writer = data.writer(); - if serde_json::to_writer(&mut writer, &resp).is_err() { - continue; - } - - yield PART_HEADER.clone(); - yield writer.into_inner().freeze(); - yield CRLF.clone(); - } - None => break, - } - } - _ = heartbeat_timer => { - heartbeat_timer = Delay::new(heartbeat_interval).fuse(); - yield PART_HEADER.clone(); - yield HEARTBEAT.clone(); - } - } - } - - yield EOF.clone(); - } - .boxed() -} - -fn parse_accept(accept: &str) -> Vec { - let mut items = accept - .split(',') - .map(str::trim) - .filter_map(|item| { - let mime: Mime = item.parse().ok()?; - let q = mime - .get_param("q") - .and_then(|value| Some((value.as_str().parse::().ok()? * 1000.0) as i32)) - .unwrap_or(1000); - Some((mime, q)) - }) - .collect::>(); - items.sort_by(|(_, qa), (_, qb)| qb.cmp(qa)); - items.into_iter().map(|(mime, _)| mime).collect() -} - -/// Check accept is multipart-mixed -/// -/// # Example header -/// -/// ```text -/// Accept: multipart/mixed; boundary="graphql"; subscriptionSpec="1.0" -/// ``` -/// -/// the value for boundary should always be `graphql`, and the value -/// for `subscriptionSpec` should always be `1.0`. -/// -/// Reference: -pub fn is_accept_multipart_mixed(accept: &str) -> bool { - for mime in parse_accept(accept) { - if mime.type_() == mime::APPLICATION && mime.subtype() == mime::JSON { - return false; - } - - if mime.type_() == mime::MULTIPART - && mime.subtype() == "mixed" - && mime.get_param(mime::BOUNDARY).map(|value| value.as_str()) == Some("graphql") - && mime - .get_param("subscriptionSpec") - .map(|value| value.as_str()) - == Some("1.0") - { - return true; - } - } - - false -} diff --git a/src/http/playground_source.rs b/src/http/playground_source.rs deleted file mode 100644 index 7223c5ff6..000000000 --- a/src/http/playground_source.rs +++ /dev/null @@ -1,664 +0,0 @@ -use std::collections::HashMap; - -use serde::Serialize; - -use crate::Value; - -/// Generate the page for GraphQL Playground -/// -/// # Example -/// -/// ```rust -/// use async_graphql::http::*; -/// -/// playground_source(GraphQLPlaygroundConfig::new("http://localhost:8000")); -/// ``` -pub fn playground_source(config: GraphQLPlaygroundConfig) -> String { - let title = config.title.unwrap_or("GraphQL Playground"); - r##" - - - - - - - - %GRAPHQL_PLAYGROUND_TITLE% - - - - - - - - - - -
- -
Loading - GraphQL Playground -
-
- -
- - - - "##.replace("GRAPHQL_PLAYGROUND_CONFIG", &match serde_json::to_string(&config) { - Ok(str) => str, - Err(_) => "{}".to_string() - }) - .replace("%GRAPHQL_PLAYGROUND_TITLE%", title) -} - -/// Config for GraphQL Playground -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct GraphQLPlaygroundConfig<'a> { - endpoint: &'a str, - subscription_endpoint: Option<&'a str>, - headers: Option>, - settings: Option>, - title: Option<&'a str>, -} - -impl<'a> GraphQLPlaygroundConfig<'a> { - /// Create a config for GraphQL playground. - pub fn new(endpoint: &'a str) -> Self { - Self { - endpoint, - subscription_endpoint: None, - headers: Default::default(), - settings: Default::default(), - title: Default::default(), - } - } - - /// Set subscription endpoint, for example: `ws://localhost:8000`. - #[must_use] - pub fn subscription_endpoint(mut self, endpoint: &'a str) -> Self { - self.subscription_endpoint = Some(endpoint); - self - } - - /// Set HTTP header for per query. - #[must_use] - pub fn with_header(mut self, name: &'a str, value: &'a str) -> Self { - if let Some(headers) = &mut self.headers { - headers.insert(name, value); - } else { - let mut headers = HashMap::new(); - headers.insert(name, value); - self.headers = Some(headers); - } - self - } - - /// Set the html document title. - #[must_use] - pub fn title(mut self, title: &'a str) -> Self { - self.title = Some(title); - self - } - - /// Set Playground setting for per query. - /// - /// ``` - /// # use async_graphql::Value; - /// # use async_graphql::http::GraphQLPlaygroundConfig; - /// GraphQLPlaygroundConfig::new("/api/graphql") - /// .with_setting("setting", false) - /// .with_setting("other", Value::Null); - /// ``` - #[must_use] - pub fn with_setting(mut self, name: &'a str, value: impl Into) -> Self { - let value = value.into(); - - if let Some(settings) = &mut self.settings { - settings.insert(name, value); - } else { - let mut settings = HashMap::new(); - settings.insert(name, value); - self.settings = Some(settings); - } - self - } -} - -#[cfg(test)] -mod tests { - use indexmap::IndexMap; - - use super::*; - - #[test] - fn test_with_setting_can_use_any_json_value() { - let settings = GraphQLPlaygroundConfig::new("") - .with_setting("string", "string") - .with_setting("bool", false) - .with_setting("number", 10) - .with_setting("null", Value::Null) - .with_setting("array", Vec::from([1, 2, 3])) - .with_setting("object", IndexMap::new()); - - let json = serde_json::to_value(settings).unwrap(); - let settings = json["settings"].as_object().unwrap(); - - assert!(settings["string"].as_str().is_some()); - assert!(settings["bool"].as_bool().is_some()); - assert!(settings["number"].as_u64().is_some()); - assert!(settings["null"].as_null().is_some()); - assert!(settings["array"].as_array().is_some()); - assert!(settings["object"].as_object().is_some()); - } -} diff --git a/src/http/websocket.rs b/src/http/websocket.rs deleted file mode 100644 index 8410ae0f2..000000000 --- a/src/http/websocket.rs +++ /dev/null @@ -1,623 +0,0 @@ -//! WebSocket transport for subscription - -use std::{ - collections::HashMap, - future::Future, - pin::Pin, - sync::Arc, - task::{Context, Poll}, - time::{Duration, Instant}, -}; - -use futures_timer::Delay; -use futures_util::{ - FutureExt, StreamExt, - future::{BoxFuture, Ready}, - stream::Stream, -}; -use pin_project_lite::pin_project; -use serde::{Deserialize, Serialize}; - -use crate::{Data, Error, Executor, Request, Response, Result}; - -/// All known protocols based on WebSocket. -pub const ALL_WEBSOCKET_PROTOCOLS: [&str; 2] = ["graphql-transport-ws", "graphql-ws"]; - -/// An enum representing the various forms of a WebSocket message. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum WsMessage { - /// A text WebSocket message - Text(String), - - /// A close message with the close frame. - Close(u16, String), -} - -impl WsMessage { - /// Returns the contained [WsMessage::Text] value, consuming the `self` - /// value. - /// - /// Because this function may panic, its use is generally discouraged. - /// - /// # Panics - /// - /// Panics if the self value not equals [WsMessage::Text]. - pub fn unwrap_text(self) -> String { - match self { - Self::Text(text) => text, - Self::Close(_, _) => panic!("Not a text message"), - } - } - - /// Returns the contained [WsMessage::Close] value, consuming the `self` - /// value. - /// - /// Because this function may panic, its use is generally discouraged. - /// - /// # Panics - /// - /// Panics if the self value not equals [WsMessage::Close]. - pub fn unwrap_close(self) -> (u16, String) { - match self { - Self::Close(code, msg) => (code, msg), - Self::Text(_) => panic!("Not a close message"), - } - } -} - -struct Timer { - interval: Duration, - delay: Delay, -} - -impl Timer { - #[inline] - fn new(interval: Duration) -> Self { - Self { - interval, - delay: Delay::new(interval), - } - } - - #[inline] - fn reset(&mut self) { - self.delay.reset(self.interval); - } -} - -impl Stream for Timer { - type Item = (); - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = &mut *self; - match this.delay.poll_unpin(cx) { - Poll::Ready(_) => { - this.delay.reset(this.interval); - Poll::Ready(Some(())) - } - Poll::Pending => Poll::Pending, - } - } -} - -pin_project! { - /// A GraphQL connection over websocket. - /// - /// # References - /// - /// - [subscriptions-transport-ws](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md) - /// - [graphql-ws](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md) - pub struct WebSocket { - on_connection_init: Option, - on_ping: OnPing, - init_fut: Option>>, - ping_fut: Option>>>, - connection_data: Option, - data: Option>, - executor: E, - streams: HashMap + Send>>>, - #[pin] - stream: S, - protocol: Protocols, - last_msg_at: Instant, - keepalive_timer: Option, - close: bool, - } -} - -type MessageMapStream = - futures_util::stream::Map::Item) -> serde_json::Result>; - -/// Default connection initializer type. -pub type DefaultOnConnInitType = fn(serde_json::Value) -> Ready>; - -/// Default ping handler type. -pub type DefaultOnPingType = - fn(Option<&Data>, Option) -> Ready>>; - -/// Default connection initializer function. -pub fn default_on_connection_init(_: serde_json::Value) -> Ready> { - futures_util::future::ready(Ok(Data::default())) -} - -/// Default ping handler function. -pub fn default_on_ping( - _: Option<&Data>, - _: Option, -) -> Ready>> { - futures_util::future::ready(Ok(None)) -} - -impl WebSocket -where - E: Executor, - S: Stream>, -{ - /// Create a new websocket from [`ClientMessage`] stream. - pub fn from_message_stream(executor: E, stream: S, protocol: Protocols) -> Self { - WebSocket { - on_connection_init: Some(default_on_connection_init), - on_ping: default_on_ping, - init_fut: None, - ping_fut: None, - connection_data: None, - data: None, - executor, - streams: HashMap::new(), - stream, - protocol, - last_msg_at: Instant::now(), - keepalive_timer: None, - close: false, - } - } -} - -impl WebSocket, E, DefaultOnConnInitType, DefaultOnPingType> -where - E: Executor, - S: Stream, - S::Item: AsRef<[u8]>, -{ - /// Create a new websocket from bytes stream. - pub fn new(executor: E, stream: S, protocol: Protocols) -> Self { - let stream = stream - .map(ClientMessage::from_bytes as fn(S::Item) -> serde_json::Result); - WebSocket::from_message_stream(executor, stream, protocol) - } -} - -impl WebSocket -where - E: Executor, - S: Stream>, -{ - /// Specify a connection data. - /// - /// This data usually comes from HTTP requests. - /// When the `GQL_CONNECTION_INIT` message is received, this data will be - /// merged with the data returned by the closure specified by - /// `with_initializer` into the final subscription context data. - #[must_use] - pub fn connection_data(mut self, data: Data) -> Self { - self.connection_data = Some(data); - self - } - - /// Specify a connection initialize callback function. - /// - /// This function if present, will be called with the data sent by the - /// client in the [`GQL_CONNECTION_INIT` message](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init). - /// From that point on the returned data will be accessible to all requests. - #[must_use] - pub fn on_connection_init(self, callback: F) -> WebSocket - where - F: FnOnce(serde_json::Value) -> R + Send + 'static, - R: Future> + Send + 'static, - { - WebSocket { - on_connection_init: Some(callback), - on_ping: self.on_ping, - init_fut: self.init_fut, - ping_fut: self.ping_fut, - connection_data: self.connection_data, - data: self.data, - executor: self.executor, - streams: self.streams, - stream: self.stream, - protocol: self.protocol, - last_msg_at: self.last_msg_at, - keepalive_timer: self.keepalive_timer, - close: self.close, - } - } - - /// Specify a ping callback function. - /// - /// This function if present, will be called with the data sent by the - /// client in the [`Ping` message](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#ping). - /// - /// The function should return the data to be sent in the [`Pong` message](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#pong). - /// - /// NOTE: Only used for the `graphql-ws` protocol. - #[must_use] - pub fn on_ping(self, callback: F) -> WebSocket - where - F: FnOnce(Option<&Data>, Option) -> R + Send + Clone + 'static, - R: Future>> + Send + 'static, - { - WebSocket { - on_connection_init: self.on_connection_init, - on_ping: callback, - init_fut: self.init_fut, - ping_fut: self.ping_fut, - connection_data: self.connection_data, - data: self.data, - executor: self.executor, - streams: self.streams, - stream: self.stream, - protocol: self.protocol, - last_msg_at: self.last_msg_at, - keepalive_timer: self.keepalive_timer, - close: self.close, - } - } - - /// Sets a timeout for receiving an acknowledgement of the keep-alive ping. - /// - /// If the ping is not acknowledged within the timeout, the connection will - /// be closed. - /// - /// NOTE: Only used for the `graphql-ws` protocol. - #[must_use] - pub fn keepalive_timeout(self, timeout: impl Into>) -> Self { - Self { - keepalive_timer: timeout.into().map(Timer::new), - ..self - } - } -} - -impl Stream for WebSocket -where - E: Executor, - S: Stream>, - OnInit: FnOnce(serde_json::Value) -> InitFut + Send + 'static, - InitFut: Future> + Send + 'static, - OnPing: FnOnce(Option<&Data>, Option) -> PingFut + Clone + Send + 'static, - PingFut: Future>> + Send + 'static, -{ - type Item = WsMessage; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - let mut this = self.project(); - - if *this.close { - return Poll::Ready(None); - } - - if let Some(keepalive_timer) = this.keepalive_timer { - if let Poll::Ready(Some(())) = keepalive_timer.poll_next_unpin(cx) { - return match this.protocol { - Protocols::SubscriptionsTransportWS => { - *this.close = true; - Poll::Ready(Some(WsMessage::Text( - serde_json::to_string(&ServerMessage::ConnectionError { - payload: Error::new("timeout"), - }) - .unwrap(), - ))) - } - Protocols::GraphQLWS => { - *this.close = true; - Poll::Ready(Some(WsMessage::Close(3008, "timeout".to_string()))) - } - }; - } - } - - if this.init_fut.is_none() && this.ping_fut.is_none() { - while let Poll::Ready(message) = Pin::new(&mut this.stream).poll_next(cx) { - let message = match message { - Some(message) => message, - None => return Poll::Ready(None), - }; - - let message: ClientMessage = match message { - Ok(message) => message, - Err(err) => { - *this.close = true; - return Poll::Ready(Some(WsMessage::Close(1002, err.to_string()))); - } - }; - - *this.last_msg_at = Instant::now(); - if let Some(keepalive_timer) = this.keepalive_timer { - keepalive_timer.reset(); - } - - match message { - ClientMessage::ConnectionInit { payload } => { - if let Some(on_connection_init) = this.on_connection_init.take() { - *this.init_fut = Some(Box::pin(async move { - on_connection_init(payload.unwrap_or_default()).await - })); - break; - } else { - *this.close = true; - match this.protocol { - Protocols::SubscriptionsTransportWS => { - return Poll::Ready(Some(WsMessage::Text( - serde_json::to_string(&ServerMessage::ConnectionError { - payload: Error::new( - "Too many initialisation requests.", - ), - }) - .unwrap(), - ))); - } - Protocols::GraphQLWS => { - return Poll::Ready(Some(WsMessage::Close( - 4429, - "Too many initialisation requests.".to_string(), - ))); - } - } - } - } - ClientMessage::Start { - id, - payload: request, - } => { - if let Some(data) = this.data.clone() { - this.streams.insert( - id, - Box::pin(this.executor.execute_stream(request, Some(data))), - ); - } else { - *this.close = true; - return Poll::Ready(Some(WsMessage::Close( - 1011, - "The handshake is not completed.".to_string(), - ))); - } - } - ClientMessage::Stop { id } => { - if this.streams.remove(&id).is_some() { - return Poll::Ready(Some(WsMessage::Text( - serde_json::to_string(&ServerMessage::Complete { id: &id }) - .unwrap(), - ))); - } - } - // Note: in the revised `graphql-ws` spec, there is no equivalent to the - // `CONNECTION_TERMINATE` `client -> server` message; rather, disconnection is - // handled by disconnecting the websocket - ClientMessage::ConnectionTerminate => { - *this.close = true; - return Poll::Ready(None); - } - // Pong must be sent in response from the receiving party as soon as possible. - ClientMessage::Ping { payload } => { - let on_ping = this.on_ping.clone(); - let data = this.data.clone(); - *this.ping_fut = - Some(Box::pin( - async move { on_ping(data.as_deref(), payload).await }, - )); - break; - } - ClientMessage::Pong { .. } => { - // Do nothing... - } - } - } - } - - if let Some(init_fut) = this.init_fut { - return init_fut.poll_unpin(cx).map(|res| { - *this.init_fut = None; - match res { - Ok(data) => { - let mut ctx_data = this.connection_data.take().unwrap_or_default(); - ctx_data.merge(data); - *this.data = Some(Arc::new(ctx_data)); - Some(WsMessage::Text( - serde_json::to_string(&ServerMessage::ConnectionAck).unwrap(), - )) - } - Err(err) => { - *this.close = true; - match this.protocol { - Protocols::SubscriptionsTransportWS => Some(WsMessage::Text( - serde_json::to_string(&ServerMessage::ConnectionError { - payload: Error::new(err.message), - }) - .unwrap(), - )), - Protocols::GraphQLWS => Some(WsMessage::Close(1002, err.message)), - } - } - } - }); - } - - if let Some(ping_fut) = this.ping_fut { - return ping_fut.poll_unpin(cx).map(|res| { - *this.ping_fut = None; - match res { - Ok(payload) => Some(WsMessage::Text( - serde_json::to_string(&ServerMessage::Pong { payload }).unwrap(), - )), - Err(err) => { - *this.close = true; - match this.protocol { - Protocols::SubscriptionsTransportWS => Some(WsMessage::Text( - serde_json::to_string(&ServerMessage::ConnectionError { - payload: Error::new(err.message), - }) - .unwrap(), - )), - Protocols::GraphQLWS => Some(WsMessage::Close(1002, err.message)), - } - } - } - }); - } - - for (id, stream) in &mut *this.streams { - match Pin::new(stream).poll_next(cx) { - Poll::Ready(Some(payload)) => { - return Poll::Ready(Some(WsMessage::Text( - serde_json::to_string(&this.protocol.next_message(id, payload)).unwrap(), - ))); - } - Poll::Ready(None) => { - let id = id.clone(); - this.streams.remove(&id); - return Poll::Ready(Some(WsMessage::Text( - serde_json::to_string(&ServerMessage::Complete { id: &id }).unwrap(), - ))); - } - Poll::Pending => {} - } - } - - Poll::Pending - } -} - -/// Specification of which GraphQL Over WebSockets protocol is being utilized -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Protocols { - /// [subscriptions-transport-ws protocol](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md). - SubscriptionsTransportWS, - /// [graphql-ws protocol](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md). - GraphQLWS, -} - -impl Protocols { - /// Returns the `Sec-WebSocket-Protocol` header value for the protocol - pub fn sec_websocket_protocol(&self) -> &'static str { - match self { - Protocols::SubscriptionsTransportWS => "graphql-ws", - Protocols::GraphQLWS => "graphql-transport-ws", - } - } - - #[inline] - fn next_message<'s>(&self, id: &'s str, payload: Response) -> ServerMessage<'s> { - match self { - Protocols::SubscriptionsTransportWS => ServerMessage::Data { id, payload }, - Protocols::GraphQLWS => ServerMessage::Next { id, payload }, - } - } -} - -impl std::str::FromStr for Protocols { - type Err = Error; - - fn from_str(protocol: &str) -> Result { - if protocol.eq_ignore_ascii_case("graphql-ws") { - Ok(Protocols::SubscriptionsTransportWS) - } else if protocol.eq_ignore_ascii_case("graphql-transport-ws") { - Ok(Protocols::GraphQLWS) - } else { - Err(Error::new(format!( - "Unsupported Sec-WebSocket-Protocol: {}", - protocol - ))) - } - } -} - -/// A websocket message received from the client -#[derive(Deserialize)] -#[serde(tag = "type", rename_all = "snake_case")] -#[allow(clippy::large_enum_variant)] // Request is at fault -pub enum ClientMessage { - /// A new connection - ConnectionInit { - /// Optional init payload from the client - payload: Option, - }, - /// The start of a Websocket subscription - #[serde(alias = "subscribe")] - Start { - /// Message ID - id: String, - /// The GraphQL Request - this can be modified by protocol implementors - /// to add files uploads. - payload: Request, - }, - /// The end of a Websocket subscription - #[serde(alias = "complete")] - Stop { - /// Message ID - id: String, - }, - /// Connection terminated by the client - ConnectionTerminate, - /// Useful for detecting failed connections, displaying latency metrics or - /// other types of network probing. - /// - /// Reference: - Ping { - /// Additional details about the ping. - payload: Option, - }, - /// The response to the Ping message. - /// - /// Reference: - Pong { - /// Additional details about the pong. - payload: Option, - }, -} - -impl ClientMessage { - /// Creates a ClientMessage from an array of bytes - pub fn from_bytes(message: T) -> serde_json::Result - where - T: AsRef<[u8]>, - { - serde_json::from_slice(message.as_ref()) - } -} - -#[derive(Serialize)] -#[serde(tag = "type", rename_all = "snake_case")] -enum ServerMessage<'a> { - ConnectionError { - payload: Error, - }, - ConnectionAck, - /// subscriptions-transport-ws protocol next payload - Data { - id: &'a str, - payload: Response, - }, - /// graphql-ws protocol next payload - Next { - id: &'a str, - payload: Response, - }, - // Not used by this library, as it's not necessary to send - // Error { - // id: &'a str, - // payload: serde_json::Value, - // }, - Complete { - id: &'a str, - }, - /// The response to the Ping message. - /// - /// https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md#pong - Pong { - #[serde(skip_serializing_if = "Option::is_none")] - payload: Option, - }, - // Not used by this library - // #[serde(rename = "ka")] - // KeepAlive -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 7b6cbb055..000000000 --- a/src/lib.rs +++ /dev/null @@ -1,293 +0,0 @@ -//! # A GraphQL server library implemented in Rust -//! -//!
-//! -//! -//! -//! -//! -//! -//! Crates.io version -//! -//! -//! -//! Download -//! -//! -//! -//! docs.rs docs -//! -//! -//! Unsafe Rust forbidden -//! -//!
-//! -//! ## Documentation -//! -//! * [Book](https://async-graphql.github.io/async-graphql/en/index.html) -//! * [中文文档](https://async-graphql.github.io/async-graphql/zh-CN/index.html) -//! * [Docs](https://docs.rs/async-graphql) -//! * [GitHub repository](https://github.com/async-graphql/async-graphql) -//! * [Cargo package](https://crates.io/crates/async-graphql) -//! * Minimum supported Rust version: 1.56.1 or later -//! -//! ## Features -//! -//! * Fully supports async/await -//! * Type safety -//! * Rustfmt friendly (Procedural Macro) -//! * Custom scalars -//! * Minimal overhead -//! * Easy integration ([poem](https://crates.io/crates/poem), actix_web, tide, -//! warp, rocket ...) -//! * File upload (Multipart request) -//! * Subscriptions (WebSocket transport) -//! * Custom extensions -//! * Apollo Tracing extension -//! * Limit query complexity/depth -//! * Error Extensions -//! * Apollo Federation(v2) -//! * Batch Queries -//! * Apollo Persisted Queries -//! -//! ## Crate features -//! -//! This crate offers the following features, all of which are not activated by -//! default: -//! -//! | feature | enables | -//! |:-------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -//! | **`apollo_tracing`** | Enable the [Apollo tracing extension](https://docs.rs/async-graphql/latest/async_graphql/extensions/struct.ApolloTracing.html). | -//! | **`apollo_persisted_queries`** | Enable the [Apollo persisted queries extension](https://docs.rs/async-graphql/latest/async_graphql/extensions/apollo_persisted_queries/struct.ApolloPersistedQueries.html). | -//! | **`boxed-trait`** | Enables [`async-trait`](https://crates.io/crates/async-trait) for all traits. | -//! | **`bson`** | Integrate with the [`bson` crate](https://crates.io/crates/bson). | -//! | **`bigdecimal`** | Integrate with the [`bigdecimal` crate](https://crates.io/crates/bigdecimal). | -//! | **`cbor`** | Support for [serde_cbor](https://crates.io/crates/serde_cbor). | -//! | **`chrono`** | Integrate with the [`chrono` crate](https://crates.io/crates/chrono). | -//! | **`chrono-tz`** | Integrate with the [`chrono-tz` crate](https://crates.io/crates/chrono-tz). | -//! | **`dataloader`** | Support [DataLoader](dataloader/struct.DataLoader.html). | -//! | **`decimal`** | Integrate with the [`rust_decimal` crate](https://crates.io/crates/rust_decimal). | -//! | **`dynamic-schema`** | Support dynamic schema | -//! | **`fast_chemail`** | Integrate with the [`fast_chemail` crate](https://crates.io/crates/fast_chemail). | -//! | **`graphiql`** | Enables the [GraphiQL IDE](https://github.com/graphql/graphiql) integration | -//! | **`hashbrown`** | Integrate with the [`hashbrown` crate](https://github.com/rust-lang/hashbrown). | -//! | **`log`** | Enable the [Logger extension](https://docs.rs/async-graphql/latest/async_graphql/extensions/struct.Logger.html). | -//! | **`opentelemetry`** | Enable the [OpenTelemetry extension](https://docs.rs/async-graphql/latest/async_graphql/extensions/struct.OpenTelemetry.html). | -//! | **`playground`** | Enables the [GraphQL playground IDE](https://github.com/graphql/graphql-playground) integration | -//! | **`rawvalue`** | Support raw values from [`serde_json`](https://crates.io/crates/serde_json) | -//! | **`secrecy`** | Integrate with the [`secrecy` crate](https://crates.io/crates/secrecy). | -//! | **`smol_str`** | Integrate with the [`smol_str` crate](https://crates.io/crates/smol_str). | -//! | **`string_number`** | Enable the [StringNumber](types/struct.StringNumber.html). | -//! | **`time`** | Integrate with the [`time` crate](https://github.com/time-rs/time). | -//! | **`tracing`** | Enable the [Tracing extension](https://docs.rs/async-graphql/latest/async_graphql/extensions/struct.Tracing.html). | -//! | **`tempfile`** | Save the uploaded content in the temporary file. | -//! | **`tokio-sync`** | Integrate with the [`tokio::sync::RwLock`](https://docs.rs/tokio/1.18.1/tokio/sync/struct.RwLock.html) and [`tokio::sync::Mutex`](https://docs.rs/tokio/1.18.1/tokio/sync/struct.Mutex.html). | -//! | **`unblock`** | Support [Asynchronous reader for Upload](types/struct.Upload.html) | -//! | **`uuid`** | Integrate with the [`uuid` crate](https://crates.io/crates/uuid). | -//! | **`url`** | Integrate with the [`url` crate](https://crates.io/crates/url). | -//! -//! ## Integrations -//! -//! * Poem [async-graphql-poem](https://crates.io/crates/async-graphql-poem) -//! * Actix-web [async-graphql-actix-web](https://crates.io/crates/async-graphql-actix-web) -//! * Warp [async-graphql-warp](https://crates.io/crates/async-graphql-warp) -//! * Tide [async-graphql-tide](https://crates.io/crates/async-graphql-tide) -//! * Rocket [async-graphql-rocket](https://github.com/async-graphql/async-graphql/tree/master/integrations/rocket) -//! * Axum [async-graphql-axum](https://github.com/async-graphql/async-graphql/tree/master/integrations/axum) -//! -//! ## License -//! -//! Licensed under either of -//! -//! * Apache License, Version 2.0, (./LICENSE-APACHE or ) -//! * MIT license (./LICENSE-MIT or ) at -//! your option. -//! -//! ## References -//! -//! * [GraphQL](https://graphql.org) -//! * [GraphQL Multipart Request](https://github.com/jaydenseric/graphql-multipart-request-spec) -//! * [GraphQL Cursor Connections Specification](https://facebook.github.io/relay/graphql/connections.htm) -//! * [GraphQL over WebSocket Protocol](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md) -//! * [Apollo Tracing](https://github.com/apollographql/apollo-tracing) -//! * [Apollo Federation](https://www.apollographql.com/docs/apollo-server/federation/introduction) -//! -//! ## Examples -//! -//! All examples are in the [sub-repository](https://github.com/async-graphql/examples), located in the examples directory. -//! -//! **Run an example:** -//! -//! ```shell -//! git submodule update # update the examples repo -//! cd examples && cargo run --bin [name] -//! ``` -//! -//! ## Benchmarks -//! -//! Ensure that there is no CPU-heavy process in background! -//! -//! ```shell script -//! cd benchmark -//! cargo bench -//! ``` -//! -//! Now a HTML report is available at `benchmark/target/criterion/report`. - -#![deny(clippy::all)] -// #![deny(clippy::pedantic)] -#![deny(clippy::inefficient_to_string)] -#![deny(clippy::match_wildcard_for_single_variants)] -#![allow(clippy::module_name_repetitions)] -#![allow(clippy::similar_names)] -#![allow(clippy::if_not_else)] -#![allow(clippy::doc_markdown)] -#![allow(clippy::must_use_candidate)] -#![allow(clippy::missing_errors_doc)] -#![allow(clippy::needless_pass_by_value)] -#![deny(clippy::redundant_closure_for_method_calls)] -#![allow(clippy::option_if_let_else)] -#![allow(clippy::match_same_arms)] -#![allow(clippy::default_trait_access)] -#![allow(clippy::map_flatten)] -#![allow(clippy::map_unwrap_or)] -#![allow(clippy::explicit_iter_loop)] -#![allow(clippy::too_many_lines)] -#![allow(clippy::cast_sign_loss)] -#![allow(clippy::unused_self)] -#![allow(clippy::cast_lossless)] -#![allow(clippy::cast_possible_truncation)] -#![allow(clippy::implicit_hasher)] -// #![deny(clippy::nursery)] -#![allow(clippy::use_self)] -#![allow(clippy::missing_const_for_fn)] -#![allow(clippy::needless_borrow)] -#![allow(clippy::future_not_send)] -#![allow(clippy::redundant_pub_crate)] -#![allow(clippy::cognitive_complexity)] -#![allow(clippy::useless_let_if_seq)] -#![allow(clippy::uninlined_format_args)] -#![warn(missing_docs)] -#![allow(clippy::trivially_copy_pass_by_ref)] -#![allow(clippy::upper_case_acronyms)] -#![recursion_limit = "256"] -#![forbid(unsafe_code)] -#![cfg_attr(docsrs, feature(doc_cfg))] - -mod base; -mod custom_directive; -mod error; -mod executor; -mod guard; -mod look_ahead; -mod model; -mod request; -mod response; -mod schema; -mod subscription; -mod validation; - -pub mod context; -#[cfg(feature = "dataloader")] -#[cfg_attr(docsrs, doc(cfg(feature = "dataloader")))] -pub mod dataloader; -#[cfg(feature = "dynamic-schema")] -#[cfg_attr(docsrs, doc(cfg(feature = "dynamic-schema")))] -pub mod dynamic; -pub mod extensions; -pub mod http; -pub mod resolver_utils; -pub mod types; -#[doc(hidden)] -pub mod validators; - -#[doc(hidden)] -pub mod registry; - -pub use async_graphql_parser as parser; -pub use async_graphql_value::{ - ConstValue as Value, DeserializerError, Extensions, Name, Number, SerializerError, Variables, - from_value, to_value, value, -}; -#[doc(hidden)] -pub use async_stream; -#[doc(hidden)] -pub use async_trait; -pub use base::{ - ComplexObject, Description, InputObjectType, InputType, InterfaceType, ObjectType, - OneofObjectType, OutputType, TypeName, UnionType, -}; -#[doc(hidden)] -pub use context::ContextSelectionSet; -pub use context::*; -pub use custom_directive::{CustomDirective, CustomDirectiveFactory, TypeDirective}; -pub use error::{ - Error, ErrorExtensionValues, ErrorExtensions, InputValueError, InputValueResult, - ParseRequestError, PathSegment, Result, ResultExt, ServerError, ServerResult, -}; -pub use executor::Executor; -pub use extensions::ResolveFut; -#[doc(hidden)] -pub use futures_util; -pub use guard::{Guard, GuardExt}; -#[doc(hidden)] -pub use indexmap; -pub use look_ahead::Lookahead; -#[doc(no_inline)] -pub use parser::{Pos, Positioned}; -pub use registry::{CacheControl, SDLExportOptions}; -pub use request::{BatchRequest, Request}; -#[doc(no_inline)] -pub use resolver_utils::{ContainerType, EnumType, ScalarType}; -pub use response::{BatchResponse, Response}; -pub use schema::{IntrospectionMode, Schema, SchemaBuilder, SchemaEnv}; -#[doc(hidden)] -pub use static_assertions_next; -pub use subscription::SubscriptionType; -pub use types::*; -pub use validation::{ValidationMode, ValidationResult, VisitorContext}; -pub use validators::CustomValidator; - -/// An alias of [async_graphql::Error](struct.Error.html). Present for backward -/// compatibility reasons. -pub type FieldError = Error; - -/// An alias of [async_graphql::Result](type.Result.html). Present for backward -/// compatibility reasons. -pub type FieldResult = Result; - -#[doc = include_str!("docs/complex_object.md")] -pub use async_graphql_derive::ComplexObject; -#[doc = include_str!("docs/description.md")] -pub use async_graphql_derive::Description; -#[doc = include_str!("docs/directive.md")] -pub use async_graphql_derive::Directive; -#[doc = include_str!("docs/enum.md")] -pub use async_graphql_derive::Enum; -#[doc = include_str!("docs/input_object.md")] -pub use async_graphql_derive::InputObject; -#[doc = include_str!("docs/interface.md")] -pub use async_graphql_derive::Interface; -#[doc = include_str!("docs/merged_object.md")] -pub use async_graphql_derive::MergedObject; -#[doc = include_str!("docs/merged_subscription.md")] -pub use async_graphql_derive::MergedSubscription; -#[doc = include_str!("docs/newtype.md")] -pub use async_graphql_derive::NewType; -#[doc = include_str!("docs/object.md")] -pub use async_graphql_derive::Object; -#[doc = include_str!("docs/oneof_object.md")] -pub use async_graphql_derive::OneofObject; -#[doc = include_str!("docs/scalar.md")] -pub use async_graphql_derive::Scalar; -#[doc = include_str!("docs/simple_object.md")] -pub use async_graphql_derive::SimpleObject; -#[doc = include_str!("docs/subscription.md")] -pub use async_graphql_derive::Subscription; -pub use async_graphql_derive::TypeDirective; -#[doc = include_str!("docs/union.md")] -pub use async_graphql_derive::Union; diff --git a/src/look_ahead.rs b/src/look_ahead.rs deleted file mode 100644 index fbafbc87f..000000000 --- a/src/look_ahead.rs +++ /dev/null @@ -1,368 +0,0 @@ -use std::collections::HashMap; - -use crate::{ - Context, Name, Positioned, SelectionField, - parser::types::{Field, FragmentDefinition, Selection, SelectionSet}, -}; - -/// A selection performed by a query. -pub struct Lookahead<'a> { - fragments: &'a HashMap>, - fields: Vec<&'a Field>, - context: &'a Context<'a>, -} - -impl<'a> Lookahead<'a> { - pub(crate) fn new( - fragments: &'a HashMap>, - field: &'a Field, - context: &'a Context<'a>, - ) -> Self { - Self { - fragments, - fields: vec![field], - context, - } - } - - /// Get the field of the selection set with the specified name. This will - /// ignore aliases. - /// - /// For example, calling `.field("a")` on `{ a { b } }` will return a - /// lookahead that represents `{ b }`. - #[must_use] - pub fn field(&self, name: &str) -> Self { - let mut fields = Vec::new(); - for field in &self.fields { - filter(&mut fields, self.fragments, &field.selection_set.node, name) - } - - Self { - fragments: self.fragments, - fields, - context: self.context, - } - } - - /// Returns true if field exists otherwise return false. - #[inline] - pub fn exists(&self) -> bool { - !self.fields.is_empty() - } - - /// Get the `SelectionField`s for each of the fields covered by this - /// `Lookahead`. - /// - /// There will be multiple fields in situations where the same field is - /// queried twice. - pub fn selection_fields(&self) -> Vec> { - self.fields - .iter() - .map(|field| SelectionField { - fragments: self.fragments, - field, - context: self.context, - }) - .collect() - } -} - -impl<'a> From> for Lookahead<'a> { - fn from(selection_field: SelectionField<'a>) -> Self { - Lookahead { - fragments: selection_field.fragments, - fields: vec![selection_field.field], - context: selection_field.context, - } - } -} - -/// Convert a slice of `SelectionField`s to a `Lookahead`. -/// Assumes all `SelectionField`s are from the same query and thus have the same -/// fragments. -/// -/// Fails if either no `SelectionField`s were provided. -impl<'a> TryFrom<&[SelectionField<'a>]> for Lookahead<'a> { - type Error = (); - - fn try_from(selection_fields: &[SelectionField<'a>]) -> Result { - if selection_fields.is_empty() { - Err(()) - } else { - Ok(Lookahead { - fragments: selection_fields[0].fragments, - fields: selection_fields - .iter() - .map(|selection_field| selection_field.field) - .collect(), - context: selection_fields[0].context, - }) - } - } -} - -fn filter<'a>( - fields: &mut Vec<&'a Field>, - fragments: &'a HashMap>, - selection_set: &'a SelectionSet, - name: &str, -) { - for item in &selection_set.items { - match &item.node { - Selection::Field(field) => { - if field.node.name.node == name { - fields.push(&field.node) - } - } - Selection::InlineFragment(fragment) => { - filter(fields, fragments, &fragment.node.selection_set.node, name) - } - Selection::FragmentSpread(spread) => { - if let Some(fragment) = fragments.get(&spread.node.fragment_name.node) { - filter(fields, fragments, &fragment.node.selection_set.node, name) - } - } - } - } -} - -#[cfg(test)] -mod tests { - use crate::*; - - #[tokio::test] - async fn test_look_ahead() { - #[derive(SimpleObject)] - #[graphql(internal)] - struct Detail { - c: i32, - d: i32, - } - - #[derive(SimpleObject)] - #[graphql(internal)] - struct MyObj { - a: i32, - b: i32, - detail: Detail, - } - - struct Query; - - #[Object(internal)] - impl Query { - async fn obj(&self, ctx: &Context<'_>, n: i32) -> MyObj { - if ctx.look_ahead().field("a").exists() { - // This is a query like `obj { a }` - assert_eq!(n, 1); - } else if ctx.look_ahead().field("detail").field("c").exists() - && ctx.look_ahead().field("detail").field("d").exists() - { - // This is a query like `obj { detail { c } }` - assert_eq!(n, 2); - } else if ctx.look_ahead().field("detail").field("c").exists() { - // This is a query like `obj { detail { c } }` - assert_eq!(n, 3); - } else { - // This query doesn't have `a` - assert_eq!(n, 4); - } - MyObj { - a: 0, - b: 0, - detail: Detail { c: 0, d: 0 }, - } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - assert!( - schema - .execute( - r#"{ - obj(n: 1) { - a - } - }"#, - ) - .await - .is_ok() - ); - - assert!( - schema - .execute( - r#"{ - obj(n: 1) { - k:a - } - }"#, - ) - .await - .is_ok() - ); - - assert!( - schema - .execute( - r#"{ - obj(n: 3) { - detail { - c - } - } - }"#, - ) - .await - .is_ok() - ); - - assert!( - schema - .execute( - r#"{ - obj(n: 2) { - detail { - d - } - - detail { - c - } - } - }"#, - ) - .await - .is_ok() - ); - - assert!( - schema - .execute( - r#"{ - obj(n: 4) { - b - } - }"#, - ) - .await - .is_ok() - ); - - assert!( - schema - .execute( - r#"{ - obj(n: 1) { - ... { - a - } - } - }"#, - ) - .await - .is_ok() - ); - - assert!( - schema - .execute( - r#"{ - obj(n: 3) { - ... { - detail { - c - } - } - } - }"#, - ) - .await - .is_ok() - ); - - assert!( - schema - .execute( - r#"{ - obj(n: 2) { - ... { - detail { - d - } - - detail { - c - } - } - } - }"#, - ) - .await - .is_ok() - ); - - assert!( - schema - .execute( - r#"{ - obj(n: 1) { - ... A - } - } - - fragment A on MyObj { - a - }"#, - ) - .await - .is_ok() - ); - - assert!( - schema - .execute( - r#"{ - obj(n: 3) { - ... A - } - } - - fragment A on MyObj { - detail { - c - } - }"#, - ) - .await - .is_ok() - ); - - assert!( - schema - .execute( - r#"{ - obj(n: 2) { - ... A - ... B - } - } - - fragment A on MyObj { - detail { - d - } - } - - fragment B on MyObj { - detail { - c - } - }"#, - ) - .await - .is_ok() - ); - } -} diff --git a/src/model/directive.rs b/src/model/directive.rs deleted file mode 100644 index 84ba71d54..000000000 --- a/src/model/directive.rs +++ /dev/null @@ -1,158 +0,0 @@ -use std::collections::HashSet; - -use crate::{Enum, Object, model::__InputValue, registry}; - -/// A Directive can be adjacent to many parts of the GraphQL language, a -/// __DirectiveLocation describes one such possible adjacencies. -#[derive(Debug, Enum, Copy, Clone, Eq, PartialEq)] -#[graphql(internal, name = "__DirectiveLocation")] -#[allow(non_camel_case_types)] -pub enum __DirectiveLocation { - /// Location adjacent to a query operation. - QUERY, - - /// Location adjacent to a mutation operation. - MUTATION, - - /// Location adjacent to a subscription operation. - SUBSCRIPTION, - - /// Location adjacent to a field. - FIELD, - - /// Location adjacent to a fragment definition. - FRAGMENT_DEFINITION, - - /// Location adjacent to a fragment spread. - FRAGMENT_SPREAD, - - /// Location adjacent to an inline fragment. - INLINE_FRAGMENT, - - /// Location adjacent to a variable definition. - VARIABLE_DEFINITION, - - /// Location adjacent to a schema definition. - SCHEMA, - - /// Location adjacent to a scalar definition. - SCALAR, - - /// Location adjacent to an object type definition. - OBJECT, - - /// Location adjacent to a field definition. - FIELD_DEFINITION, - - /// Location adjacent to an argument definition. - ARGUMENT_DEFINITION, - - /// Location adjacent to an interface definition. - INTERFACE, - - /// Location adjacent to a union definition. - UNION, - - /// Location adjacent to an enum definition. - ENUM, - - /// Location adjacent to an enum value definition. - ENUM_VALUE, - - /// Location adjacent to an input object type definition. - INPUT_OBJECT, - - /// Location adjacent to an input object field definition. - INPUT_FIELD_DEFINITION, -} - -// Traits for compile time checking if location at which directive is called is -// supported by directives definition Would be nice to auto generate traits from -// variants of __DirectiveLocation -#[doc(hidden)] -#[allow(non_camel_case_types)] -pub mod location_traits { - pub trait Directive_At_FIELD_DEFINITION { - fn check() {} - } - - pub trait Directive_At_OBJECT { - fn check() {} - } - - pub trait Directive_At_INPUT_FIELD_DEFINITION { - fn check() {} - } - - pub trait Directive_At_ARGUMENT_DEFINITION { - fn check() {} - } - - pub trait Directive_At_INPUT_OBJECT { - fn check() {} - } - - pub trait Directive_At_INTERFACE { - fn check() {} - } - - pub trait Directive_At_ENUM { - fn check() {} - } - - pub trait Directive_At_ENUM_VALUE { - fn check() {} - } -} - -pub struct __Directive<'a> { - pub registry: &'a registry::Registry, - pub visible_types: &'a HashSet<&'a str>, - pub directive: &'a registry::MetaDirective, -} - -/// A Directive provides a way to describe alternate runtime execution and type -/// validation behavior in a GraphQL document. -/// -/// In some cases, you need to provide options to alter GraphQL's execution -/// behavior in ways field arguments will not suffice, such as conditionally -/// including or skipping a field. Directives provide this by describing -/// additional information to the executor. -#[Object(internal, name = "__Directive")] -impl<'a> __Directive<'a> { - #[inline] - async fn name(&self) -> &str { - &self.directive.name - } - - #[inline] - async fn description(&self) -> Option<&str> { - self.directive.description.as_deref() - } - - #[inline] - async fn locations(&self) -> &Vec<__DirectiveLocation> { - &self.directive.locations - } - - async fn args( - &self, - #[graphql(default = false)] include_deprecated: bool, - ) -> Vec<__InputValue<'a>> { - self.directive - .args - .values() - .filter(|input_value| include_deprecated || !input_value.deprecation.is_deprecated()) - .map(|input_value| __InputValue { - registry: self.registry, - visible_types: self.visible_types, - input_value, - }) - .collect() - } - - #[inline] - async fn is_repeatable(&self) -> bool { - self.directive.is_repeatable - } -} diff --git a/src/model/enum_value.rs b/src/model/enum_value.rs deleted file mode 100644 index a2d49c386..000000000 --- a/src/model/enum_value.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::{Object, registry}; - -pub struct __EnumValue<'a> { - pub value: &'a registry::MetaEnumValue, -} - -/// One possible value for a given Enum. Enum values are unique values, not a -/// placeholder for a string or numeric value. However an Enum value is returned -/// in a JSON response as a string. -#[Object(internal, name = "__EnumValue")] -impl __EnumValue<'_> { - #[inline] - async fn name(&self) -> &str { - &self.value.name - } - - #[inline] - async fn description(&self) -> Option<&str> { - self.value.description.as_deref() - } - - #[inline] - async fn is_deprecated(&self) -> bool { - self.value.deprecation.is_deprecated() - } - - #[inline] - async fn deprecation_reason(&self) -> Option<&str> { - self.value.deprecation.reason() - } -} diff --git a/src/model/field.rs b/src/model/field.rs deleted file mode 100644 index 9a213dee9..000000000 --- a/src/model/field.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::collections::HashSet; - -use crate::{ - Context, Object, - model::{__InputValue, __Type}, - registry, - registry::is_visible, -}; - -pub struct __Field<'a> { - pub registry: &'a registry::Registry, - pub visible_types: &'a HashSet<&'a str>, - pub field: &'a registry::MetaField, -} - -/// Object and Interface types are described by a list of Fields, each of which -/// has a name, potentially a list of arguments, and a return type. -#[Object(internal, name = "__Field")] -impl<'a> __Field<'a> { - #[inline] - async fn name(&self) -> &str { - &self.field.name - } - - #[inline] - async fn description(&self) -> Option<&str> { - self.field.description.as_deref() - } - - async fn args( - &self, - ctx: &Context<'_>, - #[graphql(default = false)] include_deprecated: bool, - ) -> Vec<__InputValue<'a>> { - self.field - .args - .values() - .filter(|input_value| include_deprecated || !input_value.deprecation.is_deprecated()) - .filter(|input_value| is_visible(ctx, &input_value.visible)) - .map(|input_value| __InputValue { - registry: self.registry, - visible_types: self.visible_types, - input_value, - }) - .collect() - } - - #[graphql(name = "type")] - async fn ty(&self) -> __Type<'a> { - __Type::new(self.registry, self.visible_types, &self.field.ty) - } - - #[inline] - async fn is_deprecated(&self) -> bool { - self.field.deprecation.is_deprecated() - } - - #[inline] - async fn deprecation_reason(&self) -> Option<&str> { - self.field.deprecation.reason() - } -} diff --git a/src/model/input_value.rs b/src/model/input_value.rs deleted file mode 100644 index 8135dbdf7..000000000 --- a/src/model/input_value.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::collections::HashSet; - -use crate::{Object, model::__Type, registry}; - -pub struct __InputValue<'a> { - pub registry: &'a registry::Registry, - pub visible_types: &'a HashSet<&'a str>, - pub input_value: &'a registry::MetaInputValue, -} - -/// Arguments provided to Fields or Directives and the input fields of an -/// InputObject are represented as Input Values which describe their type and -/// optionally a default value. -#[Object(internal, name = "__InputValue")] -impl<'a> __InputValue<'a> { - #[inline] - async fn name(&self) -> &str { - &self.input_value.name - } - - #[inline] - async fn description(&self) -> Option<&str> { - self.input_value.description.as_deref() - } - - #[graphql(name = "type")] - #[inline] - async fn ty(&self) -> __Type<'a> { - __Type::new(self.registry, self.visible_types, &self.input_value.ty) - } - - #[inline] - async fn default_value(&self) -> Option<&str> { - self.input_value.default_value.as_deref() - } - - #[inline] - async fn is_deprecated(&self) -> bool { - self.input_value.deprecation.is_deprecated() - } - - #[inline] - async fn deprecation_reason(&self) -> Option<&str> { - self.input_value.deprecation.reason() - } -} diff --git a/src/model/kind.rs b/src/model/kind.rs deleted file mode 100644 index e3cc3a12f..000000000 --- a/src/model/kind.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::Enum; - -/// An enum describing what kind of type a given `__Type` is. -#[derive(Enum, Copy, Clone, Eq, PartialEq)] -#[graphql(internal, name = "__TypeKind")] -pub enum __TypeKind { - /// Indicates this type is a scalar. - Scalar, - - /// Indicates this type is an object. `fields` and `interfaces` are valid - /// fields. - Object, - - /// Indicates this type is an interface. `fields` and `possibleTypes` are - /// valid fields. - Interface, - - /// Indicates this type is a union. `possibleTypes` is a valid field. - Union, - - /// Indicates this type is an enum. `enumValues` is a valid field. - Enum, - - /// Indicates this type is an input object. `inputFields` is a valid field. - InputObject, - - /// Indicates this type is a list. `ofType` is a valid field. - List, - - /// Indicates this type is a non-null. `ofType` is a valid field. - NonNull, -} diff --git a/src/model/mod.rs b/src/model/mod.rs deleted file mode 100644 index 6a491856c..000000000 --- a/src/model/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -mod directive; -mod enum_value; -mod field; -mod input_value; -mod kind; -mod schema; -mod r#type; - -pub use directive::{__Directive, __DirectiveLocation, location_traits}; -pub use enum_value::__EnumValue; -pub use field::__Field; -pub use input_value::__InputValue; -pub use kind::__TypeKind; -pub use schema::__Schema; -pub use r#type::__Type; diff --git a/src/model/schema.rs b/src/model/schema.rs deleted file mode 100644 index 2eeebb53d..000000000 --- a/src/model/schema.rs +++ /dev/null @@ -1,116 +0,0 @@ -use std::collections::HashSet; - -use crate::{ - Object, - model::{__Directive, __Type}, - registry, -}; - -pub struct __Schema<'a> { - registry: &'a registry::Registry, - visible_types: &'a HashSet<&'a str>, -} - -impl<'a> __Schema<'a> { - pub fn new(registry: &'a registry::Registry, visible_types: &'a HashSet<&'a str>) -> Self { - Self { - registry, - visible_types, - } - } -} - -/// A GraphQL Schema defines the capabilities of a GraphQL server. It exposes -/// all available types and directives on the server, as well as the entry -/// points for query, mutation, and subscription operations. -#[Object(internal, name = "__Schema")] -impl<'a> __Schema<'a> { - /// description of __Schema for newer graphiql introspection schema - /// requirements - async fn description(&self) -> String { - String::from( - "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", - ) - } - - /// A list of all types supported by this server. - async fn types(&self) -> Vec<__Type<'a>> { - let mut types: Vec<_> = self - .registry - .types - .values() - .filter_map(|ty| { - if self.visible_types.contains(ty.name()) { - Some(( - ty.name(), - __Type::new_simple(self.registry, self.visible_types, ty), - )) - } else { - None - } - }) - .collect(); - types.sort_by(|a, b| a.0.cmp(b.0)); - types.into_iter().map(|(_, ty)| ty).collect() - } - - /// The type that query operations will be rooted at. - #[inline] - async fn query_type(&self) -> __Type<'a> { - __Type::new_simple( - self.registry, - self.visible_types, - &self.registry.types[&self.registry.query_type], - ) - } - - /// If this server supports mutation, the type that mutation operations will - /// be rooted at. - #[inline] - async fn mutation_type(&self) -> Option<__Type<'a>> { - self.registry.mutation_type.as_ref().and_then(|ty| { - if self.visible_types.contains(ty.as_str()) { - Some(__Type::new_simple( - self.registry, - self.visible_types, - &self.registry.types[ty], - )) - } else { - None - } - }) - } - - /// If this server support subscription, the type that subscription - /// operations will be rooted at. - #[inline] - async fn subscription_type(&self) -> Option<__Type<'a>> { - self.registry.subscription_type.as_ref().and_then(|ty| { - if self.visible_types.contains(ty.as_str()) { - Some(__Type::new_simple( - self.registry, - self.visible_types, - &self.registry.types[ty], - )) - } else { - None - } - }) - } - - /// A list of all directives supported by this server. - async fn directives(&self) -> Vec<__Directive<'a>> { - let mut directives: Vec<_> = self - .registry - .directives - .values() - .map(|directive| __Directive { - registry: self.registry, - visible_types: self.visible_types, - directive, - }) - .collect(); - directives.sort_by(|a, b| a.directive.name.cmp(&b.directive.name)); - directives - } -} diff --git a/src/model/type.rs b/src/model/type.rs deleted file mode 100644 index 167189853..000000000 --- a/src/model/type.rs +++ /dev/null @@ -1,252 +0,0 @@ -use std::collections::HashSet; - -use crate::{ - Context, Object, - model::{__EnumValue, __Field, __InputValue, __TypeKind}, - registry, - registry::is_visible, -}; - -enum TypeDetail<'a> { - Named(&'a registry::MetaType), - NonNull(String), - List(String), -} - -pub struct __Type<'a> { - registry: &'a registry::Registry, - visible_types: &'a HashSet<&'a str>, - detail: TypeDetail<'a>, -} - -impl<'a> __Type<'a> { - #[inline] - pub fn new_simple( - registry: &'a registry::Registry, - visible_types: &'a HashSet<&'a str>, - ty: &'a registry::MetaType, - ) -> __Type<'a> { - __Type { - registry, - visible_types, - detail: TypeDetail::Named(ty), - } - } - - #[inline] - pub fn new( - registry: &'a registry::Registry, - visible_types: &'a HashSet<&'a str>, - type_name: &str, - ) -> __Type<'a> { - match registry::MetaTypeName::create(type_name) { - registry::MetaTypeName::NonNull(ty) => __Type { - registry, - visible_types, - detail: TypeDetail::NonNull(ty.to_string()), - }, - registry::MetaTypeName::List(ty) => __Type { - registry, - visible_types, - detail: TypeDetail::List(ty.to_string()), - }, - registry::MetaTypeName::Named(ty) => __Type { - registry, - visible_types, - detail: TypeDetail::Named(match registry.types.get(ty) { - Some(t) => t, - None => panic!("Type '{}' not found!", ty), - }), - }, - } - } -} - -/// The fundamental unit of any GraphQL Schema is the type. There are many kinds -/// of types in GraphQL as represented by the `__TypeKind` enum. -/// -/// Depending on the kind of a type, certain fields describe information about -/// that type. Scalar types provide no information beyond a name and -/// description, while Enum types provide their values. Object and Interface -/// types provide the fields they describe. Abstract types, Union and Interface, -/// provide the Object types possible at runtime. List and NonNull types compose -/// other types. -#[Object(internal, name = "__Type")] -impl<'a> __Type<'a> { - #[inline] - async fn kind(&self) -> __TypeKind { - match &self.detail { - TypeDetail::Named(ty) => match ty { - registry::MetaType::Scalar { .. } => __TypeKind::Scalar, - registry::MetaType::Object { .. } => __TypeKind::Object, - registry::MetaType::Interface { .. } => __TypeKind::Interface, - registry::MetaType::Union { .. } => __TypeKind::Union, - registry::MetaType::Enum { .. } => __TypeKind::Enum, - registry::MetaType::InputObject { .. } => __TypeKind::InputObject, - }, - TypeDetail::NonNull(_) => __TypeKind::NonNull, - TypeDetail::List(_) => __TypeKind::List, - } - } - - #[inline] - async fn name(&self) -> Option<&str> { - match &self.detail { - TypeDetail::Named(ty) => Some(ty.name()), - TypeDetail::NonNull(_) => None, - TypeDetail::List(_) => None, - } - } - - #[inline] - async fn description(&self) -> Option<&str> { - match &self.detail { - TypeDetail::Named(ty) => match ty { - registry::MetaType::Scalar { description, .. } - | registry::MetaType::Object { description, .. } - | registry::MetaType::Interface { description, .. } - | registry::MetaType::Union { description, .. } - | registry::MetaType::Enum { description, .. } - | registry::MetaType::InputObject { description, .. } => description.as_deref(), - }, - TypeDetail::NonNull(_) => None, - TypeDetail::List(_) => None, - } - } - - async fn fields( - &self, - ctx: &Context<'_>, - #[graphql(default = false)] include_deprecated: bool, - ) -> Option>> { - if let TypeDetail::Named(ty) = &self.detail { - ty.fields().map(|fields| { - fields - .values() - .filter(|field| is_visible(ctx, &field.visible)) - .filter(|field| { - (include_deprecated || !field.deprecation.is_deprecated()) - && !field.name.starts_with("__") - }) - .map(|field| __Field { - registry: self.registry, - visible_types: self.visible_types, - field, - }) - .collect() - }) - } else { - None - } - } - - async fn interfaces(&self) -> Option>> { - if let TypeDetail::Named(registry::MetaType::Object { name, .. }) = &self.detail { - Some( - self.registry - .implements - .get(name) - .unwrap_or(&Default::default()) - .iter() - .filter(|ty| self.visible_types.contains(ty.as_str())) - .map(|ty| __Type::new(self.registry, self.visible_types, ty)) - .collect(), - ) - } else { - None - } - } - - async fn possible_types(&self) -> Option>> { - if let TypeDetail::Named(registry::MetaType::Interface { possible_types, .. }) - | TypeDetail::Named(registry::MetaType::Union { possible_types, .. }) = &self.detail - { - Some( - possible_types - .iter() - .filter(|ty| self.visible_types.contains(ty.as_str())) - .map(|ty| __Type::new(self.registry, self.visible_types, ty)) - .collect(), - ) - } else { - None - } - } - - async fn enum_values( - &self, - ctx: &Context<'_>, - #[graphql(default = false)] include_deprecated: bool, - ) -> Option>> { - if let TypeDetail::Named(registry::MetaType::Enum { enum_values, .. }) = &self.detail { - Some( - enum_values - .values() - .filter(|value| is_visible(ctx, &value.visible)) - .filter(|value| include_deprecated || !value.deprecation.is_deprecated()) - .map(|value| __EnumValue { value }) - .collect(), - ) - } else { - None - } - } - - async fn input_fields( - &self, - ctx: &Context<'_>, - #[graphql(default = false)] include_deprecated: bool, - ) -> Option>> { - if let TypeDetail::Named(registry::MetaType::InputObject { input_fields, .. }) = - &self.detail - { - Some( - input_fields - .values() - .filter(|input_value| { - include_deprecated || !input_value.deprecation.is_deprecated() - }) - .filter(|input_value| is_visible(ctx, &input_value.visible)) - .map(|input_value| __InputValue { - registry: self.registry, - visible_types: self.visible_types, - input_value, - }) - .collect(), - ) - } else { - None - } - } - - #[inline] - async fn of_type(&self) -> Option<__Type<'a>> { - if let TypeDetail::List(ty) = &self.detail { - Some(__Type::new(self.registry, self.visible_types, &ty)) - } else if let TypeDetail::NonNull(ty) = &self.detail { - Some(__Type::new(self.registry, self.visible_types, &ty)) - } else { - None - } - } - - #[graphql(name = "specifiedByURL")] - async fn specified_by_url(&self) -> Option<&'a str> { - if let TypeDetail::Named(registry::MetaType::Scalar { - specified_by_url, .. - }) = &self.detail - { - specified_by_url.as_deref() - } else { - None - } - } - - async fn is_one_of(&self) -> Option { - if let TypeDetail::Named(registry::MetaType::InputObject { oneof, .. }) = &self.detail { - Some(*oneof) - } else { - None - } - } -} diff --git a/src/registry/cache_control.rs b/src/registry/cache_control.rs deleted file mode 100644 index fa92873c5..000000000 --- a/src/registry/cache_control.rs +++ /dev/null @@ -1,200 +0,0 @@ -/// Cache control value -/// -/// # Examples -/// -/// ```rust -/// use async_graphql::*; -/// -/// struct Query; -/// -/// #[Object(cache_control(max_age = 60))] -/// impl Query { -/// #[graphql(cache_control(max_age = 30))] -/// async fn value1(&self) -> i32 { -/// 0 -/// } -/// -/// #[graphql(cache_control(private))] -/// async fn value2(&self) -> i32 { -/// 0 -/// } -/// -/// #[graphql(cache_control(no_cache))] -/// async fn value3(&self) -> i32 { -/// 0 -/// } -/// } -/// -/// # tokio::runtime::Runtime::new().unwrap().block_on(async { -/// let schema = Schema::new(Query, EmptyMutation, EmptySubscription); -/// assert_eq!( -/// schema -/// .execute("{ value1 }") -/// .await -/// .into_result() -/// .unwrap() -/// .cache_control, -/// CacheControl { -/// public: true, -/// max_age: 30 -/// } -/// ); -/// -/// assert_eq!( -/// schema -/// .execute("{ value2 }") -/// .await -/// .into_result() -/// .unwrap() -/// .cache_control, -/// CacheControl { -/// public: false, -/// max_age: 60 -/// } -/// ); -/// -/// assert_eq!( -/// schema -/// .execute("{ value1 value2 }") -/// .await -/// .into_result() -/// .unwrap() -/// .cache_control, -/// CacheControl { -/// public: false, -/// max_age: 30 -/// } -/// ); -/// -/// assert_eq!( -/// schema -/// .execute("{ value1 value2 value3 }") -/// .await -/// .into_result() -/// .unwrap() -/// .cache_control, -/// CacheControl { -/// public: false, -/// max_age: -1 -/// } -/// ); -/// # }); -/// ``` -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct CacheControl { - /// Scope is public, default is true. - pub public: bool, - - /// Cache max age, `-1` represent `no-cache`, default is 0. - pub max_age: i32, -} - -impl Default for CacheControl { - fn default() -> Self { - Self { - public: true, - max_age: 0, - } - } -} - -impl CacheControl { - /// Get 'Cache-Control' header value. - #[must_use] - pub fn value(&self) -> Option { - let mut value = if self.max_age > 0 { - format!("max-age={}", self.max_age) - } else if self.max_age == -1 { - "no-cache".to_string() - } else { - String::new() - }; - - if !self.public { - if !value.is_empty() { - value += ", "; - } - value += "private"; - } - - if !value.is_empty() { Some(value) } else { None } - } -} - -impl CacheControl { - #[must_use] - pub(crate) fn merge(self, other: &CacheControl) -> CacheControl { - CacheControl { - public: self.public && other.public, - max_age: match (self.max_age, other.max_age) { - (-1, _) => -1, - (_, -1) => -1, - (a, 0) => a, - (0, b) => b, - (a, b) => a.min(b), - }, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn to_value() { - assert_eq!( - CacheControl { - public: true, - max_age: 0, - } - .value(), - None - ); - - assert_eq!( - CacheControl { - public: false, - max_age: 0, - } - .value(), - Some("private".to_string()) - ); - - assert_eq!( - CacheControl { - public: false, - max_age: 10, - } - .value(), - Some("max-age=10, private".to_string()) - ); - - assert_eq!( - CacheControl { - public: true, - max_age: 10, - } - .value(), - Some("max-age=10".to_string()) - ); - - assert_eq!( - CacheControl { - public: true, - max_age: -1, - } - .value(), - Some("no-cache".to_string()) - ); - - assert_eq!( - CacheControl { - public: false, - max_age: -1, - } - .value(), - Some("no-cache, private".to_string()) - ); - } -} diff --git a/src/registry/export_sdl.rs b/src/registry/export_sdl.rs deleted file mode 100644 index 60e1e58e9..000000000 --- a/src/registry/export_sdl.rs +++ /dev/null @@ -1,921 +0,0 @@ -use std::{collections::HashMap, fmt::Write}; - -use crate::registry::{Deprecation, MetaField, MetaInputValue, MetaType, Registry}; - -const SYSTEM_SCALARS: &[&str] = &["Int", "Float", "String", "Boolean", "ID"]; -const FEDERATION_SCALARS: &[&str] = &["Any"]; - -/// Options for SDL export -#[derive(Debug, Copy, Clone)] -pub struct SDLExportOptions { - sorted_fields: bool, - sorted_arguments: bool, - sorted_enum_values: bool, - federation: bool, - prefer_single_line_descriptions: bool, - include_specified_by: bool, - compose_directive: bool, - use_space_ident: bool, - indent_width: u8, -} - -impl Default for SDLExportOptions { - fn default() -> Self { - Self { - sorted_fields: false, - sorted_arguments: false, - sorted_enum_values: false, - federation: false, - prefer_single_line_descriptions: false, - include_specified_by: false, - compose_directive: false, - use_space_ident: false, - indent_width: 2, - } - } -} - -impl SDLExportOptions { - /// Create a `SDLExportOptions` - #[inline] - pub fn new() -> Self { - Default::default() - } - - /// Export sorted fields - #[inline] - #[must_use] - pub fn sorted_fields(self) -> Self { - Self { - sorted_fields: true, - ..self - } - } - - /// Export sorted field arguments - #[inline] - #[must_use] - pub fn sorted_arguments(self) -> Self { - Self { - sorted_arguments: true, - ..self - } - } - - /// Export sorted enum items - #[inline] - #[must_use] - pub fn sorted_enum_items(self) -> Self { - Self { - sorted_enum_values: true, - ..self - } - } - - /// Export as Federation SDL(Schema Definition Language) - #[inline] - #[must_use] - pub fn federation(self) -> Self { - Self { - federation: true, - ..self - } - } - - /// When possible, write one-line instead of three-line descriptions - #[inline] - #[must_use] - pub fn prefer_single_line_descriptions(self) -> Self { - Self { - prefer_single_line_descriptions: true, - ..self - } - } - - /// Includes `specifiedBy` directive in SDL - pub fn include_specified_by(self) -> Self { - Self { - include_specified_by: true, - ..self - } - } - - /// Enable `composeDirective` if federation is enabled - pub fn compose_directive(self) -> Self { - Self { - compose_directive: true, - ..self - } - } - - /// Use spaces for indentation instead of tabs - pub fn use_space_ident(self) -> Self { - Self { - use_space_ident: true, - ..self - } - } - - /// Set the number of spaces to use for each indentation level (default: 2). - /// Only applies when `use_space_indent` is true - pub fn indent_width(self, width: u8) -> Self { - Self { - indent_width: width, - ..self - } - } -} - -impl Registry { - pub(crate) fn export_sdl(&self, options: SDLExportOptions) -> String { - let mut sdl = String::new(); - - for ty in self.types.values() { - if ty.name().starts_with("__") { - continue; - } - - if options.federation { - const FEDERATION_TYPES: &[&str] = &["_Any", "_Entity", "_Service"]; - if FEDERATION_TYPES.contains(&ty.name()) { - continue; - } - } - - self.export_type(ty, &mut sdl, &options); - } - - self.directives.values().for_each(|directive| { - // Filter out deprecated directive from SDL if it is not used - if directive.name == "deprecated" - && !self.types.values().any(|ty| match ty { - MetaType::Object { fields, .. } => fields - .values() - .any(|field| field.deprecation.is_deprecated()), - MetaType::Enum { enum_values, .. } => enum_values - .values() - .any(|value| value.deprecation.is_deprecated()), - _ => false, - }) - { - return; - } - - // Filter out specifiedBy directive from SDL if it is not used - if directive.name == "specifiedBy" - && !self.types.values().any(|ty| { - matches!( - ty, - MetaType::Scalar { - specified_by_url: Some(_), - .. - } - ) - }) - { - return; - } - - // Filter out oneOf directive from SDL if it is not used - if directive.name == "oneOf" - && !self - .types - .values() - .any(|ty| matches!(ty, MetaType::InputObject { oneof: true, .. })) - { - return; - } - - writeln!(sdl, "{}", directive.sdl(&options)).ok(); - }); - - if options.federation { - writeln!(sdl, "extend schema @link(").ok(); - writeln!( - sdl, - "{}url: \"https://specs.apollo.dev/federation/v2.5\",", - tab(&options) - ) - .ok(); - writeln!(sdl, "{}import: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\", \"@requiresScopes\"]", tab(&options)).ok(); - writeln!(sdl, ")").ok(); - - if options.compose_directive { - writeln!(sdl).ok(); - let mut compose_directives = HashMap::<&str, Vec>::new(); - self.directives - .values() - .filter_map(|d| { - d.composable - .as_ref() - .map(|ext_url| (ext_url, format!("\"@{}\"", d.name))) - }) - .for_each(|(ext_url, name)| { - compose_directives.entry(ext_url).or_default().push(name) - }); - for (url, directives) in compose_directives { - writeln!(sdl, "extend schema @link(").ok(); - writeln!(sdl, "{}url: \"{}\"", tab(&options), url).ok(); - writeln!(sdl, "{}import: [{}]", tab(&options), directives.join(",")).ok(); - writeln!(sdl, ")").ok(); - for name in directives { - writeln!(sdl, "{}@composeDirective(name: {})", tab(&options), name).ok(); - } - writeln!(sdl).ok(); - } - } - } else { - writeln!(sdl, "schema {{").ok(); - writeln!(sdl, "{}query: {}", tab(&options), self.query_type).ok(); - if let Some(mutation_type) = self.mutation_type.as_deref() { - writeln!(sdl, "{}mutation: {}", tab(&options), mutation_type).ok(); - } - if let Some(subscription_type) = self.subscription_type.as_deref() { - writeln!(sdl, "{}subscription: {}", tab(&options), subscription_type).ok(); - } - writeln!(sdl, "}}").ok(); - } - - sdl - } - - fn export_fields<'a, I: Iterator>( - sdl: &mut String, - it: I, - options: &SDLExportOptions, - ) { - let mut fields = it.collect::>(); - - if options.sorted_fields { - fields.sort_by_key(|field| &field.name); - } - - for field in fields { - if field.name.starts_with("__") - || (options.federation && matches!(&*field.name, "_service" | "_entities")) - { - continue; - } - - if let Some(description) = &field.description { - write_description(sdl, options, 1, description); - } - - if !field.args.is_empty() { - write!(sdl, "{}{}(", tab(&options), field.name).ok(); - - let mut args = field.args.values().collect::>(); - if options.sorted_arguments { - args.sort_by_key(|value| &value.name); - } - - let need_multiline = args.iter().any(|x| x.description.is_some()); - - for (i, arg) in args.into_iter().enumerate() { - if i != 0 { - sdl.push(','); - } - - if let Some(description) = &arg.description { - writeln!(sdl).ok(); - write_description(sdl, options, 2, description); - } - - if need_multiline { - write!(sdl, "{0}{0}", tab(options)).ok(); - } else if i != 0 { - sdl.push(' '); - } - - write_input_value(sdl, arg); - - if options.federation { - if arg.inaccessible { - write!(sdl, " @inaccessible").ok(); - } - - for tag in &arg.tags { - write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok(); - } - } - - for directive in &arg.directive_invocations { - write!(sdl, " {}", directive.sdl()).ok(); - } - } - - if need_multiline { - write!(sdl, "\n{}", tab(&options)).ok(); - } - write!(sdl, "): {}", field.ty).ok(); - } else { - write!(sdl, "{}{}: {}", tab(&options), field.name, field.ty).ok(); - } - - write_deprecated(sdl, &field.deprecation); - - for directive in &field.directive_invocations { - write!(sdl, " {}", directive.sdl()).ok(); - } - - if options.federation { - if field.external { - write!(sdl, " @external").ok(); - } - if let Some(requires) = &field.requires { - write!(sdl, " @requires(fields: \"{}\")", requires).ok(); - } - if let Some(provides) = &field.provides { - write!(sdl, " @provides(fields: \"{}\")", provides).ok(); - } - if field.shareable { - write!(sdl, " @shareable").ok(); - } - if field.inaccessible { - write!(sdl, " @inaccessible").ok(); - } - for tag in &field.tags { - write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok(); - } - if let Some(from) = &field.override_from { - write!(sdl, " @override(from: \"{}\")", from).ok(); - } - - if !&field.requires_scopes.is_empty() { - write_requires_scopes(sdl, &field.requires_scopes); - } - } - - writeln!(sdl).ok(); - } - } - - fn export_type(&self, ty: &MetaType, sdl: &mut String, options: &SDLExportOptions) { - match ty { - MetaType::Scalar { - name, - description, - inaccessible, - tags, - specified_by_url, - directive_invocations, - requires_scopes, - .. - } => { - let mut export_scalar = !SYSTEM_SCALARS.contains(&name.as_str()); - if options.federation && FEDERATION_SCALARS.contains(&name.as_str()) { - export_scalar = false; - } - if export_scalar { - if let Some(description) = description { - write_description(sdl, options, 0, description); - } - write!(sdl, "scalar {}", name).ok(); - - if options.include_specified_by { - if let Some(specified_by_url) = specified_by_url { - write!( - sdl, - " @specifiedBy(url: \"{}\")", - specified_by_url.replace('"', "\\\"") - ) - .ok(); - } - } - - if options.federation { - if *inaccessible { - write!(sdl, " @inaccessible").ok(); - } - for tag in tags { - write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok(); - } - if !requires_scopes.is_empty() { - write_requires_scopes(sdl, requires_scopes); - } - } - - for directive in directive_invocations { - write!(sdl, " {}", directive.sdl()).ok(); - } - - writeln!(sdl, "\n").ok(); - } - } - MetaType::Object { - name, - fields, - extends, - keys, - description, - shareable, - resolvable, - inaccessible, - interface_object, - tags, - directive_invocations: raw_directives, - requires_scopes, - .. - } => { - if Some(name.as_str()) == self.subscription_type.as_deref() - && options.federation - && !self.federation_subscription - { - return; - } - - if name.as_str() == self.query_type && options.federation { - let mut field_count = 0; - for field in fields.values() { - if field.name.starts_with("__") - || (options.federation - && matches!(&*field.name, "_service" | "_entities")) - { - continue; - } - field_count += 1; - } - if field_count == 0 { - // is empty query root type - return; - } - } - - if let Some(description) = description { - write_description(sdl, options, 0, description); - } - - if options.federation && *extends { - write!(sdl, "extend ").ok(); - } - - write!(sdl, "type {}", name).ok(); - self.write_implements(sdl, name); - - for directive_invocation in raw_directives { - write!(sdl, " {}", directive_invocation.sdl()).ok(); - } - - if options.federation { - if let Some(keys) = keys { - for key in keys { - write!(sdl, " @key(fields: \"{}\"", key).ok(); - if !resolvable { - write!(sdl, ", resolvable: false").ok(); - } - write!(sdl, ")").ok(); - } - } - if *shareable { - write!(sdl, " @shareable").ok(); - } - - if *inaccessible { - write!(sdl, " @inaccessible").ok(); - } - - if *interface_object { - write!(sdl, " @interfaceObject").ok(); - } - - for tag in tags { - write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok(); - } - - if !requires_scopes.is_empty() { - write_requires_scopes(sdl, requires_scopes); - } - } - - writeln!(sdl, " {{").ok(); - Self::export_fields(sdl, fields.values(), options); - writeln!(sdl, "}}\n").ok(); - } - MetaType::Interface { - name, - fields, - extends, - keys, - description, - inaccessible, - tags, - directive_invocations, - requires_scopes, - .. - } => { - if let Some(description) = description { - write_description(sdl, options, 0, description); - } - - if options.federation && *extends { - write!(sdl, "extend ").ok(); - } - write!(sdl, "interface {}", name).ok(); - - if options.federation { - if let Some(keys) = keys { - for key in keys { - write!(sdl, " @key(fields: \"{}\")", key).ok(); - } - } - if *inaccessible { - write!(sdl, " @inaccessible").ok(); - } - - for tag in tags { - write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok(); - } - - if !requires_scopes.is_empty() { - write_requires_scopes(sdl, requires_scopes); - } - } - - for directive in directive_invocations { - write!(sdl, " {}", directive.sdl()).ok(); - } - - self.write_implements(sdl, name); - - writeln!(sdl, " {{").ok(); - Self::export_fields(sdl, fields.values(), options); - writeln!(sdl, "}}\n").ok(); - } - MetaType::Enum { - name, - enum_values, - description, - inaccessible, - tags, - directive_invocations, - requires_scopes, - .. - } => { - if let Some(description) = description { - write_description(sdl, options, 0, description); - } - - write!(sdl, "enum {}", name).ok(); - if options.federation { - if *inaccessible { - write!(sdl, " @inaccessible").ok(); - } - for tag in tags { - write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok(); - } - - if !requires_scopes.is_empty() { - write_requires_scopes(sdl, requires_scopes); - } - } - - for directive in directive_invocations { - write!(sdl, " {}", directive.sdl()).ok(); - } - - writeln!(sdl, " {{").ok(); - - let mut values = enum_values.values().collect::>(); - if options.sorted_enum_values { - values.sort_by_key(|value| &value.name); - } - - for value in values { - if let Some(description) = &value.description { - write_description(sdl, options, 1, description); - } - write!(sdl, "{}{}", tab(&options), value.name).ok(); - write_deprecated(sdl, &value.deprecation); - - if options.federation { - if value.inaccessible { - write!(sdl, " @inaccessible").ok(); - } - - for tag in &value.tags { - write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok(); - } - } - - for directive in &value.directive_invocations { - write!(sdl, " {}", directive.sdl()).ok(); - } - - writeln!(sdl).ok(); - } - - writeln!(sdl, "}}\n").ok(); - } - MetaType::InputObject { - name, - input_fields, - description, - inaccessible, - tags, - oneof, - directive_invocations: raw_directives, - .. - } => { - if let Some(description) = description { - write_description(sdl, options, 0, description); - } - - write!(sdl, "input {}", name).ok(); - - if *oneof { - write!(sdl, " @oneOf").ok(); - } - if options.federation { - if *inaccessible { - write!(sdl, " @inaccessible").ok(); - } - for tag in tags { - write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok(); - } - } - - for directive in raw_directives { - write!(sdl, " {}", directive.sdl()).ok(); - } - - writeln!(sdl, " {{").ok(); - - let mut fields = input_fields.values().collect::>(); - if options.sorted_fields { - fields.sort_by_key(|value| &value.name); - } - - for field in fields { - if let Some(description) = &field.description { - write_description(sdl, options, 1, description); - } - write!(sdl, "{}", tab(options)).ok(); - write_input_value(sdl, field); - if options.federation { - if field.inaccessible { - write!(sdl, " @inaccessible").ok(); - } - for tag in &field.tags { - write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok(); - } - } - for directive in &field.directive_invocations { - write!(sdl, " {}", directive.sdl()).ok(); - } - writeln!(sdl).ok(); - } - - writeln!(sdl, "}}\n").ok(); - } - MetaType::Union { - name, - possible_types, - description, - inaccessible, - tags, - directive_invocations, - .. - } => { - if let Some(description) = description { - write_description(sdl, options, 0, description); - } - - write!(sdl, "union {}", name).ok(); - if options.federation { - if *inaccessible { - write!(sdl, " @inaccessible").ok(); - } - for tag in tags { - write!(sdl, " @tag(name: \"{}\")", tag.replace('"', "\\\"")).ok(); - } - } - - for directive in directive_invocations { - write!(sdl, " {}", directive.sdl()).ok(); - } - - write!(sdl, " =").ok(); - - for (idx, ty) in possible_types.iter().enumerate() { - if idx == 0 { - write!(sdl, " {}", ty).ok(); - } else { - write!(sdl, " | {}", ty).ok(); - } - } - writeln!(sdl, "\n").ok(); - } - } - } - - fn write_implements(&self, sdl: &mut String, name: &str) { - if let Some(implements) = self.implements.get(name) { - if !implements.is_empty() { - write!( - sdl, - " implements {}", - implements - .iter() - .map(AsRef::as_ref) - .collect::>() - .join(" & ") - ) - .ok(); - } - } - } -} - -pub(super) fn write_description( - sdl: &mut String, - options: &SDLExportOptions, - level: usize, - description: &str, -) { - let tabs = tab(options).repeat(level); - - if options.prefer_single_line_descriptions && !description.contains('\n') { - let description = description.replace('"', r#"\""#); - writeln!(sdl, "{tabs}\"{description}\"").ok(); - } else { - let description = description.replace('\n', &format!("\n{tabs}")); - writeln!(sdl, "{tabs}\"\"\"\n{tabs}{description}\n{tabs}\"\"\"").ok(); - } -} - -fn write_input_value(sdl: &mut String, input_value: &MetaInputValue) { - if let Some(default_value) = &input_value.default_value { - _ = write!( - sdl, - "{}: {} = {}", - input_value.name, input_value.ty, default_value - ); - } else { - _ = write!(sdl, "{}: {}", input_value.name, input_value.ty); - } - - write_deprecated(sdl, &input_value.deprecation); -} - -fn write_deprecated(sdl: &mut String, deprecation: &Deprecation) { - if let Deprecation::Deprecated { reason } = deprecation { - let _ = match reason { - Some(reason) => write!(sdl, " @deprecated(reason: \"{}\")", escape_string(reason)).ok(), - None => write!(sdl, " @deprecated").ok(), - }; - } -} - -fn write_requires_scopes(sdl: &mut String, requires_scopes: &[String]) { - write!( - sdl, - " @requiresScopes(scopes: [{}])", - requires_scopes - .iter() - .map(|x| { - "[".to_string() - + &x.split_whitespace() - .map(|y| "\"".to_string() + y + "\"") - .collect::>() - .join(", ") - + "]" - }) - .collect::>() - .join(", ") - ) - .ok(); -} - -fn escape_string(s: &str) -> String { - let mut res = String::new(); - - for c in s.chars() { - let ec = match c { - '\\' => Some("\\\\"), - '\x08' => Some("\\b"), - '\x0c' => Some("\\f"), - '\n' => Some("\\n"), - '\r' => Some("\\r"), - '\t' => Some("\\t"), - _ => None, - }; - match ec { - Some(ec) => { - res.write_str(ec).ok(); - } - None => { - res.write_char(c).ok(); - } - } - } - - res -} - -fn tab(options: &SDLExportOptions) -> String { - if options.use_space_ident { - " ".repeat(options.indent_width.into()) - } else { - "\t".to_string() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{model::__DirectiveLocation, registry::MetaDirective}; - - #[test] - fn test_escape_string() { - assert_eq!( - escape_string("1\\\x08d\x0c3\n4\r5\t6"), - "1\\\\\\bd\\f3\\n4\\r5\\t6" - ); - } - - #[test] - fn test_compose_directive_dsl() { - let expected = r#"directive @custom_type_directive on FIELD_DEFINITION -extend schema @link( - url: "https://specs.apollo.dev/federation/v2.5", - import: ["@key", "@tag", "@shareable", "@inaccessible", "@override", "@external", "@provides", "@requires", "@composeDirective", "@interfaceObject", "@requiresScopes"] -) - -extend schema @link( - url: "https://custom.spec.dev/extension/v1.0" - import: ["@custom_type_directive"] -) - @composeDirective(name: "@custom_type_directive") - -"#; - let mut registry = Registry::default(); - registry.add_directive(MetaDirective { - name: "custom_type_directive".to_string(), - description: None, - locations: vec![__DirectiveLocation::FIELD_DEFINITION], - args: Default::default(), - is_repeatable: false, - visible: None, - composable: Some("https://custom.spec.dev/extension/v1.0".to_string()), - }); - let dsl = registry.export_sdl(SDLExportOptions::new().federation().compose_directive()); - assert_eq!(dsl, expected) - } - - #[test] - fn test_type_directive_sdl_without_federation() { - let expected = r#"directive @custom_type_directive(optionalWithoutDefault: String, optionalWithDefault: String = "DEFAULT") on FIELD_DEFINITION | OBJECT -schema { - query: Query -} -"#; - let mut registry = Registry::default(); - registry.add_directive(MetaDirective { - name: "custom_type_directive".to_string(), - description: None, - locations: vec![ - __DirectiveLocation::FIELD_DEFINITION, - __DirectiveLocation::OBJECT, - ], - args: [ - ( - "optionalWithoutDefault".to_string(), - MetaInputValue { - name: "optionalWithoutDefault".to_string(), - description: None, - ty: "String".to_string(), - deprecation: Deprecation::NoDeprecated, - default_value: None, - visible: None, - inaccessible: false, - tags: vec![], - is_secret: false, - directive_invocations: vec![], - }, - ), - ( - "optionalWithDefault".to_string(), - MetaInputValue { - name: "optionalWithDefault".to_string(), - description: None, - ty: "String".to_string(), - deprecation: Deprecation::NoDeprecated, - default_value: Some("\"DEFAULT\"".to_string()), - visible: None, - inaccessible: false, - tags: vec![], - is_secret: false, - directive_invocations: vec![], - }, - ), - ] - .into(), - is_repeatable: false, - visible: None, - composable: None, - }); - registry.query_type = "Query".to_string(); - let sdl = registry.export_sdl(SDLExportOptions::new()); - assert_eq!(sdl, expected) - } -} diff --git a/src/registry/mod.rs b/src/registry/mod.rs deleted file mode 100644 index 240f391b2..000000000 --- a/src/registry/mod.rs +++ /dev/null @@ -1,1699 +0,0 @@ -mod cache_control; -mod export_sdl; -mod stringify_exec_doc; - -use std::{ - collections::{BTreeMap, BTreeSet, HashMap, HashSet}, - fmt::{self, Display, Formatter, Write}, - sync::Arc, -}; - -pub use cache_control::CacheControl; -pub use export_sdl::SDLExportOptions; -use indexmap::{map::IndexMap, set::IndexSet}; - -pub use crate::model::{__DirectiveLocation, location_traits}; -use crate::{ - Any, Context, ID, InputType, OutputType, Positioned, ServerResult, SubscriptionType, Value, - VisitorContext, - model::__Schema, - parser::types::{BaseType as ParsedBaseType, Field, Type as ParsedType, VariableDefinition}, - schema::IntrospectionMode, -}; - -fn strip_brackets(type_name: &str) -> Option<&str> { - type_name - .strip_prefix('[') - .map(|rest| &rest[..rest.len() - 1]) -} - -#[derive(Clone, Copy, Eq, PartialEq, Debug)] -pub enum MetaTypeName<'a> { - List(&'a str), - NonNull(&'a str), - Named(&'a str), -} - -impl Display for MetaTypeName<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - MetaTypeName::Named(name) => write!(f, "{}", name), - MetaTypeName::NonNull(name) => write!(f, "{}!", name), - MetaTypeName::List(name) => write!(f, "[{}]", name), - } - } -} - -impl MetaTypeName<'_> { - #[inline] - pub fn create(type_name: &str) -> MetaTypeName { - if let Some(type_name) = type_name.strip_suffix('!') { - MetaTypeName::NonNull(type_name) - } else if let Some(type_name) = strip_brackets(type_name) { - MetaTypeName::List(type_name) - } else { - MetaTypeName::Named(type_name) - } - } - - #[inline] - pub fn concrete_typename(type_name: &str) -> &str { - match MetaTypeName::create(type_name) { - MetaTypeName::List(type_name) => Self::concrete_typename(type_name), - MetaTypeName::NonNull(type_name) => Self::concrete_typename(type_name), - MetaTypeName::Named(type_name) => type_name, - } - } - - #[inline] - pub fn is_non_null(&self) -> bool { - matches!(self, MetaTypeName::NonNull(_)) - } - - #[inline] - #[must_use] - pub fn unwrap_non_null(&self) -> Self { - match self { - MetaTypeName::NonNull(ty) => MetaTypeName::create(ty), - _ => *self, - } - } - - #[inline] - pub fn is_subtype(&self, sub: &MetaTypeName<'_>) -> bool { - match (self, sub) { - (MetaTypeName::NonNull(super_type), MetaTypeName::NonNull(sub_type)) - | (MetaTypeName::Named(super_type), MetaTypeName::NonNull(sub_type)) => { - MetaTypeName::create(super_type).is_subtype(&MetaTypeName::create(sub_type)) - } - (MetaTypeName::Named(super_type), MetaTypeName::Named(sub_type)) => { - super_type == sub_type - } - (MetaTypeName::List(super_type), MetaTypeName::List(sub_type)) => { - MetaTypeName::create(super_type).is_subtype(&MetaTypeName::create(sub_type)) - } - _ => false, - } - } - - #[inline] - pub fn is_list(&self) -> bool { - match self { - MetaTypeName::List(_) => true, - MetaTypeName::NonNull(ty) => MetaTypeName::create(ty).is_list(), - MetaTypeName::Named(name) => name.ends_with(']'), - } - } -} - -/// actual directive invocation on SDL definitions -#[derive(Debug, Clone)] -pub struct MetaDirectiveInvocation { - /// name of directive to invoke - pub name: String, - /// actual arguments passed to directive - pub args: IndexMap, -} - -impl MetaDirectiveInvocation { - pub fn sdl(&self) -> String { - let formatted_args = if self.args.is_empty() { - String::new() - } else { - format!( - "({})", - self.args - .iter() - .map(|(name, value)| format!("{}: {}", name, value)) - .collect::>() - .join(", ") - ) - }; - format!("@{}{}", self.name, formatted_args) - } -} - -/// Input value metadata -#[derive(Clone)] -pub struct MetaInputValue { - /// The name of the input value - pub name: String, - /// The description of the input value - pub description: Option, - /// The type of the input value - pub ty: String, - /// Field deprecation - pub deprecation: Deprecation, - /// The default value of the input value - pub default_value: Option, - /// A function that uses to check if the input value should be exported to - /// schemas - pub visible: Option, - /// Indicate that an input object is not accessible from a supergraph when - /// using Apollo Federation - pub inaccessible: bool, - /// Arbitrary string metadata that will be propagated to the supergraph when - /// using Apollo Federation. This attribute is repeatable - pub tags: Vec, - /// Indicate that an input object is secret - pub is_secret: bool, - /// Custom directive invocations - pub directive_invocations: Vec, -} - -type ComputeComplexityFn = fn( - &VisitorContext<'_>, - &[Positioned], - &Field, - usize, -) -> ServerResult; - -#[derive(Debug, Clone, Default)] -pub enum Deprecation { - #[default] - NoDeprecated, - Deprecated { - reason: Option, - }, -} - -impl Deprecation { - #[inline] - pub fn is_deprecated(&self) -> bool { - matches!(self, Deprecation::Deprecated { .. }) - } - - #[inline] - pub fn reason(&self) -> Option<&str> { - match self { - Deprecation::NoDeprecated => None, - Deprecation::Deprecated { reason } => reason.as_deref(), - } - } -} - -/// Field metadata -#[derive(Clone)] -pub struct MetaField { - /// The name of the field - pub name: String, - /// The description of the field - pub description: Option, - /// The arguments of the field - pub args: IndexMap, - /// The type of the field - pub ty: String, - /// Field deprecation - pub deprecation: Deprecation, - /// Used to create HTTP `Cache-Control` header - pub cache_control: CacheControl, - /// Mark a field as owned by another service. This allows service A to use - /// fields from service B while also knowing at runtime the types of that - /// field. - pub external: bool, - /// Annotate the required input fieldset from a base type for a resolver. It - /// is used to develop a query plan where the required fields may not be - /// needed by the client, but the service may need additional information - /// from other services. - pub requires: Option, - /// Annotate the expected returned fieldset from a field on a base type that - /// is guaranteed to be selectable by the gateway. - pub provides: Option, - /// A function that uses to check if the field should be exported to - /// schemas - pub visible: Option, - /// Indicate that an object type's field is allowed to be resolved by - /// multiple subgraphs - pub shareable: bool, - /// Indicate that an object is not accessible from a supergraph when using - /// Apollo Federation - pub inaccessible: bool, - /// Arbitrary string metadata that will be propagated to the supergraph when - /// using Apollo Federation. This attribute is repeatable - pub tags: Vec, - /// Mark the field as overriding a field currently present on another - /// subgraph. It is used to migrate fields between subgraphs. - pub override_from: Option, - /// A constant or function to get the complexity - pub compute_complexity: Option, - /// Custom directive invocations - pub directive_invocations: Vec, - /// Indicates to composition that the target element is accessible only to - /// the authenticated supergraph users with the appropriate JWT scopes - /// when using Apollo Federation. - pub requires_scopes: Vec, -} - -#[derive(Clone)] -pub struct MetaEnumValue { - pub name: String, - pub description: Option, - pub deprecation: Deprecation, - pub visible: Option, - pub inaccessible: bool, - pub tags: Vec, - pub directive_invocations: Vec, -} - -type MetaVisibleFn = fn(&Context<'_>) -> bool; - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum MetaTypeId { - Scalar, - Object, - Interface, - Union, - Enum, - InputObject, -} - -impl MetaTypeId { - fn create_fake_type(&self, rust_typename: &'static str) -> MetaType { - match self { - MetaTypeId::Scalar => MetaType::Scalar { - name: "".to_string(), - description: None, - is_valid: None, - visible: None, - inaccessible: false, - tags: vec![], - specified_by_url: None, - directive_invocations: vec![], - requires_scopes: vec![], - }, - MetaTypeId::Object => MetaType::Object { - name: "".to_string(), - description: None, - fields: Default::default(), - cache_control: Default::default(), - extends: false, - shareable: false, - resolvable: true, - inaccessible: false, - interface_object: false, - tags: vec![], - keys: None, - visible: None, - is_subscription: false, - rust_typename: Some(rust_typename), - directive_invocations: vec![], - requires_scopes: vec![], - }, - MetaTypeId::Interface => MetaType::Interface { - name: "".to_string(), - description: None, - fields: Default::default(), - possible_types: Default::default(), - extends: false, - inaccessible: false, - tags: vec![], - keys: None, - visible: None, - rust_typename: Some(rust_typename), - directive_invocations: vec![], - requires_scopes: vec![], - }, - MetaTypeId::Union => MetaType::Union { - name: "".to_string(), - description: None, - possible_types: Default::default(), - visible: None, - inaccessible: false, - tags: vec![], - rust_typename: Some(rust_typename), - directive_invocations: vec![], - }, - MetaTypeId::Enum => MetaType::Enum { - name: "".to_string(), - description: None, - enum_values: Default::default(), - visible: None, - inaccessible: false, - tags: vec![], - rust_typename: Some(rust_typename), - directive_invocations: vec![], - requires_scopes: vec![], - }, - MetaTypeId::InputObject => MetaType::InputObject { - name: "".to_string(), - description: None, - input_fields: Default::default(), - visible: None, - inaccessible: false, - tags: vec![], - rust_typename: Some(rust_typename), - oneof: false, - directive_invocations: vec![], - }, - } - } -} - -impl Display for MetaTypeId { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(match self { - MetaTypeId::Scalar => "Scalar", - MetaTypeId::Object => "Object", - MetaTypeId::Interface => "Interface", - MetaTypeId::Union => "Union", - MetaTypeId::Enum => "Enum", - MetaTypeId::InputObject => "InputObject", - }) - } -} - -/// A validator for scalar -pub type ScalarValidatorFn = Arc bool + Send + Sync>; - -/// Type metadata -#[derive(Clone)] -pub enum MetaType { - /// Scalar - /// - /// Reference: - Scalar { - /// The name of the scalar - name: String, - /// the description of the scalar - description: Option, - /// A function that uses to check if the scalar is valid - is_valid: Option, - /// A function that uses to check if the scalar should be exported to - /// schemas - visible: Option, - /// Indicate that a scalar is not accessible from a supergraph when - /// using Apollo Federation - /// - /// Reference: - inaccessible: bool, - /// Arbitrary string metadata that will be propagated to the supergraph - /// when using Apollo Federation. This attribute is repeatable - /// - /// Reference: - tags: Vec, - /// Provide a specification URL for this scalar type, it must link to a - /// human-readable specification of the data format, serialization and - /// coercion rules for this scalar. - specified_by_url: Option, - /// custom directive invocations - directive_invocations: Vec, - /// Indicates to composition that the target element is accessible only - /// to the authenticated supergraph users with the appropriate - /// JWT scopes when using Apollo Federation. - requires_scopes: Vec, - }, - /// Object - /// - /// Reference: - Object { - /// The name of the object - name: String, - /// The description of the object - description: Option, - /// The fields of the object type - fields: IndexMap, - /// Used to create HTTP `Cache-Control` header - cache_control: CacheControl, - /// Indicates that an object definition is an extension of another - /// definition of that same type. - /// - /// Reference: - extends: bool, - /// Indicates that an object type's field is allowed to be resolved by - /// multiple subgraphs (by default in Federation 2, object fields can be - /// resolved by only one subgraph). - /// - /// Reference: - shareable: bool, - /// Indicates that the subgraph does not define a reference resolver - /// for this object. Objects are assumed to be resolvable by default. - /// - /// Most commonly used to reference an entity defined in another - /// subgraph without contributing fields. Part of the `@key` directive. - /// - /// Reference: - resolvable: bool, - /// The keys of the object type - /// - /// Designates an object type as an [entity](https://www.apollographql.com/docs/federation/entities) and specifies - /// its key fields (a set of fields that the subgraph can use to - /// uniquely identify any instance of the entity). - /// - /// Reference: - keys: Option>, - /// A function that uses to check if the object should be exported to - /// schemas - visible: Option, - /// Indicate that an object is not accessible from a supergraph when - /// using Apollo Federation - /// - /// Reference: - inaccessible: bool, - /// During composition, the fields of every `@interfaceObject` are added - /// both to their corresponding interface definition and to all - /// entity types that implement that interface. - /// - /// Reference: - interface_object: bool, - /// Arbitrary string metadata that will be propagated to the supergraph - /// when using Apollo Federation. This attribute is repeatable - /// - /// Reference: - tags: Vec, - /// Indicates whether it is a subscription object - is_subscription: bool, - /// The Rust typename corresponding to the object - rust_typename: Option<&'static str>, - /// custom directive invocations - directive_invocations: Vec, - /// Indicates to composition that the target element is accessible only - /// to the authenticated supergraph users with the appropriate - /// JWT scopes when using Apollo Federation. - requires_scopes: Vec, - }, - /// Interface - /// - /// Reference: - Interface { - /// The name of the interface - name: String, - /// The description of the interface - description: Option, - /// The fields of the interface - fields: IndexMap, - /// The object types that implement this interface - /// Add fields to an entity that's defined in another service - possible_types: IndexSet, - /// Indicates that an interface definition is an extension of another - /// definition of that same type. - /// - /// Reference: - extends: bool, - /// The keys of the object type - /// - /// Designates an object type as an [entity](https://www.apollographql.com/docs/federation/entities) and specifies - /// its key fields (a set of fields that the subgraph can use to - /// uniquely identify any instance of the entity). - /// - /// Reference: - keys: Option>, - /// A function that uses to check if the interface should be exported to - /// schemas - visible: Option, - /// Indicate that an interface is not accessible from a supergraph when - /// using Apollo Federation - /// - /// Reference: - inaccessible: bool, - /// Arbitrary string metadata that will be propagated to the supergraph - /// when using Apollo Federation. This attribute is repeatable - /// - /// Reference: - tags: Vec, - /// The Rust typename corresponding to the interface - rust_typename: Option<&'static str>, - /// custom directive invocations - directive_invocations: Vec, - /// Indicates to composition that the target element is accessible only - /// to the authenticated supergraph users with the appropriate - /// JWT scopes when using Apollo Federation. - requires_scopes: Vec, - }, - /// Union - /// - /// Reference: - Union { - /// The name of the interface - name: String, - /// The description of the union - description: Option, - /// The object types that could be the union - possible_types: IndexSet, - /// A function that uses to check if the union should be exported to - /// schemas - visible: Option, - /// Indicate that an union is not accessible from a supergraph when - /// using Apollo Federation - /// - /// Reference: - inaccessible: bool, - /// Arbitrary string metadata that will be propagated to the supergraph - /// when using Apollo Federation. This attribute is repeatable - /// - /// Reference: - tags: Vec, - /// The Rust typename corresponding to the union - rust_typename: Option<&'static str>, - /// custom directive invocations - directive_invocations: Vec, - }, - /// Enum - /// - /// Reference: - Enum { - /// The name of the enum - name: String, - /// The description of the enum - description: Option, - /// The values of the enum - enum_values: IndexMap, - /// A function that uses to check if the enum should be exported to - /// schemas - visible: Option, - /// Indicate that an enum is not accessible from a supergraph when - /// using Apollo Federation - /// - /// Reference: - inaccessible: bool, - /// Arbitrary string metadata that will be propagated to the supergraph - /// when using Apollo Federation. This attribute is repeatable - /// - /// Reference: - tags: Vec, - /// The Rust typename corresponding to the enum - rust_typename: Option<&'static str>, - /// custom directive invocations - directive_invocations: Vec, - /// Indicates to composition that the target element is accessible only - /// to the authenticated supergraph users with the appropriate - /// JWT scopes when using Apollo Federation. - requires_scopes: Vec, - }, - /// Input object - /// - /// Reference: - InputObject { - /// The name of the input object - name: String, - /// The description of the input object - description: Option, - /// The fields of the input object - input_fields: IndexMap, - /// A function that uses to check if the input object should be exported - /// to schemas - visible: Option, - /// Indicate that a input object is not accessible from a supergraph - /// when using Apollo Federation - /// - /// Reference: - inaccessible: bool, - /// Arbitrary string metadata that will be propagated to the supergraph - /// when using Apollo Federation. This attribute is repeatable - /// - /// Reference: - tags: Vec, - /// The Rust typename corresponding to the enum - rust_typename: Option<&'static str>, - /// Is the oneof input objects - /// - /// Reference: - oneof: bool, - /// custom directive invocations - directive_invocations: Vec, - }, -} - -impl MetaType { - #[inline] - pub fn type_id(&self) -> MetaTypeId { - match self { - MetaType::Scalar { .. } => MetaTypeId::Scalar, - MetaType::Object { .. } => MetaTypeId::Object, - MetaType::Interface { .. } => MetaTypeId::Interface, - MetaType::Union { .. } => MetaTypeId::Union, - MetaType::Enum { .. } => MetaTypeId::Enum, - MetaType::InputObject { .. } => MetaTypeId::InputObject, - } - } - - #[inline] - pub fn field_by_name(&self, name: &str) -> Option<&MetaField> { - self.fields().and_then(|fields| fields.get(name)) - } - - #[inline] - pub fn fields(&self) -> Option<&IndexMap> { - match self { - MetaType::Object { fields, .. } => Some(&fields), - MetaType::Interface { fields, .. } => Some(&fields), - _ => None, - } - } - - #[inline] - pub fn is_visible(&self, ctx: &Context<'_>) -> bool { - let visible = match self { - MetaType::Scalar { visible, .. } => visible, - MetaType::Object { visible, .. } => visible, - MetaType::Interface { visible, .. } => visible, - MetaType::Union { visible, .. } => visible, - MetaType::Enum { visible, .. } => visible, - MetaType::InputObject { visible, .. } => visible, - }; - is_visible(ctx, visible) - } - - #[inline] - pub fn name(&self) -> &str { - match self { - MetaType::Scalar { name, .. } => &name, - MetaType::Object { name, .. } => name, - MetaType::Interface { name, .. } => name, - MetaType::Union { name, .. } => name, - MetaType::Enum { name, .. } => name, - MetaType::InputObject { name, .. } => name, - } - } - - #[inline] - pub fn is_composite(&self) -> bool { - matches!( - self, - MetaType::Object { .. } | MetaType::Interface { .. } | MetaType::Union { .. } - ) - } - - #[inline] - pub fn is_abstract(&self) -> bool { - matches!(self, MetaType::Interface { .. } | MetaType::Union { .. }) - } - - #[inline] - pub fn is_leaf(&self) -> bool { - matches!(self, MetaType::Enum { .. } | MetaType::Scalar { .. }) - } - - #[inline] - pub fn is_input(&self) -> bool { - matches!( - self, - MetaType::Enum { .. } | MetaType::Scalar { .. } | MetaType::InputObject { .. } - ) - } - - #[inline] - pub fn is_possible_type(&self, type_name: &str) -> bool { - match self { - MetaType::Interface { possible_types, .. } => possible_types.contains(type_name), - MetaType::Union { possible_types, .. } => possible_types.contains(type_name), - MetaType::Object { name, .. } => name == type_name, - _ => false, - } - } - - #[inline] - pub fn possible_types(&self) -> Option<&IndexSet> { - match self { - MetaType::Interface { possible_types, .. } => Some(possible_types), - MetaType::Union { possible_types, .. } => Some(possible_types), - _ => None, - } - } - - pub fn type_overlap(&self, ty: &MetaType) -> bool { - if std::ptr::eq(self, ty) { - return true; - } - - match (self.is_abstract(), ty.is_abstract()) { - (true, true) => self - .possible_types() - .iter() - .copied() - .flatten() - .any(|type_name| ty.is_possible_type(type_name)), - (true, false) => self.is_possible_type(ty.name()), - (false, true) => ty.is_possible_type(self.name()), - (false, false) => false, - } - } - - pub fn rust_typename(&self) -> Option<&'static str> { - match self { - MetaType::Scalar { .. } => None, - MetaType::Object { rust_typename, .. } => *rust_typename, - MetaType::Interface { rust_typename, .. } => *rust_typename, - MetaType::Union { rust_typename, .. } => *rust_typename, - MetaType::Enum { rust_typename, .. } => *rust_typename, - MetaType::InputObject { rust_typename, .. } => *rust_typename, - } - } -} - -pub struct MetaDirective { - pub name: String, - pub description: Option, - pub locations: Vec<__DirectiveLocation>, - pub args: IndexMap, - pub is_repeatable: bool, - pub visible: Option, - pub composable: Option, -} - -impl MetaDirective { - pub(crate) fn sdl(&self, options: &SDLExportOptions) -> String { - let mut sdl = String::new(); - - if let Some(description) = &self.description { - self::export_sdl::write_description(&mut sdl, options, 0, description); - } - - write!(sdl, "directive @{}", self.name).ok(); - - if !self.args.is_empty() { - let args = self - .args - .values() - .map(|value| self.argument_sdl(value)) - .collect::>() - .join(", "); - write!(sdl, "({})", args).ok(); - } - let locations = self - .locations - .iter() - .map(|location| location.to_value().to_string()) - .collect::>() - .join(" | "); - write!(sdl, " on {}", locations).ok(); - sdl - } - - pub(crate) fn argument_sdl(&self, argument: &MetaInputValue) -> String { - let argument_default = match &argument.default_value { - Some(default) => format!(" = {default}"), - None => "".to_string(), - }; - - format!("{}: {}{}", argument.name, argument.ty, argument_default) - } -} - -/// A type registry for build schemas -#[derive(Default)] -pub struct Registry { - pub types: BTreeMap, - pub directives: BTreeMap, - pub implements: HashMap>, - pub query_type: String, - pub mutation_type: Option, - pub subscription_type: Option, - pub introspection_mode: IntrospectionMode, - pub enable_federation: bool, - pub federation_subscription: bool, - pub ignore_name_conflicts: HashSet, - pub enable_suggestions: bool, -} - -impl Registry { - pub(crate) fn add_system_types(&mut self) { - self.add_directive(MetaDirective { - name: "skip".into(), - description: Some("Directs the executor to skip this field or fragment when the `if` argument is true.".to_string()), - locations: vec![ - __DirectiveLocation::FIELD, - __DirectiveLocation::FRAGMENT_SPREAD, - __DirectiveLocation::INLINE_FRAGMENT - ], - args: { - let mut args = IndexMap::new(); - args.insert("if".to_string(), MetaInputValue { - name: "if".to_string(), - description: Some("Skipped when true.".to_string()), - ty: "Boolean!".to_string(), - deprecation: Deprecation::NoDeprecated, - default_value: None, - visible: None, - inaccessible: false, - tags: Default::default(), - is_secret: false, - directive_invocations: vec![] - }); - args - }, - is_repeatable: false, - visible: None, - composable: None, - }); - - self.add_directive(MetaDirective { - name: "include".into(), - description: Some("Directs the executor to include this field or fragment only when the `if` argument is true.".to_string()), - locations: vec![ - __DirectiveLocation::FIELD, - __DirectiveLocation::FRAGMENT_SPREAD, - __DirectiveLocation::INLINE_FRAGMENT - ], - args: { - let mut args = IndexMap::new(); - args.insert("if".to_string(), MetaInputValue { - name: "if".to_string(), - description: Some("Included when true.".to_string()), - ty: "Boolean!".to_string(), - deprecation: Deprecation::NoDeprecated, - default_value: None, - visible: None, - inaccessible: false, - tags: Default::default(), - is_secret: false, - directive_invocations: vec![] - }); - args - }, - is_repeatable: false, - visible: None, - composable: None, - }); - - self.add_directive(MetaDirective { - name: "deprecated".into(), - description: Some( - "Marks an element of a GraphQL schema as no longer supported.".into(), - ), - locations: vec![ - __DirectiveLocation::FIELD_DEFINITION, - __DirectiveLocation::ARGUMENT_DEFINITION, - __DirectiveLocation::INPUT_FIELD_DEFINITION, - __DirectiveLocation::ENUM_VALUE, - ], - args: { - let mut args = IndexMap::new(); - args.insert( - "reason".into(), - MetaInputValue { - name: "reason".into(), - description: Some( - "A reason for why it is deprecated, formatted using Markdown syntax" - .into(), - ), - ty: "String".into(), - deprecation: Deprecation::NoDeprecated, - default_value: Some(r#""No longer supported""#.into()), - visible: None, - inaccessible: false, - tags: Default::default(), - is_secret: false, - directive_invocations: vec![], - }, - ); - args - }, - is_repeatable: false, - visible: None, - composable: None, - }); - - self.add_directive(MetaDirective { - name: "specifiedBy".into(), - description: Some("Provides a scalar specification URL for specifying the behavior of custom scalar types.".into()), - locations: vec![__DirectiveLocation::SCALAR], - args: { - let mut args = IndexMap::new(); - args.insert( - "url".into(), - MetaInputValue { - name: "url".into(), - description: Some("URL that specifies the behavior of this scalar.".into()), - ty: "String!".into(), - deprecation: Deprecation::NoDeprecated, - default_value: None, - visible: None, - inaccessible: false, - tags: Default::default(), - is_secret: false, - directive_invocations: vec![], - }, - ); - args - }, - is_repeatable: false, - visible: None, - composable: None, - }); - - self.add_directive(MetaDirective { - name: "oneOf".into(), - description: Some( - "Indicates that an Input Object is a OneOf Input Object (and thus requires \ - exactly one of its field be provided)" - .to_string(), - ), - locations: vec![__DirectiveLocation::INPUT_OBJECT], - args: Default::default(), - is_repeatable: false, - visible: None, - composable: None, - }); - - // create system scalars - ::create_type_info(self); - ::create_type_info(self); - ::create_type_info(self); - ::create_type_info(self); - ::create_type_info(self); - } - - pub fn create_input_type(&mut self, type_id: MetaTypeId, mut f: F) -> String - where - T: InputType, - F: FnMut(&mut Registry) -> MetaType, - { - self.create_type(&mut f, &T::type_name(), std::any::type_name::(), type_id); - T::qualified_type_name() - } - - pub fn create_output_type(&mut self, type_id: MetaTypeId, mut f: F) -> String - where - T: OutputType + ?Sized, - F: FnMut(&mut Registry) -> MetaType, - { - self.create_type(&mut f, &T::type_name(), std::any::type_name::(), type_id); - T::qualified_type_name() - } - - pub fn create_subscription_type(&mut self, mut f: F) -> String - where - T: SubscriptionType + ?Sized, - F: FnMut(&mut Registry) -> MetaType, - { - self.create_type( - &mut f, - &T::type_name(), - std::any::type_name::(), - MetaTypeId::Object, - ); - T::qualified_type_name() - } - - fn create_type MetaType>( - &mut self, - f: &mut F, - name: &str, - rust_typename: &'static str, - type_id: MetaTypeId, - ) { - match self.types.get(name) { - Some(ty) => { - if let Some(prev_typename) = ty.rust_typename() { - if prev_typename == "__fake_type__" { - return; - } - - if rust_typename != prev_typename && !self.ignore_name_conflicts.contains(name) - { - panic!( - "`{}` and `{}` have the same GraphQL name `{}`", - prev_typename, rust_typename, name, - ); - } - - if ty.type_id() != type_id { - panic!( - "Register `{}` as `{}`, but it is already registered as `{}`", - name, - type_id, - ty.type_id() - ); - } - } - } - None => { - // Inserting a fake type before calling the function allows recursive types to - // exist. - self.types - .insert(name.to_string(), type_id.create_fake_type(rust_typename)); - let ty = f(self); - *self.types.get_mut(name).unwrap() = ty; - } - } - } - - pub fn create_fake_output_type(&mut self) -> MetaType { - T::create_type_info(self); - self.types - .get(&*T::type_name()) - .cloned() - .expect("You definitely encountered a bug!") - } - - pub fn create_fake_input_type(&mut self) -> MetaType { - T::create_type_info(self); - self.types - .get(&*T::type_name()) - .cloned() - .expect("You definitely encountered a bug!") - } - - pub fn create_fake_subscription_type(&mut self) -> MetaType { - T::create_type_info(self); - self.types - .get(&*T::type_name()) - .cloned() - .expect("You definitely encountered a bug!") - } - - pub fn add_directive(&mut self, directive: MetaDirective) { - self.directives - .insert(directive.name.to_string(), directive); - } - - pub fn add_implements(&mut self, ty: &str, interface: &str) { - self.implements - .entry(ty.to_string()) - .and_modify(|interfaces| { - interfaces.insert(interface.to_string()); - }) - .or_insert({ - let mut interfaces = IndexSet::new(); - interfaces.insert(interface.to_string()); - interfaces - }); - } - - pub fn add_keys(&mut self, ty: &str, keys: impl Into) { - let all_keys = match self.types.get_mut(ty) { - Some(MetaType::Object { keys: all_keys, .. }) => all_keys, - Some(MetaType::Interface { keys: all_keys, .. }) => all_keys, - _ => return, - }; - if let Some(all_keys) = all_keys { - all_keys.push(keys.into()); - } else { - *all_keys = Some(vec![keys.into()]); - } - } - - pub fn concrete_type_by_name(&self, type_name: &str) -> Option<&MetaType> { - self.types.get(MetaTypeName::concrete_typename(type_name)) - } - - pub fn concrete_type_by_parsed_type(&self, query_type: &ParsedType) -> Option<&MetaType> { - match &query_type.base { - ParsedBaseType::Named(name) => self.types.get(name.as_str()), - ParsedBaseType::List(ty) => self.concrete_type_by_parsed_type(ty), - } - } - - pub(crate) fn has_entities(&self) -> bool { - self.types.values().any(|ty| match ty { - MetaType::Object { - keys: Some(keys), - resolvable: true, - .. - } - | MetaType::Interface { - keys: Some(keys), .. - } => !keys.is_empty(), - _ => false, - }) - } - - /// Each type annotated with @key should be added to the _Entity union. - /// If no types are annotated with the key directive, then the _Entity union - /// and Query._entities field should be removed from the schema. - /// - /// [Reference](https://www.apollographql.com/docs/federation/federation-spec/#resolve-requests-for-entities). - fn create_entity_type_and_root_field(&mut self) { - let possible_types: IndexSet = self - .types - .values() - .filter_map(|ty| match ty { - MetaType::Object { - name, - keys: Some(keys), - resolvable: true, - .. - } if !keys.is_empty() => Some(name.clone()), - MetaType::Interface { - name, - keys: Some(keys), - .. - } if !keys.is_empty() => Some(name.clone()), - _ => None, - }) - .collect(); - - if let MetaType::Object { fields, .. } = self - .types - .get_mut(&self.query_type) - .expect("missing query type") - { - fields.insert( - "_service".to_string(), - MetaField { - name: "_service".to_string(), - description: None, - args: Default::default(), - ty: "_Service!".to_string(), - deprecation: Default::default(), - cache_control: Default::default(), - external: false, - requires: None, - provides: None, - shareable: false, - inaccessible: false, - tags: Default::default(), - override_from: None, - visible: None, - compute_complexity: None, - directive_invocations: vec![], - requires_scopes: vec![], - }, - ); - } - - if !possible_types.is_empty() { - self.types.insert( - "_Entity".to_string(), - MetaType::Union { - name: "_Entity".to_string(), - description: None, - possible_types, - visible: None, - inaccessible: false, - tags: Default::default(), - rust_typename: Some("async_graphql::federation::Entity"), - directive_invocations: vec![], - }, - ); - - if let MetaType::Object { fields, .. } = self.types.get_mut(&self.query_type).unwrap() { - fields.insert( - "_entities".to_string(), - MetaField { - name: "_entities".to_string(), - description: None, - args: { - let mut args = IndexMap::new(); - args.insert( - "representations".to_string(), - MetaInputValue { - name: "representations".to_string(), - description: None, - ty: "[_Any!]!".to_string(), - deprecation: Deprecation::NoDeprecated, - default_value: None, - visible: None, - inaccessible: false, - tags: Default::default(), - is_secret: false, - directive_invocations: vec![], - }, - ); - args - }, - ty: "[_Entity]!".to_string(), - deprecation: Default::default(), - cache_control: Default::default(), - external: false, - requires: None, - provides: None, - shareable: false, - visible: None, - inaccessible: false, - tags: Default::default(), - override_from: None, - compute_complexity: None, - directive_invocations: vec![], - requires_scopes: vec![], - }, - ); - } - } - } - - pub(crate) fn create_introspection_types(&mut self) { - __Schema::create_type_info(self); - - if let Some(MetaType::Object { fields, .. }) = self.types.get_mut(&self.query_type) { - fields.insert( - "__schema".to_string(), - MetaField { - name: "__schema".to_string(), - description: Some("Access the current type schema of this server.".to_string()), - args: Default::default(), - ty: "__Schema".to_string(), - deprecation: Default::default(), - cache_control: Default::default(), - external: false, - requires: None, - provides: None, - shareable: false, - inaccessible: false, - tags: Default::default(), - visible: None, - compute_complexity: None, - override_from: None, - directive_invocations: vec![], - requires_scopes: vec![], - }, - ); - - fields.insert( - "__type".to_string(), - MetaField { - name: "__type".to_string(), - description: Some("Request the type information of a single type.".to_string()), - args: { - let mut args = IndexMap::new(); - args.insert( - "name".to_string(), - MetaInputValue { - name: "name".to_string(), - description: None, - ty: "String!".to_string(), - deprecation: Deprecation::NoDeprecated, - default_value: None, - visible: None, - inaccessible: false, - tags: Default::default(), - is_secret: false, - directive_invocations: vec![], - }, - ); - args - }, - ty: "__Type".to_string(), - deprecation: Default::default(), - cache_control: Default::default(), - external: false, - requires: None, - provides: None, - shareable: false, - inaccessible: false, - tags: Default::default(), - override_from: None, - visible: None, - compute_complexity: None, - directive_invocations: vec![], - requires_scopes: vec![], - }, - ); - } - } - - pub(crate) fn create_federation_types(&mut self) { - ::create_type_info(self); - - self.types.insert( - "_Service".to_string(), - MetaType::Object { - name: "_Service".to_string(), - description: None, - fields: { - let mut fields = IndexMap::new(); - fields.insert( - "sdl".to_string(), - MetaField { - name: "sdl".to_string(), - description: None, - args: Default::default(), - ty: "String".to_string(), - deprecation: Default::default(), - cache_control: Default::default(), - external: false, - requires: None, - provides: None, - shareable: false, - visible: None, - inaccessible: false, - tags: Default::default(), - override_from: None, - compute_complexity: None, - directive_invocations: vec![], - requires_scopes: vec![], - }, - ); - fields - }, - cache_control: Default::default(), - extends: false, - shareable: false, - resolvable: true, - interface_object: false, - keys: None, - visible: None, - inaccessible: false, - tags: Default::default(), - is_subscription: false, - rust_typename: Some("async_graphql::federation::Service"), - directive_invocations: vec![], - requires_scopes: vec![], - }, - ); - - self.create_entity_type_and_root_field(); - } - - pub fn names(&self) -> Vec { - let mut names = HashSet::new(); - - for d in self.directives.values() { - names.insert(d.name.to_string()); - names.extend(d.args.values().map(|arg| arg.name.to_string())); - } - - for ty in self.types.values() { - match ty { - MetaType::Scalar { name, .. } | MetaType::Union { name, .. } => { - names.insert(name.clone()); - } - MetaType::Object { name, fields, .. } - | MetaType::Interface { name, fields, .. } => { - names.insert(name.clone()); - names.extend( - fields - .values() - .map(|field| { - std::iter::once(field.name.clone()) - .chain(field.args.values().map(|arg| arg.name.to_string())) - }) - .flatten(), - ); - } - MetaType::Enum { - name, enum_values, .. - } => { - names.insert(name.clone()); - names.extend(enum_values.values().map(|value| value.name.to_string())); - } - MetaType::InputObject { - name, input_fields, .. - } => { - names.insert(name.clone()); - names.extend(input_fields.values().map(|field| field.name.to_string())); - } - } - } - - names.into_iter().collect() - } - - pub fn set_description(&mut self, name: impl AsRef, desc: impl Into) { - let desc = desc.into(); - match self.types.get_mut(name.as_ref()) { - Some(MetaType::Scalar { description, .. }) => *description = Some(desc), - Some(MetaType::Object { description, .. }) => *description = Some(desc), - Some(MetaType::Interface { description, .. }) => *description = Some(desc), - Some(MetaType::Union { description, .. }) => *description = Some(desc), - Some(MetaType::Enum { description, .. }) => *description = Some(desc), - Some(MetaType::InputObject { description, .. }) => *description = Some(desc), - None => {} - } - } - - pub fn remove_unused_types(&mut self) { - let mut used_types = BTreeSet::new(); - let mut unused_types = BTreeSet::new(); - - fn traverse_field<'a>( - types: &'a BTreeMap, - used_types: &mut BTreeSet<&'a str>, - field: &'a MetaField, - ) { - traverse_type( - types, - used_types, - MetaTypeName::concrete_typename(&field.ty), - ); - for arg in field.args.values() { - traverse_input_value(types, used_types, arg); - } - } - - fn traverse_input_value<'a>( - types: &'a BTreeMap, - used_types: &mut BTreeSet<&'a str>, - input_value: &'a MetaInputValue, - ) { - traverse_type( - types, - used_types, - MetaTypeName::concrete_typename(&input_value.ty), - ); - } - - fn traverse_type<'a>( - types: &'a BTreeMap, - used_types: &mut BTreeSet<&'a str>, - type_name: &'a str, - ) { - if used_types.contains(type_name) { - return; - } - - if let Some(ty) = types.get(type_name) { - used_types.insert(type_name); - match ty { - MetaType::Object { fields, .. } => { - for field in fields.values() { - traverse_field(types, used_types, field); - } - } - MetaType::Interface { - fields, - possible_types, - .. - } => { - for field in fields.values() { - traverse_field(types, used_types, field); - } - for type_name in possible_types.iter() { - traverse_type(types, used_types, type_name); - } - } - MetaType::Union { possible_types, .. } => { - for type_name in possible_types.iter() { - traverse_type(types, used_types, type_name); - } - } - MetaType::InputObject { input_fields, .. } => { - for field in input_fields.values() { - traverse_input_value(types, used_types, field); - } - } - _ => {} - } - } - } - - for directive in self.directives.values() { - for arg in directive.args.values() { - traverse_input_value(&self.types, &mut used_types, arg); - } - } - - for type_name in Some(&self.query_type) - .into_iter() - .chain(self.mutation_type.iter()) - .chain(self.subscription_type.iter()) - { - traverse_type(&self.types, &mut used_types, type_name); - } - - for ty in self.types.values().filter(|ty| match ty { - MetaType::Object { - keys: Some(keys), .. - } - | MetaType::Interface { - keys: Some(keys), .. - } => !keys.is_empty(), - _ => false, - }) { - traverse_type(&self.types, &mut used_types, ty.name()); - } - - for ty in self.types.values() { - let name = ty.name(); - if !is_system_type(name) && !used_types.contains(name) { - unused_types.insert(name.to_string()); - } - } - - for type_name in unused_types { - self.types.remove(&type_name); - } - } - - pub fn find_visible_types(&self, ctx: &Context<'_>) -> HashSet<&str> { - let mut visible_types = HashSet::new(); - - fn traverse_field<'a>( - ctx: &Context<'_>, - types: &'a BTreeMap, - visible_types: &mut HashSet<&'a str>, - field: &'a MetaField, - ) { - if !is_visible(ctx, &field.visible) { - return; - } - - traverse_type( - ctx, - types, - visible_types, - MetaTypeName::concrete_typename(&field.ty), - ); - for arg in field.args.values() { - traverse_input_value(ctx, types, visible_types, arg); - } - } - - fn traverse_input_value<'a>( - ctx: &Context<'_>, - types: &'a BTreeMap, - visible_types: &mut HashSet<&'a str>, - input_value: &'a MetaInputValue, - ) { - if !is_visible(ctx, &input_value.visible) { - return; - } - - traverse_type( - ctx, - types, - visible_types, - MetaTypeName::concrete_typename(&input_value.ty), - ); - } - - fn traverse_type<'a>( - ctx: &Context<'_>, - types: &'a BTreeMap, - visible_types: &mut HashSet<&'a str>, - type_name: &'a str, - ) { - if visible_types.contains(type_name) { - return; - } - - if let Some(ty) = types.get(type_name) { - if !ty.is_visible(ctx) { - return; - } - - visible_types.insert(type_name); - match ty { - MetaType::Object { fields, .. } => { - for field in fields.values() { - traverse_field(ctx, types, visible_types, field); - } - } - MetaType::Interface { - fields, - possible_types, - .. - } => { - for field in fields.values() { - traverse_field(ctx, types, visible_types, field); - } - for type_name in possible_types.iter() { - traverse_type(ctx, types, visible_types, type_name); - } - } - MetaType::Union { possible_types, .. } => { - for type_name in possible_types.iter() { - traverse_type(ctx, types, visible_types, type_name); - } - } - MetaType::InputObject { input_fields, .. } => { - for field in input_fields.values() { - traverse_input_value(ctx, types, visible_types, field); - } - } - _ => {} - } - } - } - - for directive in self.directives.values() { - if is_visible(ctx, &directive.visible) { - for arg in directive.args.values() { - traverse_input_value(ctx, &self.types, &mut visible_types, arg); - } - } - } - - for type_name in Some(&self.query_type) - .into_iter() - .chain(self.mutation_type.iter()) - .chain(self.subscription_type.iter()) - { - traverse_type(ctx, &self.types, &mut visible_types, type_name); - } - - for ty in self.types.values().filter(|ty| match ty { - MetaType::Object { - keys: Some(keys), .. - } - | MetaType::Interface { - keys: Some(keys), .. - } => !keys.is_empty(), - _ => false, - }) { - traverse_type(ctx, &self.types, &mut visible_types, ty.name()); - } - - for ty in self.types.values() { - if let MetaType::Interface { possible_types, .. } = ty { - if ty.is_visible(ctx) && !visible_types.contains(ty.name()) { - for type_name in possible_types.iter() { - if visible_types.contains(type_name.as_str()) { - traverse_type(ctx, &self.types, &mut visible_types, ty.name()); - break; - } - } - } - } - } - - self.types - .values() - .filter_map(|ty| { - let name = ty.name(); - if is_system_type(name) || visible_types.contains(name) { - Some(name) - } else { - None - } - }) - .collect() - } -} - -pub(crate) fn is_visible(ctx: &Context<'_>, visible: &Option) -> bool { - match visible { - Some(f) => f(ctx), - None => true, - } -} - -fn is_system_type(name: &str) -> bool { - if name.starts_with("__") { - return true; - } - - name == "Boolean" || name == "Int" || name == "Float" || name == "String" || name == "ID" -} - -#[cfg(test)] -mod test { - use crate::registry::MetaDirectiveInvocation; - - #[test] - fn test_directive_invocation_dsl() { - let expected = r#"@testDirective(int_value: 1, str_value: "abc")"#; - assert_eq!( - expected.to_string(), - MetaDirectiveInvocation { - name: "testDirective".to_string(), - args: [ - ("int_value".to_string(), 1u32.into()), - ("str_value".to_string(), "abc".into()) - ] - .into(), - } - .sdl() - ) - } -} diff --git a/src/registry/stringify_exec_doc.rs b/src/registry/stringify_exec_doc.rs deleted file mode 100644 index 4f8458a16..000000000 --- a/src/registry/stringify_exec_doc.rs +++ /dev/null @@ -1,317 +0,0 @@ -use std::fmt::{Error, Result as FmtResult, Write}; - -use async_graphql_value::ConstValue; - -use crate::{ - Variables, - parser::types::{ - ExecutableDocument, FragmentDefinition, OperationType, Selection, SelectionSet, - }, - registry::{MetaInputValue, MetaType, MetaTypeName, Registry}, -}; - -impl Registry { - pub(crate) fn stringify_exec_doc( - &self, - variables: &Variables, - doc: &ExecutableDocument, - ) -> Result { - let mut output = String::new(); - for (name, fragment) in &doc.fragments { - self.stringify_fragment_definition( - &mut output, - variables, - name, - self.types - .get(fragment.node.type_condition.node.on.node.as_str()), - &fragment.node, - )?; - } - for (name, operation_definition) in doc.operations.iter() { - write!(&mut output, "{} ", operation_definition.node.ty)?; - if let Some(name) = name { - write!(&mut output, "{}", name)?; - - if !operation_definition.node.variable_definitions.is_empty() { - output.push('('); - for (idx, variable_definition) in operation_definition - .node - .variable_definitions - .iter() - .enumerate() - { - if idx > 0 { - output.push_str(", "); - } - write!( - output, - "${}: {}", - variable_definition.node.name.node, - variable_definition.node.var_type.node - )?; - if let Some(default_value) = &variable_definition.node.default_value { - write!(output, " = {}", default_value.node)?; - } - } - output.push(')'); - } - - output.push(' '); - } - let root_type = match operation_definition.node.ty { - OperationType::Query => self.types.get(&self.query_type), - OperationType::Mutation => self - .mutation_type - .as_ref() - .and_then(|name| self.types.get(name)), - OperationType::Subscription => self - .subscription_type - .as_ref() - .and_then(|name| self.types.get(name)), - }; - self.stringify_selection_set( - &mut output, - variables, - &operation_definition.node.selection_set.node, - root_type, - )?; - } - Ok(output) - } - - fn stringify_fragment_definition( - &self, - output: &mut String, - variables: &Variables, - name: &str, - parent_type: Option<&MetaType>, - fragment_definition: &FragmentDefinition, - ) -> FmtResult { - write!( - output, - "fragment {} on {}", - name, fragment_definition.type_condition.node.on.node - )?; - self.stringify_selection_set( - output, - variables, - &fragment_definition.selection_set.node, - parent_type, - )?; - output.push_str("}\n\n"); - Ok(()) - } - - fn stringify_input_value( - &self, - output: &mut String, - meta_input_value: Option<&MetaInputValue>, - value: &ConstValue, - ) -> FmtResult { - if meta_input_value.map(|v| v.is_secret).unwrap_or_default() { - output.push_str("\"\""); - return Ok(()); - } - - match value { - ConstValue::Object(obj) => { - let parent_type = meta_input_value.and_then(|input_value| { - self.types - .get(MetaTypeName::concrete_typename(&input_value.ty)) - }); - if let Some(MetaType::InputObject { input_fields, .. }) = parent_type { - output.push('{'); - for (idx, (key, value)) in obj.iter().enumerate() { - if idx > 0 { - output.push_str(", "); - } - write!(output, "{}: ", key)?; - self.stringify_input_value(output, input_fields.get(key.as_str()), value)?; - } - output.push('}'); - } else { - write!(output, "{}", value)?; - } - } - _ => write!(output, "{}", value)?, - } - - Ok(()) - } - - fn stringify_selection_set( - &self, - output: &mut String, - variables: &Variables, - selection_set: &SelectionSet, - parent_type: Option<&MetaType>, - ) -> FmtResult { - output.push_str("{ "); - for (idx, selection) in selection_set.items.iter().map(|s| &s.node).enumerate() { - if idx > 0 { - output.push(' '); - } - match selection { - Selection::Field(field) => { - if let Some(alias) = &field.node.alias { - write!(output, "{}:", alias.node)?; - } - write!(output, "{}", field.node.name.node)?; - if !field.node.arguments.is_empty() { - output.push('('); - for (idx, (name, argument)) in field.node.arguments.iter().enumerate() { - let meta_input_value = parent_type - .and_then(|parent_type| { - parent_type.field_by_name(field.node.name.node.as_str()) - }) - .and_then(|field| field.args.get(name.node.as_str())); - if idx > 0 { - output.push_str(", "); - } - write!(output, "{}: ", name)?; - let value = argument - .node - .clone() - .into_const_with(|name| variables.get(&name).cloned().ok_or(())) - .unwrap_or_default(); - self.stringify_input_value(output, meta_input_value, &value)?; - } - output.push(')'); - } - if !field.node.selection_set.node.items.is_empty() { - output.push(' '); - let parent_type = parent_type - .and_then(|ty| ty.field_by_name(field.node.name.node.as_str())) - .and_then(|field| { - self.types.get(MetaTypeName::concrete_typename(&field.ty)) - }); - self.stringify_selection_set( - output, - variables, - &field.node.selection_set.node, - parent_type, - )?; - } - } - Selection::FragmentSpread(fragment_spread) => { - write!(output, "... {}", fragment_spread.node.fragment_name.node)?; - } - Selection::InlineFragment(inline_fragment) => { - output.push_str("... "); - let parent_type = if let Some(name) = &inline_fragment.node.type_condition { - write!(output, "on {} ", name.node.on.node)?; - self.types.get(name.node.on.node.as_str()) - } else { - None - }; - self.stringify_selection_set( - output, - variables, - &inline_fragment.node.selection_set.node, - parent_type, - )?; - } - } - } - output.push_str(" }"); - Ok(()) - } -} - -#[cfg(test)] -#[allow(clippy::diverging_sub_expression)] -mod tests { - use super::*; - use crate::{parser::parse_query, *}; - - #[test] - fn test_stringify() { - let registry = Registry::default(); - let doc = parse_query( - r#" - query Abc { - a b c(a:1,b:2) { - d e f - } - } - "#, - ) - .unwrap(); - assert_eq!( - registry - .stringify_exec_doc(&Default::default(), &doc) - .unwrap(), - r#"query Abc { a b c(a: 1, b: 2) { d e f } }"# - ); - - let doc = parse_query( - r#" - query Abc($a:Int) { - value(input:$a) - } - "#, - ) - .unwrap(); - assert_eq!( - registry - .stringify_exec_doc( - &Variables::from_value(value! ({ - "a": 10, - })), - &doc - ) - .unwrap(), - r#"query Abc($a: Int) { value(input: 10) }"# - ); - } - - #[test] - fn test_stringify_secret() { - #[derive(InputObject)] - #[graphql(internal)] - struct MyInput { - v1: i32, - #[graphql(secret)] - v2: i32, - v3: MyInput2, - } - - #[derive(InputObject)] - #[graphql(internal)] - struct MyInput2 { - v4: i32, - #[graphql(secret)] - v5: i32, - } - - struct Query; - - #[Object(internal)] - #[allow(unreachable_code, unused_variables)] - impl Query { - async fn value(&self, a: i32, #[graphql(secret)] b: i32, c: MyInput) -> i32 { - todo!() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let registry = schema.registry(); - let s = registry - .stringify_exec_doc( - &Default::default(), - &parse_query( - r#" - { - value(a: 10, b: 20, c: { v1: 1, v2: 2, v3: { v4: 4, v5: 5}}) - } - "#, - ) - .unwrap(), - ) - .unwrap(); - assert_eq!( - s, - r#"query { value(a: 10, b: "", c: {v1: 1, v2: "", v3: {v4: 4, v5: ""}}) }"# - ); - } -} diff --git a/src/request.rs b/src/request.rs deleted file mode 100644 index f1f2b7694..000000000 --- a/src/request.rs +++ /dev/null @@ -1,421 +0,0 @@ -use std::{ - any::Any, - fmt::{self, Debug, Formatter}, -}; - -use serde::{Deserialize, Deserializer, Serialize}; - -use crate::{ - Data, Extensions, ParseRequestError, ServerError, UploadValue, Value, Variables, - parser::{parse_query, types::ExecutableDocument}, - schema::IntrospectionMode, -}; - -/// GraphQL request. -/// -/// This can be deserialized from a structure of the query string, the operation -/// name and the variables. The names are all in `camelCase` (e.g. -/// `operationName`). -#[non_exhaustive] -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Request { - /// The query source of the request. - #[serde(default)] - pub query: String, - - /// The operation name of the request. - #[serde(default, rename = "operationName")] - pub operation_name: Option, - - /// The variables of the request. - #[serde(default)] - pub variables: Variables, - - /// Uploads sent with the request. - #[serde(skip)] - pub uploads: Vec, - - /// The data of the request that can be accessed through `Context::data`. - /// - /// **This data is only valid for this request** - #[serde(skip)] - pub data: Data, - - /// The extensions config of the request. - #[serde(default)] - pub extensions: Extensions, - - #[serde(skip)] - pub(crate) parsed_query: Option, - - /// Sets the introspection mode for this request (defaults to - /// [IntrospectionMode::Enabled]). - #[serde(skip)] - pub introspection_mode: IntrospectionMode, -} - -impl Request { - /// Create a request object with query source. - pub fn new(query: impl Into) -> Self { - Self { - query: query.into(), - operation_name: None, - variables: Variables::default(), - uploads: Vec::default(), - data: Data::default(), - extensions: Default::default(), - parsed_query: None, - introspection_mode: IntrospectionMode::Enabled, - } - } - - /// Specify the operation name of the request. - #[must_use] - pub fn operation_name>(self, name: T) -> Self { - Self { - operation_name: Some(name.into()), - ..self - } - } - - /// Specify the variables. - #[must_use] - pub fn variables(self, variables: Variables) -> Self { - Self { variables, ..self } - } - - /// Insert some data for this request. - #[must_use] - pub fn data(mut self, data: D) -> Self { - self.data.insert(data); - self - } - - /// Disable introspection queries for this request. - #[must_use] - pub fn disable_introspection(mut self) -> Self { - self.introspection_mode = IntrospectionMode::Disabled; - self - } - - /// Only allow introspection queries for this request. - #[must_use] - pub fn only_introspection(mut self) -> Self { - self.introspection_mode = IntrospectionMode::IntrospectionOnly; - self - } - - #[inline] - /// Performs parsing of query ahead of execution. - /// - /// This effectively allows to inspect query information, before passing - /// request to schema for execution as long as query is valid. - pub fn parsed_query(&mut self) -> Result<&ExecutableDocument, ServerError> { - if self.parsed_query.is_none() { - match parse_query(&self.query) { - Ok(parsed) => self.parsed_query = Some(parsed), - Err(error) => return Err(error.into()), - } - } - - // forbid_unsafe effectively bans optimize away else branch here so use unwrap - // but this unwrap never panics - Ok(self.parsed_query.as_ref().unwrap()) - } - - /// Sets the parsed query into the request. - /// - /// This is useful special with dynamic schema when the query has been - /// parsed ahead of time. It can reduce performance overhead of parsing - /// the query again. - pub fn set_parsed_query(&mut self, doc: ExecutableDocument) { - self.parsed_query = Some(doc); - } - - /// Set a variable to an upload value. - /// - /// `var_path` is a dot-separated path to the item that begins with - /// `variables`, for example `variables.files.2.content` is equivalent - /// to the Rust code `request.variables["files"][2]["content"]`. If no - /// variable exists at the path this function won't do anything. - pub fn set_upload(&mut self, var_path: &str, upload: UploadValue) { - fn variable_path<'a>(variables: &'a mut Variables, path: &str) -> Option<&'a mut Value> { - let mut parts = path.strip_prefix("variables.")?.split('.'); - - let initial = variables.get_mut(parts.next().unwrap())?; - - parts.try_fold(initial, |current, part| match current { - Value::List(list) => part - .parse::() - .ok() - .and_then(|idx| usize::try_from(idx).ok()) - .and_then(move |idx| list.get_mut(idx)), - Value::Object(obj) => obj.get_mut(part), - _ => None, - }) - } - - let variable = match variable_path(&mut self.variables, var_path) { - Some(variable) => variable, - None => return, - }; - self.uploads.push(upload); - *variable = Value::String(format!("#__graphql_file__:{}", self.uploads.len() - 1)); - } -} - -impl> From for Request { - fn from(query: T) -> Self { - Self::new(query) - } -} - -impl Debug for Request { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_struct("Request") - .field("query", &self.query) - .field("operation_name", &self.operation_name) - .field("variables", &self.variables) - .field("extensions", &self.extensions) - .finish() - } -} - -/// Batch support for GraphQL requests, which is either a single query, or an -/// array of queries -/// -/// **Reference:** -#[derive(Debug, Deserialize)] -#[serde(untagged)] -#[allow(clippy::large_enum_variant)] // Request is at fault -pub enum BatchRequest { - /// Single query - Single(Request), - - /// Non-empty array of queries - #[serde(deserialize_with = "deserialize_non_empty_vec")] - Batch(Vec), -} - -impl BatchRequest { - /// Attempt to convert the batch request into a single request. - /// - /// # Errors - /// - /// Fails if the batch request is a list of requests with a message saying - /// that batch requests aren't supported. - pub fn into_single(self) -> Result { - match self { - Self::Single(req) => Ok(req), - Self::Batch(_) => Err(ParseRequestError::UnsupportedBatch), - } - } - - /// Returns an iterator over the requests. - pub fn iter(&self) -> impl Iterator { - match self { - BatchRequest::Single(request) => { - Box::new(std::iter::once(request)) as Box> - } - BatchRequest::Batch(requests) => Box::new(requests.iter()), - } - } - - /// Returns an iterator that allows modifying each request. - pub fn iter_mut(&mut self) -> impl Iterator { - match self { - BatchRequest::Single(request) => { - Box::new(std::iter::once(request)) as Box> - } - BatchRequest::Batch(requests) => Box::new(requests.iter_mut()), - } - } - - /// Specify the variables for each requests. - #[must_use] - pub fn variables(mut self, variables: Variables) -> Self { - for request in self.iter_mut() { - request.variables = variables.clone(); - } - self - } - - /// Insert some data for for each requests. - #[must_use] - pub fn data(mut self, data: D) -> Self { - for request in self.iter_mut() { - request.data.insert(data.clone()); - } - self - } - - /// Disable introspection queries for each request. - #[must_use] - pub fn disable_introspection(mut self) -> Self { - for request in self.iter_mut() { - request.introspection_mode = IntrospectionMode::Disabled; - } - self - } - - /// Only allow introspection queries for each request. - #[must_use] - pub fn introspection_only(mut self) -> Self { - for request in self.iter_mut() { - request.introspection_mode = IntrospectionMode::IntrospectionOnly; - } - self - } -} - -fn deserialize_non_empty_vec<'de, D, T>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, - T: Deserialize<'de>, -{ - use serde::de::Error as _; - - let v = >::deserialize(deserializer)?; - if v.is_empty() { - Err(D::Error::invalid_length(0, &"a non-empty sequence")) - } else { - Ok(v) - } -} - -impl From for BatchRequest { - fn from(r: Request) -> Self { - BatchRequest::Single(r) - } -} - -impl From> for BatchRequest { - fn from(r: Vec) -> Self { - BatchRequest::Batch(r) - } -} - -#[cfg(test)] -mod tests { - use crate::*; - - #[test] - fn test_request() { - let request: Request = from_value(value! ({ - "query": "{ a b c }" - })) - .unwrap(); - assert!(request.variables.is_empty()); - assert!(request.operation_name.is_none()); - assert_eq!(request.query, "{ a b c }"); - } - - #[test] - fn test_request_with_operation_name() { - let request: Request = from_value(value! ({ - "query": "{ a b c }", - "operationName": "a" - })) - .unwrap(); - assert!(request.variables.is_empty()); - assert_eq!(request.operation_name.as_deref(), Some("a")); - assert_eq!(request.query, "{ a b c }"); - } - - #[test] - fn test_request_with_variables() { - let request: Request = from_value(value! ({ - "query": "{ a b c }", - "variables": { - "v1": 100, - "v2": [1, 2, 3], - "v3": "str", - } - })) - .unwrap(); - assert_eq!( - request.variables.into_value(), - value!({ - "v1": 100, - "v2": [1, 2, 3], - "v3": "str", - }) - ); - assert!(request.operation_name.is_none()); - assert_eq!(request.query, "{ a b c }"); - } - - #[test] - fn test_deserialize_request_with_empty_object_variables() { - let request: Request = from_value(value! ({ - "query": "{ a b c }", - "variables": {} - })) - .unwrap(); - assert!(request.operation_name.is_none()); - assert!(request.variables.is_empty()); - } - - #[test] - fn test_deserialize_request_with_empty_array_variables() { - let error: DeserializerError = from_value::(value! ({ - "query": "{ a b c }", - "variables": [] - })) - .unwrap_err(); - assert_eq!(error.to_string(), "invalid type: sequence, expected a map"); - } - - #[test] - fn test_deserialize_request_with_null_variables() { - let request: Request = from_value(value! ({ - "query": "{ a b c }", - "variables": null - })) - .unwrap(); - assert!(request.operation_name.is_none()); - assert!(request.variables.is_empty()); - } - - #[test] - fn test_batch_request_single() { - let request: BatchRequest = from_value(value! ({ - "query": "{ a b c }" - })) - .unwrap(); - - if let BatchRequest::Single(request) = request { - assert!(request.variables.is_empty()); - assert!(request.operation_name.is_none()); - assert_eq!(request.query, "{ a b c }"); - } else { - unreachable!() - } - } - - #[test] - fn test_batch_request_batch() { - let request: BatchRequest = from_value(value!([ - { - "query": "{ a b c }" - }, - { - "query": "{ d e }" - } - ])) - .unwrap(); - - if let BatchRequest::Batch(requests) = request { - assert!(requests[0].variables.is_empty()); - assert!(requests[0].operation_name.is_none()); - assert_eq!(requests[0].query, "{ a b c }"); - - assert!(requests[1].variables.is_empty()); - assert!(requests[1].operation_name.is_none()); - assert_eq!(requests[1].query, "{ d e }"); - } else { - unreachable!() - } - } -} diff --git a/src/resolver_utils/container.rs b/src/resolver_utils/container.rs deleted file mode 100644 index 7435dce93..000000000 --- a/src/resolver_utils/container.rs +++ /dev/null @@ -1,382 +0,0 @@ -use std::{future::Future, pin::Pin, sync::Arc}; - -use futures_util::FutureExt; -use indexmap::IndexMap; - -use crate::{ - Context, ContextBase, ContextSelectionSet, Error, Name, OutputType, ServerError, ServerResult, - Value, extensions::ResolveInfo, parser::types::Selection, -}; - -/// Represents a GraphQL container object. -/// -/// This helper trait allows the type to call `resolve_container` on itself in -/// its `OutputType::resolve` implementation. -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -pub trait ContainerType: OutputType { - /// This function returns true of type `EmptyMutation` only. - #[doc(hidden)] - fn is_empty() -> bool { - false - } - - /// Resolves a field value and outputs it as a json value - /// `async_graphql::Value`. - /// - /// If the field was not found returns None. - #[cfg(feature = "boxed-trait")] - async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult>; - - /// Resolves a field value and outputs it as a json value - /// `async_graphql::Value`. - /// - /// If the field was not found returns None. - #[cfg(not(feature = "boxed-trait"))] - fn resolve_field( - &self, - ctx: &Context<'_>, - ) -> impl Future>> + Send; - - /// Collect all the fields of the container that are queried in the - /// selection set. - /// - /// Objects do not have to override this, but interfaces and unions must - /// call it on their internal type. - fn collect_all_fields<'a>( - &'a self, - ctx: &ContextSelectionSet<'a>, - fields: &mut Fields<'a>, - ) -> ServerResult<()> - where - Self: Send + Sync, - { - fields.add_set(ctx, self) - } - - /// Find the GraphQL entity with the given name from the parameter. - /// - /// Objects should override this in case they are the query root. - #[cfg(feature = "boxed-trait")] - async fn find_entity(&self, _: &Context<'_>, _: &Value) -> ServerResult> { - Ok(None) - } - - /// Find the GraphQL entity with the given name from the parameter. - /// - /// Objects should override this in case they are the query root. - #[cfg(not(feature = "boxed-trait"))] - fn find_entity( - &self, - _: &Context<'_>, - _params: &Value, - ) -> impl Future>> + Send { - async { Ok(None) } - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl ContainerType for &T { - async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult> { - T::resolve_field(*self, ctx).await - } - - async fn find_entity(&self, ctx: &Context<'_>, params: &Value) -> ServerResult> { - T::find_entity(*self, ctx, params).await - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl ContainerType for Arc { - async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult> { - T::resolve_field(self, ctx).await - } - - async fn find_entity(&self, ctx: &Context<'_>, params: &Value) -> ServerResult> { - T::find_entity(self, ctx, params).await - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl ContainerType for Box { - async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult> { - T::resolve_field(self, ctx).await - } - - async fn find_entity(&self, ctx: &Context<'_>, params: &Value) -> ServerResult> { - T::find_entity(self, ctx, params).await - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl + Send + Sync + Clone> ContainerType for Result { - async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult> { - match self { - Ok(value) => T::resolve_field(value, ctx).await, - Err(err) => Err(ctx.set_error_path(err.clone().into().into_server_error(ctx.item.pos))), - } - } - - async fn find_entity(&self, ctx: &Context<'_>, params: &Value) -> ServerResult> { - match self { - Ok(value) => T::find_entity(value, ctx, params).await, - Err(err) => Err(ctx.set_error_path(err.clone().into().into_server_error(ctx.item.pos))), - } - } -} - -/// Resolve an container by executing each of the fields concurrently. -pub async fn resolve_container<'a, T: ContainerType + ?Sized>( - ctx: &ContextSelectionSet<'a>, - root: &'a T, -) -> ServerResult { - resolve_container_inner(ctx, root, true).await -} - -/// Resolve an container by executing each of the fields serially. -pub async fn resolve_container_serial<'a, T: ContainerType + ?Sized>( - ctx: &ContextSelectionSet<'a>, - root: &'a T, -) -> ServerResult { - resolve_container_inner(ctx, root, false).await -} - -pub(crate) fn create_value_object(values: Vec<(Name, Value)>) -> Value { - let mut map = IndexMap::new(); - for (name, value) in values { - insert_value(&mut map, name, value); - } - Value::Object(map) -} - -fn insert_value(target: &mut IndexMap, name: Name, value: Value) { - if let Some(prev_value) = target.get_mut(&name) { - if let Value::Object(target_map) = prev_value { - if let Value::Object(obj) = value { - for (key, value) in obj.into_iter() { - insert_value(target_map, key, value); - } - } - } else if let Value::List(target_list) = prev_value { - if let Value::List(list) = value { - for (idx, value) in list.into_iter().enumerate() { - if let Some(Value::Object(target_map)) = target_list.get_mut(idx) { - if let Value::Object(obj) = value { - for (key, value) in obj.into_iter() { - insert_value(target_map, key, value); - } - } - } - } - } - } - } else { - target.insert(name, value); - } -} - -async fn resolve_container_inner<'a, T: ContainerType + ?Sized>( - ctx: &ContextSelectionSet<'a>, - root: &'a T, - parallel: bool, -) -> ServerResult { - let mut fields = Fields(Vec::new()); - fields.add_set(ctx, root)?; - - let res = if parallel { - futures_util::future::try_join_all(fields.0).await? - } else { - let mut results = Vec::with_capacity(fields.0.len()); - for field in fields.0 { - results.push(field.await?); - } - results - }; - - Ok(create_value_object(res)) -} - -type BoxFieldFuture<'a> = Pin> + 'a + Send>>; - -/// A set of fields on an container that are being selected. -pub struct Fields<'a>(Vec>); - -impl<'a> Fields<'a> { - /// Add another set of fields to this set of fields using the given - /// container. - pub fn add_set( - &mut self, - ctx: &ContextSelectionSet<'a>, - root: &'a T, - ) -> ServerResult<()> { - for selection in &ctx.item.node.items { - match &selection.node { - Selection::Field(field) => { - if field.node.name.node == "__typename" { - // Get the typename - let ctx_field = ctx.with_field(field); - let field_name = ctx_field.item.node.response_key().node.clone(); - let typename = root.introspection_type_name().into_owned(); - - self.0.push(Box::pin(async move { - Ok((field_name, Value::String(typename))) - })); - continue; - } - - let resolve_fut = Box::pin({ - let ctx = ctx.clone(); - async move { - let ctx_field = ctx.with_field(field); - let field_name = ctx_field.item.node.response_key().node.clone(); - let extensions = &ctx.query_env.extensions; - - if extensions.is_empty() && field.node.directives.is_empty() { - Ok(( - field_name, - root.resolve_field(&ctx_field).await?.unwrap_or_default(), - )) - } else { - let type_name = T::type_name(); - let resolve_info = ResolveInfo { - path_node: ctx_field.path_node.as_ref().unwrap(), - parent_type: &type_name, - return_type: match ctx_field - .schema_env - .registry - .types - .get(type_name.as_ref()) - .and_then(|ty| { - ty.field_by_name(field.node.name.node.as_str()) - }) - .map(|field| &field.ty) - { - Some(ty) => &ty, - None => { - return Err(ServerError::new( - format!( - r#"Cannot query field "{}" on type "{}"."#, - field_name, type_name - ), - Some(ctx_field.item.pos), - )); - } - }, - name: field.node.name.node.as_str(), - alias: field - .node - .alias - .as_ref() - .map(|alias| alias.node.as_str()), - is_for_introspection: ctx_field.is_for_introspection, - field: &field.node, - }; - - let resolve_fut = root.resolve_field(&ctx_field); - - if field.node.directives.is_empty() { - futures_util::pin_mut!(resolve_fut); - Ok(( - field_name, - extensions - .resolve(resolve_info, &mut resolve_fut) - .await? - .unwrap_or_default(), - )) - } else { - let mut resolve_fut = resolve_fut.boxed(); - - for directive in &field.node.directives { - if let Some(directive_factory) = ctx - .schema_env - .custom_directives - .get(directive.node.name.node.as_str()) - { - let ctx_directive = ContextBase { - path_node: ctx_field.path_node, - is_for_introspection: false, - item: directive, - schema_env: ctx_field.schema_env, - query_env: ctx_field.query_env, - execute_data: ctx_field.execute_data, - }; - let directive_instance = directive_factory - .create(&ctx_directive, &directive.node)?; - resolve_fut = Box::pin({ - let ctx_field = ctx_field.clone(); - async move { - directive_instance - .resolve_field(&ctx_field, &mut resolve_fut) - .await - } - }); - } - } - - Ok(( - field_name, - extensions - .resolve(resolve_info, &mut resolve_fut) - .await? - .unwrap_or_default(), - )) - } - } - } - }); - - self.0.push(resolve_fut); - } - selection => { - let (type_condition, selection_set) = match selection { - Selection::Field(_) => unreachable!(), - Selection::FragmentSpread(spread) => { - let fragment = - ctx.query_env.fragments.get(&spread.node.fragment_name.node); - let fragment = match fragment { - Some(fragment) => fragment, - None => { - return Err(ServerError::new( - format!( - r#"Unknown fragment "{}"."#, - spread.node.fragment_name.node - ), - Some(spread.pos), - )); - } - }; - ( - Some(&fragment.node.type_condition), - &fragment.node.selection_set, - ) - } - Selection::InlineFragment(fragment) => ( - fragment.node.type_condition.as_ref(), - &fragment.node.selection_set, - ), - }; - let type_condition = - type_condition.map(|condition| condition.node.on.node.as_str()); - - let introspection_type_name = root.introspection_type_name(); - - let applies_concrete_object = type_condition.is_some_and(|condition| { - introspection_type_name == condition - || ctx - .schema_env - .registry - .implements - .get(&*introspection_type_name) - .is_some_and(|interfaces| interfaces.contains(condition)) - }); - if applies_concrete_object { - root.collect_all_fields(&ctx.with_selection_set(selection_set), self)?; - } else if type_condition.is_none_or(|condition| T::type_name() == condition) { - // The fragment applies to an interface type. - self.add_set(&ctx.with_selection_set(selection_set), root)?; - } - } - } - } - Ok(()) - } -} diff --git a/src/resolver_utils/enum.rs b/src/resolver_utils/enum.rs deleted file mode 100644 index 8f8d6de54..000000000 --- a/src/resolver_utils/enum.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::{InputType, InputValueError, InputValueResult, Name, Value}; - -/// A variant of an enum. -pub struct EnumItem { - /// The name of the variant. - pub name: &'static str, - /// The value of the variant. - pub value: T, -} - -/// A GraphQL enum. -pub trait EnumType: Sized + Eq + Send + Copy + Sized + 'static { - /// Get a list of possible variants of the enum and their values. - fn items() -> &'static [EnumItem]; -} - -/// Parse a value as an enum value. -/// -/// This can be used to implement `InputType::parse`. -pub fn parse_enum(value: Value) -> InputValueResult { - let value = match &value { - Value::Enum(s) => s, - Value::String(s) => s.as_str(), - _ => return Err(InputValueError::expected_type(value)), - }; - - T::items() - .iter() - .find(|item| item.name == value) - .map(|item| item.value) - .ok_or_else(|| { - InputValueError::custom(format_args!( - r#"Enumeration type does not contain value "{}"."#, - value, - )) - }) -} - -/// Convert the enum value into a GraphQL value. -/// -/// This can be used to implement `InputType::to_value` or -/// `OutputType::resolve`. -pub fn enum_value(value: T) -> Value { - let item = T::items().iter().find(|item| item.value == value).unwrap(); - Value::Enum(Name::new(item.name)) -} diff --git a/src/resolver_utils/list.rs b/src/resolver_utils/list.rs deleted file mode 100644 index fc1cdfa3f..000000000 --- a/src/resolver_utils/list.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::{ - ContextSelectionSet, OutputType, Positioned, ServerResult, Value, extensions::ResolveInfo, - parser::types::Field, -}; - -/// Resolve an list by executing each of the items concurrently. -pub async fn resolve_list<'a, T: OutputType + 'a>( - ctx: &ContextSelectionSet<'a>, - field: &Positioned, - iter: impl IntoIterator, - len: Option, -) -> ServerResult { - let extensions = &ctx.query_env.extensions; - if !extensions.is_empty() { - let mut futures = len.map(Vec::with_capacity).unwrap_or_default(); - for (idx, item) in iter.into_iter().enumerate() { - futures.push({ - let ctx = ctx.clone(); - async move { - let ctx_idx = ctx.with_index(idx); - let extensions = &ctx.query_env.extensions; - - let resolve_info = ResolveInfo { - path_node: ctx_idx.path_node.as_ref().unwrap(), - parent_type: &Vec::::type_name(), - return_type: &T::qualified_type_name(), - name: field.node.name.node.as_str(), - alias: field.node.alias.as_ref().map(|alias| alias.node.as_str()), - is_for_introspection: ctx_idx.is_for_introspection, - field: &field.node, - }; - let resolve_fut = async { - OutputType::resolve(&item, &ctx_idx, field) - .await - .map(Option::Some) - .map_err(|err| ctx_idx.set_error_path(err)) - }; - futures_util::pin_mut!(resolve_fut); - extensions - .resolve(resolve_info, &mut resolve_fut) - .await - .map(|value| value.expect("You definitely encountered a bug!")) - } - }); - } - Ok(Value::List( - futures_util::future::try_join_all(futures).await?, - )) - } else { - let mut futures = len.map(Vec::with_capacity).unwrap_or_default(); - for (idx, item) in iter.into_iter().enumerate() { - let ctx_idx = ctx.with_index(idx); - futures.push(async move { - OutputType::resolve(&item, &ctx_idx, field) - .await - .map_err(|err| ctx_idx.set_error_path(err)) - }); - } - Ok(Value::List( - futures_util::future::try_join_all(futures).await?, - )) - } -} diff --git a/src/resolver_utils/mod.rs b/src/resolver_utils/mod.rs deleted file mode 100644 index 1f410faf5..000000000 --- a/src/resolver_utils/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Utilities for implementing -//! [`OutputType::resolve`](trait.OutputType.html#tymethod.resolve). - -mod container; -mod r#enum; -mod list; -mod scalar; - -pub use container::*; -pub use r#enum::*; -pub use list::*; -pub use scalar::*; diff --git a/src/resolver_utils/scalar.rs b/src/resolver_utils/scalar.rs deleted file mode 100644 index b699bd241..000000000 --- a/src/resolver_utils/scalar.rs +++ /dev/null @@ -1,278 +0,0 @@ -use crate::{InputValueResult, Value}; - -/// A GraphQL scalar. -/// -/// You can implement the trait to create a custom scalar. -/// -/// # Examples -/// -/// ```rust -/// use async_graphql::*; -/// -/// struct MyInt(i32); -/// -/// #[Scalar] -/// impl ScalarType for MyInt { -/// fn parse(value: Value) -> InputValueResult { -/// if let Value::Number(n) = &value { -/// if let Some(n) = n.as_i64() { -/// return Ok(MyInt(n as i32)); -/// } -/// } -/// Err(InputValueError::expected_type(value)) -/// } -/// -/// fn to_value(&self) -> Value { -/// Value::Number(self.0.into()) -/// } -/// } -/// ``` -pub trait ScalarType: Sized + Send { - /// Parse a scalar value. - fn parse(value: Value) -> InputValueResult; - - /// Checks for a valid scalar value. - /// - /// Implementing this function can find incorrect input values during the - /// verification phase, which can improve performance. - fn is_valid(_value: &Value) -> bool { - true - } - - /// Convert the scalar to `Value`. - fn to_value(&self) -> Value; -} - -/// Define a scalar -/// -/// If your type implemented `serde::Serialize` and `serde::Deserialize`, then -/// you can use this macro to define a scalar more simply. It helps you -/// implement the `ScalarType::parse` and `ScalarType::to_value` functions by -/// calling the [from_value](fn.from_value.html) and -/// [to_value](fn.to_value.html) functions. -/// -/// # Examples -/// -/// ```rust -/// use async_graphql::*; -/// use serde::{Serialize, Deserialize}; -/// use std::collections::HashMap; -/// -/// #[derive(Serialize, Deserialize)] -/// struct MyValue { -/// a: i32, -/// b: HashMap, -/// } -/// -/// scalar!(MyValue); -/// -/// // Rename to `MV`. -/// // scalar!(MyValue, "MV"); -/// -/// // Rename to `MV` and add description. -/// // scalar!(MyValue, "MV", "This is my value"); -/// -/// // Rename to `MV`, add description and specifiedByURL. -/// // scalar!(MyValue, "MV", "This is my value", "https://tools.ietf.org/html/rfc4122"); -/// -/// struct Query; -/// -/// #[Object] -/// impl Query { -/// async fn value(&self, input: MyValue) -> MyValue { -/// input -/// } -/// } -/// -/// # tokio::runtime::Runtime::new().unwrap().block_on(async move { -/// let schema = Schema::new(Query, EmptyMutation, EmptySubscription); -/// let res = schema.execute(r#"{ value(input: {a: 10, b: {v1: 1, v2: 2} }) }"#).await.into_result().unwrap().data; -/// assert_eq!(res, value!({ -/// "value": { -/// "a": 10, -/// "b": {"v1": 1, "v2": 2}, -/// } -/// })); -/// # }); -/// ``` -#[macro_export] -macro_rules! scalar { - ($ty:ty, $name:literal, $desc:literal, $specified_by_url:literal) => { - $crate::scalar_internal!( - $ty, - $name, - ::std::option::Option::Some(::std::string::ToString::to_string($desc)), - ::std::option::Option::Some(::std::string::ToString::to_string($specified_by_url)) - ); - }; - - ($ty:ty, $name:literal, $desc:literal) => { - $crate::scalar_internal!( - $ty, - $name, - ::std::option::Option::Some(::std::string::ToString::to_string($desc)), - ::std::option::Option::None - ); - }; - - ($ty:ty, $name:literal) => { - $crate::scalar_internal!( - $ty, - $name, - ::std::option::Option::None, - ::std::option::Option::None - ); - }; - - ($ty:ty) => { - $crate::scalar_internal!( - $ty, - ::std::stringify!($ty), - ::std::option::Option::None, - ::std::option::Option::None - ); - }; -} - -#[macro_export] -#[doc(hidden)] -macro_rules! scalar_internal { - ($ty:ty, $name:expr, $desc:expr, $specified_by_url:expr) => { - impl $crate::ScalarType for $ty { - fn parse(value: $crate::Value) -> $crate::InputValueResult { - ::std::result::Result::Ok($crate::from_value(value)?) - } - - fn to_value(&self) -> $crate::Value { - $crate::to_value(self).unwrap_or_else(|_| $crate::Value::Null) - } - } - - impl $crate::InputType for $ty { - type RawValueType = Self; - - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - ::std::borrow::Cow::Borrowed($name) - } - - fn create_type_info( - registry: &mut $crate::registry::Registry, - ) -> ::std::string::String { - registry.create_input_type::<$ty, _>($crate::registry::MetaTypeId::Scalar, |_| { - $crate::registry::MetaType::Scalar { - name: ::std::borrow::ToOwned::to_owned($name), - description: $desc, - is_valid: ::std::option::Option::Some(::std::sync::Arc::new(|value| { - <$ty as $crate::ScalarType>::is_valid(value) - })), - visible: ::std::option::Option::None, - inaccessible: false, - tags: ::std::default::Default::default(), - specified_by_url: $specified_by_url, - directive_invocations: ::std::vec::Vec::new(), - requires_scopes: ::std::vec::Vec::new(), - } - }) - } - - fn parse( - value: ::std::option::Option<$crate::Value>, - ) -> $crate::InputValueResult { - <$ty as $crate::ScalarType>::parse(value.unwrap_or_default()) - } - - fn to_value(&self) -> $crate::Value { - <$ty as $crate::ScalarType>::to_value(self) - } - - fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> { - ::std::option::Option::Some(self) - } - } - - $crate::scalar_internal_output!($ty, $name, $desc, $specified_by_url); - }; -} - -#[cfg(feature = "boxed-trait")] -#[macro_export] -#[doc(hidden)] -macro_rules! scalar_internal_output { - ($ty:ty, $name:expr, $desc:expr, $specified_by_url:expr) => { - #[$crate::async_trait::async_trait] - impl $crate::OutputType for $ty { - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - ::std::borrow::Cow::Borrowed($name) - } - - fn create_type_info( - registry: &mut $crate::registry::Registry, - ) -> ::std::string::String { - registry.create_output_type::<$ty, _>($crate::registry::MetaTypeId::Scalar, |_| { - $crate::registry::MetaType::Scalar { - name: ::std::borrow::ToOwned::to_owned($name), - description: $desc, - is_valid: ::std::option::Option::Some(::std::sync::Arc::new(|value| { - <$ty as $crate::ScalarType>::is_valid(value) - })), - visible: ::std::option::Option::None, - inaccessible: false, - tags: ::std::default::Default::default(), - specified_by_url: $specified_by_url, - directive_invocations: ::std::vec::Vec::new(), - requires_scopes: ::std::vec::Vec::new(), - } - }) - } - - async fn resolve( - &self, - _: &$crate::ContextSelectionSet<'_>, - _field: &$crate::Positioned<$crate::parser::types::Field>, - ) -> $crate::ServerResult<$crate::Value> { - ::std::result::Result::Ok($crate::ScalarType::to_value(self)) - } - } - }; -} - -#[cfg(not(feature = "boxed-trait"))] -#[macro_export] -#[doc(hidden)] -macro_rules! scalar_internal_output { - ($ty:ty, $name:expr, $desc:expr, $specified_by_url:expr) => { - impl $crate::OutputType for $ty { - fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { - ::std::borrow::Cow::Borrowed($name) - } - - fn create_type_info( - registry: &mut $crate::registry::Registry, - ) -> ::std::string::String { - registry.create_output_type::<$ty, _>($crate::registry::MetaTypeId::Scalar, |_| { - $crate::registry::MetaType::Scalar { - name: ::std::borrow::ToOwned::to_owned($name), - description: $desc, - is_valid: ::std::option::Option::Some(::std::sync::Arc::new(|value| { - <$ty as $crate::ScalarType>::is_valid(value) - })), - visible: ::std::option::Option::None, - inaccessible: false, - tags: ::std::default::Default::default(), - specified_by_url: $specified_by_url, - directive_invocations: ::std::vec::Vec::new(), - requires_scopes: ::std::vec::Vec::new(), - } - }) - } - - async fn resolve( - &self, - _: &$crate::ContextSelectionSet<'_>, - _field: &$crate::Positioned<$crate::parser::types::Field>, - ) -> $crate::ServerResult<$crate::Value> { - ::std::result::Result::Ok($crate::ScalarType::to_value(self)) - } - } - }; -} diff --git a/src/response.rs b/src/response.rs deleted file mode 100644 index 60956511c..000000000 --- a/src/response.rs +++ /dev/null @@ -1,193 +0,0 @@ -use std::collections::BTreeMap; - -use serde::{Deserialize, Serialize}; - -use crate::{CacheControl, Result, ServerError, Value}; - -/// Query response -#[non_exhaustive] -#[derive(Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct Response { - /// Data of query result - #[serde(default)] - pub data: Value, - - /// Extensions result - #[serde(skip_serializing_if = "BTreeMap::is_empty", default)] - pub extensions: BTreeMap, - - /// Cache control value - #[serde(skip)] - pub cache_control: CacheControl, - - /// Errors - #[serde(skip_serializing_if = "Vec::is_empty", default)] - pub errors: Vec, - - /// HTTP headers - #[serde(skip)] - pub http_headers: http::HeaderMap, -} - -impl Response { - /// Create a new successful response with the data. - #[must_use] - pub fn new(data: impl Into) -> Self { - Self { - data: data.into(), - ..Default::default() - } - } - - /// Create a response from some errors. - #[must_use] - pub fn from_errors(errors: Vec) -> Self { - Self { - errors, - ..Default::default() - } - } - - /// Set the extension result of the response. - #[must_use] - pub fn extension(mut self, name: impl Into, value: Value) -> Self { - self.extensions.insert(name.into(), value); - self - } - - /// Set the http headers of the response. - #[must_use] - pub fn http_headers(self, http_headers: http::HeaderMap) -> Self { - Self { - http_headers, - ..self - } - } - - /// Set the cache control of the response. - #[must_use] - pub fn cache_control(self, cache_control: CacheControl) -> Self { - Self { - cache_control, - ..self - } - } - - /// Returns `true` if the response is ok. - #[inline] - pub fn is_ok(&self) -> bool { - self.errors.is_empty() - } - - /// Returns `true` if the response is error. - #[inline] - pub fn is_err(&self) -> bool { - !self.is_ok() - } - - /// Extract the error from the response. Only if the `error` field is empty - /// will this return `Ok`. - #[inline] - pub fn into_result(self) -> Result> { - if self.is_err() { - Err(self.errors) - } else { - Ok(self) - } - } -} - -/// Response for batchable queries -#[allow(clippy::large_enum_variant)] -#[derive(Debug, Serialize)] -#[serde(untagged)] -pub enum BatchResponse { - /// Response for single queries - Single(Response), - - /// Response for batch queries - Batch(Vec), -} - -impl BatchResponse { - /// Gets cache control value - pub fn cache_control(&self) -> CacheControl { - match self { - BatchResponse::Single(resp) => resp.cache_control, - BatchResponse::Batch(resp) => resp.iter().fold(CacheControl::default(), |acc, item| { - acc.merge(&item.cache_control) - }), - } - } - - /// Returns `true` if all responses are ok. - pub fn is_ok(&self) -> bool { - match self { - BatchResponse::Single(resp) => resp.is_ok(), - BatchResponse::Batch(resp) => resp.iter().all(Response::is_ok), - } - } - - /// Returns HTTP headers map. - pub fn http_headers(&self) -> http::HeaderMap { - match self { - BatchResponse::Single(resp) => resp.http_headers.clone(), - BatchResponse::Batch(resp) => { - resp.iter().fold(http::HeaderMap::new(), |mut acc, resp| { - acc.extend(resp.http_headers.clone()); - acc - }) - } - } - } - - /// Returns HTTP headers iterator. - pub fn http_headers_iter(&self) -> impl Iterator { - let headers = self.http_headers(); - - let mut current_name = None; - headers.into_iter().filter_map(move |(name, value)| { - if let Some(name) = name { - current_name = Some(name); - } - current_name - .clone() - .map(|current_name| (current_name, value)) - }) - } -} - -impl From for BatchResponse { - fn from(response: Response) -> Self { - Self::Single(response) - } -} - -impl From> for BatchResponse { - fn from(responses: Vec) -> Self { - Self::Batch(responses) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_batch_response_single() { - let resp = BatchResponse::Single(Response::new(Value::Boolean(true))); - assert_eq!(serde_json::to_string(&resp).unwrap(), r#"{"data":true}"#); - } - - #[test] - fn test_batch_response_batch() { - let resp = BatchResponse::Batch(vec![ - Response::new(Value::Boolean(true)), - Response::new(Value::String("1".to_string())), - ]); - assert_eq!( - serde_json::to_string(&resp).unwrap(), - r#"[{"data":true},{"data":"1"}]"# - ); - } -} diff --git a/src/schema.rs b/src/schema.rs deleted file mode 100644 index a0a7d9467..000000000 --- a/src/schema.rs +++ /dev/null @@ -1,932 +0,0 @@ -use std::{ - any::Any, - collections::{HashMap, HashSet}, - ops::Deref, - sync::Arc, -}; - -use async_graphql_parser::types::ExecutableDocument; -use futures_util::stream::{self, BoxStream, FuturesOrdered, Stream, StreamExt}; - -use crate::{ - BatchRequest, BatchResponse, CacheControl, ContextBase, EmptyMutation, EmptySubscription, - Executor, InputType, ObjectType, OutputType, QueryEnv, Request, Response, ServerError, - ServerResult, SubscriptionType, Variables, - context::{Data, QueryEnvInner}, - custom_directive::CustomDirectiveFactory, - extensions::{ExtensionFactory, Extensions}, - parser::{ - Positioned, parse_query, - types::{Directive, DocumentOperations, OperationType, Selection, SelectionSet}, - }, - registry::{Registry, SDLExportOptions}, - resolver_utils::{resolve_container, resolve_container_serial}, - subscription::collect_subscription_streams, - types::QueryRoot, - validation::{ValidationMode, check_rules}, -}; - -/// Introspection mode -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] -pub enum IntrospectionMode { - /// Introspection only - IntrospectionOnly, - /// Enables introspection - #[default] - Enabled, - /// Disables introspection - Disabled, -} - -/// Schema builder -pub struct SchemaBuilder { - validation_mode: ValidationMode, - query: QueryRoot, - mutation: Mutation, - subscription: Subscription, - registry: Registry, - data: Data, - complexity: Option, - depth: Option, - recursive_depth: usize, - max_directives: Option, - extensions: Vec>, - custom_directives: HashMap>, -} - -impl SchemaBuilder { - /// Manually register a input type in the schema. - /// - /// You can use this function to register schema types that are not directly - /// referenced. - #[must_use] - pub fn register_input_type(mut self) -> Self { - T::create_type_info(&mut self.registry); - self - } - - /// Manually register a output type in the schema. - /// - /// You can use this function to register schema types that are not directly - /// referenced. - #[must_use] - pub fn register_output_type(mut self) -> Self { - T::create_type_info(&mut self.registry); - self - } - - /// Disable introspection queries. - #[must_use] - pub fn disable_introspection(mut self) -> Self { - self.registry.introspection_mode = IntrospectionMode::Disabled; - self - } - - /// Only process introspection queries, everything else is processed as an - /// error. - #[must_use] - pub fn introspection_only(mut self) -> Self { - self.registry.introspection_mode = IntrospectionMode::IntrospectionOnly; - self - } - - /// Set the maximum complexity a query can have. By default, there is no - /// limit. - #[must_use] - pub fn limit_complexity(mut self, complexity: usize) -> Self { - self.complexity = Some(complexity); - self - } - - /// Set the maximum depth a query can have. By default, there is no limit. - #[must_use] - pub fn limit_depth(mut self, depth: usize) -> Self { - self.depth = Some(depth); - self - } - - /// Set the maximum recursive depth a query can have. (default: 32) - /// - /// If the value is too large, stack overflow may occur, usually `32` is - /// enough. - #[must_use] - pub fn limit_recursive_depth(mut self, depth: usize) -> Self { - self.recursive_depth = depth; - self - } - - /// Set the maximum number of directives on a single field. (default: no - /// limit) - pub fn limit_directives(mut self, max_directives: usize) -> Self { - self.max_directives = Some(max_directives); - self - } - - /// Add an extension to the schema. - /// - /// # Examples - /// - /// ```rust - /// use async_graphql::*; - /// - /// struct Query; - /// - /// #[Object] - /// impl Query { - /// async fn value(&self) -> i32 { - /// 100 - /// } - /// } - /// - /// let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - /// .extension(extensions::Logger) - /// .finish(); - /// ``` - #[must_use] - pub fn extension(mut self, extension: impl ExtensionFactory) -> Self { - self.extensions.push(Box::new(extension)); - self - } - - /// Add a global data that can be accessed in the `Schema`. You access it - /// with `Context::data`. - #[must_use] - pub fn data(mut self, data: D) -> Self { - self.data.insert(data); - self - } - - /// Set the validation mode, default is `ValidationMode::Strict`. - #[must_use] - pub fn validation_mode(mut self, validation_mode: ValidationMode) -> Self { - self.validation_mode = validation_mode; - self - } - - /// Enable federation, which is automatically enabled if the Query has least - /// one entity definition. - #[must_use] - pub fn enable_federation(mut self) -> Self { - self.registry.enable_federation = true; - self - } - - /// Make the Federation SDL include subscriptions. - /// - /// Note: Not included by default, in order to be compatible with Apollo - /// Server. - #[must_use] - pub fn enable_subscription_in_federation(mut self) -> Self { - self.registry.federation_subscription = true; - self - } - - /// Override the name of the specified input type. - #[must_use] - pub fn override_input_type_description(mut self, desc: &'static str) -> Self { - self.registry.set_description(&*T::type_name(), desc); - self - } - - /// Override the name of the specified output type. - #[must_use] - pub fn override_output_type_description(mut self, desc: &'static str) -> Self { - self.registry.set_description(&*T::type_name(), desc); - self - } - - /// Register a custom directive. - /// - /// # Panics - /// - /// Panics if the directive with the same name is already registered. - #[must_use] - pub fn directive(mut self, directive: T) -> Self { - let name = directive.name(); - let instance = Box::new(directive); - - instance.register(&mut self.registry); - - if name == "skip" - || name == "include" - || self - .custom_directives - .insert(name.clone().into(), instance) - .is_some() - { - panic!("Directive `{}` already exists", name); - } - - self - } - - /// Disable field suggestions. - #[must_use] - pub fn disable_suggestions(mut self) -> Self { - self.registry.enable_suggestions = false; - self - } - - /// Make all fields sorted on introspection queries. - pub fn with_sorted_fields(mut self) -> Self { - use crate::registry::MetaType; - for ty in self.registry.types.values_mut() { - match ty { - MetaType::Object { fields, .. } | MetaType::Interface { fields, .. } => { - fields.sort_keys(); - } - MetaType::InputObject { input_fields, .. } => { - input_fields.sort_keys(); - } - MetaType::Scalar { .. } | MetaType::Enum { .. } | MetaType::Union { .. } => { - // have no fields - } - } - } - self - } - - /// Make all enum variants sorted on introspection queries. - pub fn with_sorted_enums(mut self) -> Self { - use crate::registry::MetaType; - for ty in &mut self.registry.types.values_mut() { - if let MetaType::Enum { enum_values, .. } = ty { - enum_values.sort_keys(); - } - } - self - } - - /// Consumes this builder and returns a schema. - pub fn finish(mut self) -> Schema { - // federation - if self.registry.enable_federation || self.registry.has_entities() { - self.registry.create_federation_types(); - } - - Schema(Arc::new(SchemaInner { - validation_mode: self.validation_mode, - query: self.query, - mutation: self.mutation, - subscription: self.subscription, - complexity: self.complexity, - depth: self.depth, - recursive_depth: self.recursive_depth, - max_directives: self.max_directives, - extensions: self.extensions, - env: SchemaEnv(Arc::new(SchemaEnvInner { - registry: self.registry, - data: self.data, - custom_directives: self.custom_directives, - })), - })) - } -} - -#[doc(hidden)] -pub struct SchemaEnvInner { - pub registry: Registry, - pub data: Data, - pub custom_directives: HashMap>, -} - -#[doc(hidden)] -#[derive(Clone)] -pub struct SchemaEnv(pub(crate) Arc); - -impl Deref for SchemaEnv { - type Target = SchemaEnvInner; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[doc(hidden)] -pub struct SchemaInner { - pub(crate) validation_mode: ValidationMode, - pub(crate) query: QueryRoot, - pub(crate) mutation: Mutation, - pub(crate) subscription: Subscription, - pub(crate) complexity: Option, - pub(crate) depth: Option, - pub(crate) recursive_depth: usize, - pub(crate) max_directives: Option, - pub(crate) extensions: Vec>, - pub(crate) env: SchemaEnv, -} - -/// GraphQL schema. -/// -/// Cloning a schema is cheap, so it can be easily shared. -pub struct Schema( - pub(crate) Arc>, -); - -impl Clone for Schema { - fn clone(&self) -> Self { - Schema(self.0.clone()) - } -} - -impl Default for Schema -where - Query: Default + ObjectType + 'static, - Mutation: Default + ObjectType + 'static, - Subscription: Default + SubscriptionType + 'static, -{ - fn default() -> Self { - Schema::new( - Query::default(), - Mutation::default(), - Subscription::default(), - ) - } -} - -impl Schema -where - Query: ObjectType + 'static, - Mutation: ObjectType + 'static, - Subscription: SubscriptionType + 'static, -{ - /// Create a schema builder - /// - /// The root object for the query and Mutation needs to be specified. - /// If there is no mutation, you can use `EmptyMutation`. - /// If there is no subscription, you can use `EmptySubscription`. - pub fn build( - query: Query, - mutation: Mutation, - subscription: Subscription, - ) -> SchemaBuilder { - Self::build_with_ignore_name_conflicts(query, mutation, subscription, [] as [&str; 0]) - } - - /// Create a schema builder and specifies a list to ignore type conflict - /// detection. - /// - /// NOTE: It is not recommended to use it unless you know what it does. - #[must_use] - pub fn build_with_ignore_name_conflicts( - query: Query, - mutation: Mutation, - subscription: Subscription, - ignore_name_conflicts: I, - ) -> SchemaBuilder - where - I: IntoIterator, - T: Into, - { - SchemaBuilder { - validation_mode: ValidationMode::Strict, - query: QueryRoot { inner: query }, - mutation, - subscription, - registry: Self::create_registry( - ignore_name_conflicts.into_iter().map(Into::into).collect(), - ), - data: Default::default(), - complexity: None, - depth: None, - recursive_depth: 32, - max_directives: None, - extensions: Default::default(), - custom_directives: Default::default(), - } - } - - pub(crate) fn create_registry(ignore_name_conflicts: HashSet) -> Registry { - let mut registry = Registry { - types: Default::default(), - directives: Default::default(), - implements: Default::default(), - query_type: Query::type_name().to_string(), - mutation_type: if Mutation::is_empty() { - None - } else { - Some(Mutation::type_name().to_string()) - }, - subscription_type: if Subscription::is_empty() { - None - } else { - Some(Subscription::type_name().to_string()) - }, - introspection_mode: IntrospectionMode::Enabled, - enable_federation: false, - federation_subscription: false, - ignore_name_conflicts, - enable_suggestions: true, - }; - registry.add_system_types(); - - QueryRoot::::create_type_info(&mut registry); - if !Mutation::is_empty() { - Mutation::create_type_info(&mut registry); - } - if !Subscription::is_empty() { - Subscription::create_type_info(&mut registry); - } - - registry.remove_unused_types(); - registry - } - - /// Create a schema - pub fn new( - query: Query, - mutation: Mutation, - subscription: Subscription, - ) -> Schema { - Self::build(query, mutation, subscription).finish() - } - - #[inline] - #[allow(unused)] - pub(crate) fn registry(&self) -> &Registry { - &self.0.env.registry - } - - /// Returns SDL(Schema Definition Language) of this schema. - pub fn sdl(&self) -> String { - self.0.env.registry.export_sdl(Default::default()) - } - - /// Returns SDL(Schema Definition Language) of this schema with options. - pub fn sdl_with_options(&self, options: SDLExportOptions) -> String { - self.0.env.registry.export_sdl(options) - } - - /// Get all names in this schema - /// - /// Maybe you want to serialize a custom binary protocol. In order to - /// minimize message size, a dictionary is usually used to compress type - /// names, field names, directive names, and parameter names. This function - /// gets all the names, so you can create this dictionary. - pub fn names(&self) -> Vec { - self.0.env.registry.names() - } - - fn create_extensions(&self, session_data: Arc) -> Extensions { - Extensions::new( - self.0.extensions.iter().map(|f| f.create()), - self.0.env.clone(), - session_data, - ) - } - - async fn execute_once(&self, env: QueryEnv, execute_data: Option<&Data>) -> Response { - // execute - let ctx = ContextBase { - path_node: None, - is_for_introspection: false, - item: &env.operation.node.selection_set, - schema_env: &self.0.env, - query_env: &env, - execute_data, - }; - - let res = match &env.operation.node.ty { - OperationType::Query => resolve_container(&ctx, &self.0.query).await, - OperationType::Mutation => { - if self.0.env.registry.introspection_mode == IntrospectionMode::IntrospectionOnly - || env.introspection_mode == IntrospectionMode::IntrospectionOnly - { - resolve_container_serial(&ctx, &EmptyMutation).await - } else { - resolve_container_serial(&ctx, &self.0.mutation).await - } - } - OperationType::Subscription => Err(ServerError::new( - "Subscriptions are not supported on this transport.", - None, - )), - }; - - let mut resp = match res { - Ok(value) => Response::new(value), - Err(err) => Response::from_errors(vec![err]), - } - .http_headers(std::mem::take(&mut *env.http_headers.lock().unwrap())); - - resp.errors - .extend(std::mem::take(&mut *env.errors.lock().unwrap())); - resp - } - - /// Execute a GraphQL query. - pub async fn execute(&self, request: impl Into) -> Response { - let request = request.into(); - let extensions = self.create_extensions(Default::default()); - let request_fut = { - let extensions = extensions.clone(); - async move { - match prepare_request( - extensions, - request, - Default::default(), - &self.0.env.registry, - self.0.validation_mode, - self.0.recursive_depth, - self.0.max_directives, - self.0.complexity, - self.0.depth, - ) - .await - { - Ok((env, cache_control)) => { - let f = |execute_data: Option| { - let env = env.clone(); - async move { - self.execute_once(env, execute_data.as_ref()) - .await - .cache_control(cache_control) - } - }; - env.extensions - .execute(env.operation_name.as_deref(), f) - .await - } - Err(errors) => Response::from_errors(errors), - } - } - }; - futures_util::pin_mut!(request_fut); - extensions.request(&mut request_fut).await - } - - /// Execute a GraphQL batch query. - pub async fn execute_batch(&self, batch_request: BatchRequest) -> BatchResponse { - match batch_request { - BatchRequest::Single(request) => BatchResponse::Single(self.execute(request).await), - BatchRequest::Batch(requests) => BatchResponse::Batch( - FuturesOrdered::from_iter( - requests.into_iter().map(|request| self.execute(request)), - ) - .collect() - .await, - ), - } - } - - /// Execute a GraphQL subscription with session data. - pub fn execute_stream_with_session_data( - &self, - request: impl Into, - session_data: Arc, - ) -> impl Stream + Send + Unpin + 'static { - let schema = self.clone(); - let request = request.into(); - let extensions = self.create_extensions(session_data.clone()); - - let stream = futures_util::stream::StreamExt::boxed({ - let extensions = extensions.clone(); - let env = self.0.env.clone(); - async_stream::stream! { - let (env, cache_control) = match prepare_request( - extensions, request, session_data, &env.registry, - schema.0.validation_mode, schema.0.recursive_depth, - schema.0.max_directives, schema.0.complexity, schema.0.depth - ).await { - Ok(res) => res, - Err(errors) => { - yield Response::from_errors(errors); - return; - } - }; - - if env.operation.node.ty != OperationType::Subscription { - let f = |execute_data: Option| { - let env = env.clone(); - let schema = schema.clone(); - async move { - schema.execute_once(env, execute_data.as_ref()) - .await - .cache_control(cache_control) - } - }; - yield env.extensions - .execute(env.operation_name.as_deref(), f) - .await - .cache_control(cache_control); - return; - } - - let ctx = env.create_context( - &schema.0.env, - None, - &env.operation.node.selection_set, - None, - ); - - let mut streams = Vec::new(); - let collect_result = if schema.0.env.registry.introspection_mode - == IntrospectionMode::IntrospectionOnly - || env.introspection_mode == IntrospectionMode::IntrospectionOnly - { - collect_subscription_streams(&ctx, &EmptySubscription, &mut streams) - } else { - collect_subscription_streams(&ctx, &schema.0.subscription, &mut streams) - }; - if let Err(err) = collect_result { - yield Response::from_errors(vec![err]); - } - - let mut stream = stream::select_all(streams); - while let Some(resp) = stream.next().await { - yield resp; - } - } - }); - extensions.subscribe(stream) - } - - /// Execute a GraphQL subscription. - pub fn execute_stream( - &self, - request: impl Into, - ) -> impl Stream + Send + Unpin { - self.execute_stream_with_session_data(request, Default::default()) - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl Executor for Schema -where - Query: ObjectType + 'static, - Mutation: ObjectType + 'static, - Subscription: SubscriptionType + 'static, -{ - async fn execute(&self, request: Request) -> Response { - Schema::execute(self, request).await - } - - fn execute_stream( - &self, - request: Request, - session_data: Option>, - ) -> BoxStream<'static, Response> { - Schema::execute_stream_with_session_data(&self, request, session_data.unwrap_or_default()) - .boxed() - } -} - -fn check_max_directives(doc: &ExecutableDocument, max_directives: usize) -> ServerResult<()> { - fn check_selection_set( - doc: &ExecutableDocument, - selection_set: &Positioned, - limit_directives: usize, - ) -> ServerResult<()> { - for selection in &selection_set.node.items { - match &selection.node { - Selection::Field(field) => { - if field.node.directives.len() > limit_directives { - return Err(ServerError::new( - format!( - "The number of directives on the field `{}` cannot be greater than `{}`", - field.node.name.node, limit_directives - ), - Some(field.pos), - )); - } - check_selection_set(doc, &field.node.selection_set, limit_directives)?; - } - Selection::FragmentSpread(fragment_spread) => { - if let Some(fragment) = - doc.fragments.get(&fragment_spread.node.fragment_name.node) - { - check_selection_set(doc, &fragment.node.selection_set, limit_directives)?; - } - } - Selection::InlineFragment(inline_fragment) => { - check_selection_set( - doc, - &inline_fragment.node.selection_set, - limit_directives, - )?; - } - } - } - - Ok(()) - } - - for (_, operation) in doc.operations.iter() { - check_selection_set(doc, &operation.node.selection_set, max_directives)?; - } - - Ok(()) -} - -fn check_recursive_depth(doc: &ExecutableDocument, max_depth: usize) -> ServerResult<()> { - fn check_selection_set( - doc: &ExecutableDocument, - selection_set: &Positioned, - current_depth: usize, - max_depth: usize, - ) -> ServerResult<()> { - if current_depth > max_depth { - return Err(ServerError::new( - format!( - "The recursion depth of the query cannot be greater than `{}`", - max_depth - ), - Some(selection_set.pos), - )); - } - - for selection in &selection_set.node.items { - match &selection.node { - Selection::Field(field) => { - if !field.node.selection_set.node.items.is_empty() { - check_selection_set( - doc, - &field.node.selection_set, - current_depth + 1, - max_depth, - )?; - } - } - Selection::FragmentSpread(fragment_spread) => { - if let Some(fragment) = - doc.fragments.get(&fragment_spread.node.fragment_name.node) - { - check_selection_set( - doc, - &fragment.node.selection_set, - current_depth + 1, - max_depth, - )?; - } - } - Selection::InlineFragment(inline_fragment) => { - check_selection_set( - doc, - &inline_fragment.node.selection_set, - current_depth + 1, - max_depth, - )?; - } - } - } - - Ok(()) - } - - for (_, operation) in doc.operations.iter() { - check_selection_set(doc, &operation.node.selection_set, 0, max_depth)?; - } - - Ok(()) -} - -fn remove_skipped_selection(selection_set: &mut SelectionSet, variables: &Variables) { - fn is_skipped(directives: &[Positioned], variables: &Variables) -> bool { - for directive in directives { - let include = match &*directive.node.name.node { - "skip" => false, - "include" => true, - _ => continue, - }; - - if let Some(condition_input) = directive.node.get_argument("if") { - let value = condition_input - .node - .clone() - .into_const_with(|name| variables.get(&name).cloned().ok_or(())) - .unwrap_or_default(); - let value: bool = InputType::parse(Some(value)).unwrap_or_default(); - if include != value { - return true; - } - } - } - - false - } - - selection_set - .items - .retain(|selection| !is_skipped(selection.node.directives(), variables)); - - for selection in &mut selection_set.items { - selection.node.directives_mut().retain(|directive| { - directive.node.name.node != "skip" && directive.node.name.node != "include" - }); - } - - for selection in &mut selection_set.items { - match &mut selection.node { - Selection::Field(field) => { - remove_skipped_selection(&mut field.node.selection_set.node, variables); - } - Selection::FragmentSpread(_) => {} - Selection::InlineFragment(inline_fragment) => { - remove_skipped_selection(&mut inline_fragment.node.selection_set.node, variables); - } - } - } -} - -#[allow(clippy::too_many_arguments)] -pub(crate) async fn prepare_request( - mut extensions: Extensions, - request: Request, - session_data: Arc, - registry: &Registry, - validation_mode: ValidationMode, - recursive_depth: usize, - max_directives: Option, - complexity: Option, - depth: Option, -) -> Result<(QueryEnv, CacheControl), Vec> { - let mut request = extensions.prepare_request(request).await?; - let query_data = Arc::new(std::mem::take(&mut request.data)); - extensions.attach_query_data(query_data.clone()); - - let mut document = { - let query = &request.query; - let parsed_doc = request.parsed_query.take(); - let fut_parse = async move { - let doc = match parsed_doc { - Some(parsed_doc) => parsed_doc, - None => parse_query(query)?, - }; - check_recursive_depth(&doc, recursive_depth)?; - if let Some(max_directives) = max_directives { - check_max_directives(&doc, max_directives)?; - } - Ok(doc) - }; - futures_util::pin_mut!(fut_parse); - extensions - .parse_query(query, &request.variables, &mut fut_parse) - .await? - }; - - // check rules - let validation_result = { - let validation_fut = async { - check_rules( - registry, - &document, - Some(&request.variables), - validation_mode, - complexity, - depth, - ) - }; - futures_util::pin_mut!(validation_fut); - extensions.validation(&mut validation_fut).await? - }; - - let operation = if let Some(operation_name) = &request.operation_name { - match document.operations { - DocumentOperations::Single(_) => None, - DocumentOperations::Multiple(mut operations) => operations - .remove(operation_name.as_str()) - .map(|operation| (Some(operation_name.clone()), operation)), - } - .ok_or_else(|| { - ServerError::new( - format!(r#"Unknown operation named "{}""#, operation_name), - None, - ) - }) - } else { - match document.operations { - DocumentOperations::Single(operation) => Ok((None, operation)), - DocumentOperations::Multiple(map) if map.len() == 1 => { - let (operation_name, operation) = map.into_iter().next().unwrap(); - Ok((Some(operation_name.to_string()), operation)) - } - DocumentOperations::Multiple(_) => Err(ServerError::new( - "Operation name required in request.", - None, - )), - } - }; - - let (operation_name, mut operation) = operation.map_err(|err| vec![err])?; - - // remove skipped fields - for fragment in document.fragments.values_mut() { - remove_skipped_selection(&mut fragment.node.selection_set.node, &request.variables); - } - remove_skipped_selection(&mut operation.node.selection_set.node, &request.variables); - - let env = QueryEnvInner { - extensions, - variables: request.variables, - operation_name, - operation, - fragments: document.fragments, - uploads: request.uploads, - session_data, - query_data, - http_headers: Default::default(), - introspection_mode: request.introspection_mode, - errors: Default::default(), - }; - Ok((QueryEnv::new(env), validation_result.cache_control)) -} diff --git a/src/subscription.rs b/src/subscription.rs deleted file mode 100644 index b70295ecd..000000000 --- a/src/subscription.rs +++ /dev/null @@ -1,82 +0,0 @@ -use std::{borrow::Cow, pin::Pin}; - -use futures_util::stream::{Stream, StreamExt}; - -use crate::{ - Context, ContextSelectionSet, PathSegment, Response, ServerError, ServerResult, - parser::types::Selection, registry, registry::Registry, -}; - -/// A GraphQL subscription object -pub trait SubscriptionType: Send + Sync { - /// Type the name. - fn type_name() -> Cow<'static, str>; - - /// Qualified typename. - fn qualified_type_name() -> String { - format!("{}!", Self::type_name()) - } - - /// Create type information in the registry and return qualified typename. - fn create_type_info(registry: &mut registry::Registry) -> String; - - /// This function returns true of type `EmptySubscription` only. - #[doc(hidden)] - fn is_empty() -> bool { - false - } - - #[doc(hidden)] - fn create_field_stream<'a>( - &'a self, - ctx: &'a Context<'_>, - ) -> Option + Send + 'a>>>; -} - -pub(crate) type BoxFieldStream<'a> = Pin + 'a + Send>>; - -pub(crate) fn collect_subscription_streams<'a, T: SubscriptionType + 'static>( - ctx: &ContextSelectionSet<'a>, - root: &'a T, - streams: &mut Vec>, -) -> ServerResult<()> { - for selection in &ctx.item.node.items { - if let Selection::Field(field) = &selection.node { - streams.push(Box::pin({ - let ctx = ctx.clone(); - async_stream::stream! { - let ctx = ctx.with_field(field); - let field_name = ctx.item.node.response_key().node.clone(); - let stream = root.create_field_stream(&ctx); - if let Some(mut stream) = stream { - while let Some(resp) = stream.next().await { - yield resp; - } - } else { - let err = ServerError::new(format!(r#"Cannot query field "{}" on type "{}"."#, field_name, T::type_name()), Some(ctx.item.pos)) - .with_path(vec![PathSegment::Field(field_name.to_string())]); - yield Response::from_errors(vec![err]); - } - } - })) - } - } - Ok(()) -} - -impl SubscriptionType for &T { - fn type_name() -> Cow<'static, str> { - T::type_name() - } - - fn create_type_info(registry: &mut Registry) -> String { - T::create_type_info(registry) - } - - fn create_field_stream<'a>( - &'a self, - ctx: &'a Context<'_>, - ) -> Option + Send + 'a>>> { - T::create_field_stream(*self, ctx) - } -} diff --git a/src/types/any.rs b/src/types/any.rs deleted file mode 100644 index 5f9189baf..000000000 --- a/src/types/any.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::{InputValueResult, Scalar, ScalarType, Value}; - -/// Any scalar (For [Apollo Federation](https://www.apollographql.com/docs/apollo-server/federation/introduction)) -/// -/// The `Any` scalar is used to pass representations of entities from external -/// services into the root `_entities` field for execution. -#[derive(Clone, Eq, PartialEq, Debug)] -pub struct Any(pub Value); - -/// The `_Any` scalar is used to pass representations of entities from external -/// services into the root `_entities` field for execution. -#[Scalar(internal, name = "_Any")] -impl ScalarType for Any { - fn parse(value: Value) -> InputValueResult { - Ok(Self(value)) - } - - fn is_valid(_value: &Value) -> bool { - true - } - - fn to_value(&self) -> Value { - self.0.clone() - } -} - -impl> From for Any { - fn from(value: T) -> Any { - Any(value.into()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_conversion_ok() { - let value = Value::List(vec![ - Value::Number(1.into()), - Value::Boolean(true), - Value::Null, - ]); - let expected = Any(value.clone()); - let output: Any = value.into(); - assert_eq!(output, expected); - } -} diff --git a/src/types/connection/connection_type.rs b/src/types/connection/connection_type.rs deleted file mode 100644 index d3f5bf002..000000000 --- a/src/types/connection/connection_type.rs +++ /dev/null @@ -1,189 +0,0 @@ -use std::{borrow::Cow, marker::PhantomData}; - -use super::{DisableNodesField, EnableNodesField, NodesFieldSwitcherSealed}; -use crate::{ - Object, ObjectType, OutputType, TypeName, - connection::{ - ConnectionNameType, DefaultConnectionName, DefaultEdgeName, EdgeNameType, PageInfo, - edge::Edge, - }, - types::connection::{CursorType, EmptyFields}, -}; - -/// Connection type -/// -/// Connection is the result of a query for `connection::query`. -pub struct Connection< - Cursor, - Node, - ConnectionFields = EmptyFields, - EdgeFields = EmptyFields, - Name = DefaultConnectionName, - EdgeName = DefaultEdgeName, - NodesField = EnableNodesField, -> where - Cursor: CursorType + Send + Sync, - Node: OutputType, - ConnectionFields: ObjectType, - EdgeFields: ObjectType, - Name: ConnectionNameType, - EdgeName: EdgeNameType, - NodesField: NodesFieldSwitcherSealed, -{ - _mark1: PhantomData, - _mark2: PhantomData, - _mark3: PhantomData, - /// All edges of the current page. - pub edges: Vec>, - /// Additional fields for connection object. - pub additional_fields: ConnectionFields, - /// If `true` means has previous page. - pub has_previous_page: bool, - /// If `true` means has next page. - pub has_next_page: bool, -} - -impl - Connection -where - Cursor: CursorType + Send + Sync, - Node: OutputType, - EdgeFields: ObjectType, - Name: ConnectionNameType, - EdgeName: EdgeNameType, - NodesField: NodesFieldSwitcherSealed, -{ - /// Create a new connection. - #[inline] - pub fn new(has_previous_page: bool, has_next_page: bool) -> Self { - Connection { - _mark1: PhantomData, - _mark2: PhantomData, - _mark3: PhantomData, - additional_fields: EmptyFields, - has_previous_page, - has_next_page, - edges: Vec::new(), - } - } -} - -impl - Connection -where - Cursor: CursorType + Send + Sync, - Node: OutputType, - ConnectionFields: ObjectType, - EdgeFields: ObjectType, - Name: ConnectionNameType, - EdgeName: EdgeNameType, - NodesField: NodesFieldSwitcherSealed, -{ - /// Create a new connection, it can have some additional fields. - #[inline] - pub fn with_additional_fields( - has_previous_page: bool, - has_next_page: bool, - additional_fields: ConnectionFields, - ) -> Self { - Connection { - _mark1: PhantomData, - _mark2: PhantomData, - _mark3: PhantomData, - additional_fields, - has_previous_page, - has_next_page, - edges: Vec::new(), - } - } -} - -#[Object(internal, name_type, shareable)] -impl - Connection -where - Cursor: CursorType + Send + Sync, - Node: OutputType, - ConnectionFields: ObjectType, - EdgeFields: ObjectType, - Name: ConnectionNameType, - EdgeName: EdgeNameType, -{ - /// Information to aid in pagination. - async fn page_info(&self) -> PageInfo { - PageInfo { - has_previous_page: self.has_previous_page, - has_next_page: self.has_next_page, - start_cursor: self.edges.first().map(|edge| edge.cursor.encode_cursor()), - end_cursor: self.edges.last().map(|edge| edge.cursor.encode_cursor()), - } - } - - /// A list of edges. - #[inline] - async fn edges(&self) -> &[Edge] { - &self.edges - } - - #[graphql(flatten)] - #[inline] - async fn additional_fields(&self) -> &ConnectionFields { - &self.additional_fields - } -} - -#[Object(internal, name_type, shareable)] -impl - Connection -where - Cursor: CursorType + Send + Sync, - Node: OutputType, - ConnectionFields: ObjectType, - EdgeFields: ObjectType, - Name: ConnectionNameType, - EdgeName: EdgeNameType, -{ - /// Information to aid in pagination. - async fn page_info(&self) -> PageInfo { - PageInfo { - has_previous_page: self.has_previous_page, - has_next_page: self.has_next_page, - start_cursor: self.edges.first().map(|edge| edge.cursor.encode_cursor()), - end_cursor: self.edges.last().map(|edge| edge.cursor.encode_cursor()), - } - } - - /// A list of edges. - #[inline] - async fn edges(&self) -> &[Edge] { - &self.edges - } - - /// A list of nodes. - async fn nodes(&self) -> Vec<&Node> { - self.edges.iter().map(|e| &e.node).collect() - } - - #[graphql(flatten)] - #[inline] - async fn additional_fields(&self) -> &ConnectionFields { - &self.additional_fields - } -} - -impl TypeName - for Connection -where - Cursor: CursorType + Send + Sync, - Node: OutputType, - ConnectionFields: ObjectType, - EdgeFields: ObjectType, - Name: ConnectionNameType, - EdgeName: EdgeNameType, - NodesField: NodesFieldSwitcherSealed, -{ - #[inline] - fn type_name() -> Cow<'static, str> { - Name::type_name::().into() - } -} diff --git a/src/types/connection/cursor.rs b/src/types/connection/cursor.rs deleted file mode 100644 index 3a461f45a..000000000 --- a/src/types/connection/cursor.rs +++ /dev/null @@ -1,183 +0,0 @@ -use std::{ - char::ParseCharError, - convert::Infallible, - fmt::Display, - num::{ParseFloatError, ParseIntError}, - ops::{Deref, DerefMut}, - str::ParseBoolError, -}; - -use serde::{Serialize, de::DeserializeOwned}; - -use crate::ID; - -/// Cursor type -/// -/// A custom scalar that serializes as a string. -/// -pub trait CursorType: Sized { - /// Error type for `decode_cursor`. - type Error: Display; - - /// Decode cursor from string. - fn decode_cursor(s: &str) -> Result; - - /// Encode cursor to string. - fn encode_cursor(&self) -> String; -} - -macro_rules! cursor_type_int_impl { - ($($t:ty)*) => {$( - impl CursorType for $t { - type Error = ParseIntError; - - fn decode_cursor(s: &str) -> Result { - s.parse() - } - - fn encode_cursor(&self) -> String { - self.to_string() - } - } - )*} -} - -cursor_type_int_impl! { isize i8 i16 i32 i64 i128 usize u8 u16 u32 u64 u128 } - -impl CursorType for f32 { - type Error = ParseFloatError; - - fn decode_cursor(s: &str) -> Result { - s.parse() - } - - fn encode_cursor(&self) -> String { - self.to_string() - } -} - -impl CursorType for f64 { - type Error = ParseFloatError; - - fn decode_cursor(s: &str) -> Result { - s.parse() - } - - fn encode_cursor(&self) -> String { - self.to_string() - } -} - -impl CursorType for char { - type Error = ParseCharError; - - fn decode_cursor(s: &str) -> Result { - s.parse() - } - - fn encode_cursor(&self) -> String { - self.to_string() - } -} - -impl CursorType for bool { - type Error = ParseBoolError; - - fn decode_cursor(s: &str) -> Result { - s.parse() - } - - fn encode_cursor(&self) -> String { - self.to_string() - } -} - -impl CursorType for String { - type Error = Infallible; - - fn decode_cursor(s: &str) -> Result { - Ok(s.to_string()) - } - - fn encode_cursor(&self) -> String { - self.clone() - } -} - -impl CursorType for ID { - type Error = Infallible; - - fn decode_cursor(s: &str) -> Result { - Ok(s.to_string().into()) - } - - fn encode_cursor(&self) -> String { - self.to_string() - } -} - -#[cfg(feature = "chrono")] -impl CursorType for chrono::DateTime { - type Error = chrono::ParseError; - - fn decode_cursor(s: &str) -> Result { - Ok(chrono::DateTime::parse_from_rfc3339(s)?.with_timezone::(&chrono::Utc {})) - } - - fn encode_cursor(&self) -> String { - self.to_rfc3339_opts(chrono::SecondsFormat::Micros, true) - } -} - -#[cfg(feature = "uuid")] -impl CursorType for uuid::Uuid { - type Error = uuid::Error; - - fn decode_cursor(s: &str) -> Result { - s.parse() - } - - fn encode_cursor(&self) -> String { - self.to_string() - } -} - -/// A opaque cursor that encode/decode the value to base64 -pub struct OpaqueCursor(pub T); - -impl Deref for OpaqueCursor { - type Target = T; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for OpaqueCursor { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl CursorType for OpaqueCursor -where - T: Serialize + DeserializeOwned, -{ - type Error = Box; - - fn decode_cursor(s: &str) -> Result { - use base64::Engine; - - let data = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(s)?; - Ok(Self(serde_json::from_slice(&data)?)) - } - - fn encode_cursor(&self) -> String { - use base64::Engine; - - let value = serde_json::to_vec(&self.0).unwrap_or_default(); - base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(value) - } -} diff --git a/src/types/connection/edge.rs b/src/types/connection/edge.rs deleted file mode 100644 index ee9c692bf..000000000 --- a/src/types/connection/edge.rs +++ /dev/null @@ -1,96 +0,0 @@ -use std::{borrow::Cow, marker::PhantomData}; - -use crate::{ - ComplexObject, ObjectType, OutputType, SimpleObject, TypeName, - connection::{DefaultEdgeName, EmptyFields}, - types::connection::{CursorType, EdgeNameType}, -}; - -/// An edge in a connection. -#[derive(SimpleObject)] -#[graphql(internal, name_type, shareable, complex)] -pub struct Edge -where - Cursor: CursorType + Send + Sync, - Node: OutputType, - EdgeFields: ObjectType, - Name: EdgeNameType, -{ - #[graphql(skip)] - _mark: PhantomData, - /// A cursor for use in pagination - #[graphql(skip)] - pub cursor: Cursor, - /// The item at the end of the edge - pub node: Node, - #[graphql(flatten)] - pub(crate) additional_fields: EdgeFields, -} - -#[ComplexObject(internal)] -impl Edge -where - Cursor: CursorType + Send + Sync, - Node: OutputType, - EdgeFields: ObjectType, - Name: EdgeNameType, -{ - /// A cursor for use in pagination - async fn cursor(&self) -> String { - self.cursor.encode_cursor() - } -} - -impl TypeName for Edge -where - Cursor: CursorType + Send + Sync, - Node: OutputType, - EdgeFields: ObjectType, - Name: EdgeNameType, -{ - #[inline] - fn type_name() -> Cow<'static, str> { - Name::type_name::().into() - } -} - -impl Edge -where - Name: EdgeNameType, - Cursor: CursorType + Send + Sync, - Node: OutputType, - EdgeFields: ObjectType, -{ - /// Create a new edge, it can have some additional fields. - #[inline] - pub fn with_additional_fields( - cursor: Cursor, - node: Node, - additional_fields: EdgeFields, - ) -> Self { - Self { - _mark: PhantomData, - cursor, - node, - additional_fields, - } - } -} - -impl Edge -where - Cursor: CursorType + Send + Sync, - Node: OutputType, - Name: EdgeNameType, -{ - /// Create a new edge. - #[inline] - pub fn new(cursor: Cursor, node: Node) -> Self { - Self { - _mark: PhantomData, - cursor, - node, - additional_fields: EmptyFields, - } - } -} diff --git a/src/types/connection/mod.rs b/src/types/connection/mod.rs deleted file mode 100644 index f8d84fba4..000000000 --- a/src/types/connection/mod.rs +++ /dev/null @@ -1,386 +0,0 @@ -//! Types for Relay-compliant server - -mod connection_type; -mod cursor; -mod edge; -mod page_info; - -use std::{fmt::Display, future::Future}; - -pub use connection_type::Connection; -pub use cursor::{CursorType, OpaqueCursor}; -pub use edge::Edge; -pub use page_info::PageInfo; - -use crate::{Error, ObjectType, OutputType, Result, SimpleObject}; - -/// Empty additional fields -#[derive(SimpleObject)] -#[graphql(internal, fake)] -pub struct EmptyFields; - -/// Used to specify the edge name. -pub trait EdgeNameType: Send + Sync { - /// Returns the edge type name. - fn type_name() -> String; -} - -/// Name the edge type by default with the default format. -pub struct DefaultEdgeName; - -impl EdgeNameType for DefaultEdgeName { - fn type_name() -> String { - format!("{}Edge", T::type_name()) - } -} - -/// Used to specify the connection name. -pub trait ConnectionNameType: Send + Sync { - /// Returns the connection type name. - fn type_name() -> String; -} - -/// Name the connection type by default with the default format. -pub struct DefaultConnectionName; - -impl ConnectionNameType for DefaultConnectionName { - fn type_name() -> String { - format!("{}Connection", T::type_name()) - } -} - -mod private { - pub trait NodesFieldSwitcher: Send + Sync {} - - impl NodesFieldSwitcher for super::DisableNodesField {} - impl NodesFieldSwitcher for super::EnableNodesField {} -} - -/// Allow switch if [`Connection`] contains `nodes` field in GQL output -/// -/// This trait is sealed and can not be implemented outside of this crate. -pub trait NodesFieldSwitcherSealed: private::NodesFieldSwitcher {} - -impl NodesFieldSwitcherSealed for DisableNodesField {} -impl NodesFieldSwitcherSealed for EnableNodesField {} - -/// Enable (at compile time) `nodes` field in GQL output of [`Connection`] -pub struct EnableNodesField; - -/// Disable (at compile time) `nodes` field in GQL output of [`Connection`] -pub struct DisableNodesField; - -/// Parses the parameters and executes the query. -/// -/// # Examples -/// -/// ```rust -/// use std::borrow::Cow; -/// -/// use async_graphql::*; -/// use async_graphql::types::connection::*; -/// -/// struct Query; -/// -/// struct Numbers; -/// -/// #[derive(SimpleObject)] -/// struct Diff { -/// diff: i32, -/// } -/// -/// #[Object] -/// impl Query { -/// async fn numbers(&self, -/// after: Option, -/// before: Option, -/// first: Option, -/// last: Option -/// ) -> Result> { -/// query(after, before, first, last, |after, before, first, last| async move { -/// let mut start = after.map(|after| after + 1).unwrap_or(0); -/// let mut end = before.unwrap_or(10000); -/// if let Some(first) = first { -/// end = (start + first).min(end); -/// } -/// if let Some(last) = last { -/// start = if last > end - start { -/// end -/// } else { -/// end - last -/// }; -/// } -/// let mut connection = Connection::new(start > 0, end < 10000); -/// connection.edges.extend( -/// (start..end).into_iter().map(|n| -/// Edge::with_additional_fields(n, n as i32, Diff{ diff: (10000 - n) as i32 })), -/// ); -/// Ok::<_, Error>(connection) -/// }).await -/// } -/// } -/// -/// # tokio::runtime::Runtime::new().unwrap().block_on(async { -/// let schema = Schema::new(Query, EmptyMutation, EmptySubscription); -/// -/// assert_eq!(schema.execute("{ numbers(first: 2) { edges { node diff } } }").await.into_result().unwrap().data, value!({ -/// "numbers": { -/// "edges": [ -/// {"node": 0, "diff": 10000}, -/// {"node": 1, "diff": 9999}, -/// ] -/// }, -/// })); -/// -/// assert_eq!(schema.execute("{ numbers(last: 2) { edges { node diff } } }").await.into_result().unwrap().data, value!({ -/// "numbers": { -/// "edges": [ -/// {"node": 9998, "diff": 2}, -/// {"node": 9999, "diff": 1}, -/// ] -/// }, -/// })); -/// # }); -/// ``` -/// -/// # Custom connection and edge type names -/// -/// ``` -/// use async_graphql::{connection::*, *}; -/// -/// #[derive(SimpleObject)] -/// struct MyObj { -/// a: i32, -/// b: String, -/// } -/// -/// // Use to custom connection name -/// struct MyConnectionName; -/// -/// impl ConnectionNameType for MyConnectionName { -/// fn type_name() -> String { -/// "MyConnection".to_string() -/// } -/// } -/// -/// // Use to custom edge name -/// struct MyEdgeName; -/// -/// impl EdgeNameType for MyEdgeName { -/// fn type_name() -> String { -/// "MyEdge".to_string() -/// } -/// } -/// -/// struct Query; -/// -/// #[Object] -/// impl Query { -/// async fn numbers( -/// &self, -/// after: Option, -/// before: Option, -/// first: Option, -/// last: Option, -/// ) -> Connection { -/// let mut connection = Connection::new(false, false); -/// connection.edges.push(Edge::new(1, MyObj { a: 100, b: "abc".to_string() })); -/// connection -/// } -/// } -/// -/// # tokio::runtime::Runtime::new().unwrap().block_on(async { -/// let schema = Schema::new(Query, EmptyMutation, EmptySubscription); -/// -/// let query = r#"{ -/// numbers(first: 2) { -/// __typename -/// edges { __typename node { a b } } -/// } -/// }"#; -/// let data = schema.execute(query).await.into_result().unwrap().data; -/// assert_eq!(data, value!({ -/// "numbers": { -/// "__typename": "MyConnection", -/// "edges": [ -/// {"__typename": "MyEdge", "node": { "a": 100, "b": "abc" }}, -/// ] -/// }, -/// })); -/// # }); -/// ``` -pub async fn query< - Name, - EdgeName, - Cursor, - Node, - NodesVersion, - ConnectionFields, - EdgeFields, - F, - R, - E, ->( - after: Option, - before: Option, - first: Option, - last: Option, - f: F, -) -> Result> -where - Name: ConnectionNameType, - EdgeName: EdgeNameType, - Cursor: CursorType + Send + Sync, - ::Error: Display + Send + Sync + 'static, - Node: OutputType, - NodesVersion: NodesFieldSwitcherSealed, - ConnectionFields: ObjectType, - EdgeFields: ObjectType, - F: FnOnce(Option, Option, Option, Option) -> R, - R: Future< - Output = Result< - Connection, - E, - >, - >, - E: Into, -{ - query_with(after, before, first, last, f).await -} - -/// Parses the parameters and executes the query and return a custom -/// `Connection` type. -/// -/// `Connection` and `Edge` have certain limitations. For example, you -/// cannot customize the name of the type, so you can use this function to -/// execute the query and return a customized `Connection` type. -/// -/// # Examples -/// -/// ```rust -/// -/// use async_graphql::*; -/// use async_graphql::types::connection::*; -/// -/// #[derive(SimpleObject)] -/// struct MyEdge { -/// cursor: usize, -/// node: i32, -/// diff: i32, -/// } -/// -/// #[derive(SimpleObject)] -/// struct MyConnection { -/// edges: Vec, -/// page_info: PageInfo, -/// } -/// -/// struct Query; -/// -/// #[Object] -/// impl Query { -/// async fn numbers(&self, -/// after: Option, -/// before: Option, -/// first: Option, -/// last: Option -/// ) -> Result { -/// query_with(after, before, first, last, |after, before, first, last| async move { -/// let mut start = after.map(|after| after + 1).unwrap_or(0); -/// let mut end = before.unwrap_or(10000); -/// if let Some(first) = first { -/// end = (start + first).min(end); -/// } -/// if let Some(last) = last { -/// start = if last > end - start { -/// end -/// } else { -/// end - last -/// }; -/// } -/// let connection = MyConnection { -/// edges: (start..end).into_iter().map(|n| MyEdge { -/// cursor: n, -/// node: n as i32, -/// diff: (10000 - n) as i32, -/// }).collect(), -/// page_info: PageInfo { -/// has_previous_page: start > 0, -/// has_next_page: end < 10000, -/// start_cursor: Some(start.encode_cursor()), -/// end_cursor: Some(end.encode_cursor()), -/// }, -/// }; -/// Ok::<_, Error>(connection) -/// }).await -/// } -/// } -/// -/// # tokio::runtime::Runtime::new().unwrap().block_on(async { -/// let schema = Schema::new(Query, EmptyMutation, EmptySubscription); -/// -/// assert_eq!(schema.execute("{ numbers(first: 2) { edges { node diff } } }").await.into_result().unwrap().data, value!({ -/// "numbers": { -/// "edges": [ -/// {"node": 0, "diff": 10000}, -/// {"node": 1, "diff": 9999}, -/// ] -/// }, -/// })); -/// -/// assert_eq!(schema.execute("{ numbers(last: 2) { edges { node diff } } }").await.into_result().unwrap().data, value!({ -/// "numbers": { -/// "edges": [ -/// {"node": 9998, "diff": 2}, -/// {"node": 9999, "diff": 1}, -/// ] -/// }, -/// })); -/// # }); -/// ``` -pub async fn query_with( - after: Option, - before: Option, - first: Option, - last: Option, - f: F, -) -> Result -where - Cursor: CursorType + Send + Sync, - ::Error: Display + Send + Sync + 'static, - F: FnOnce(Option, Option, Option, Option) -> R, - R: Future>, - E: Into, -{ - let first = match first { - Some(first) if first < 0 => { - return Err(Error::new( - "The \"first\" parameter must be a non-negative number", - )); - } - Some(first) => Some(first as usize), - None => None, - }; - - let last = match last { - Some(last) if last < 0 => { - return Err(Error::new( - "The \"last\" parameter must be a non-negative number", - )); - } - Some(last) => Some(last as usize), - None => None, - }; - - let before = match before { - Some(before) => Some(Cursor::decode_cursor(&before).map_err(Error::new_with_source)?), - None => None, - }; - - let after = match after { - Some(after) => Some(Cursor::decode_cursor(&after).map_err(Error::new_with_source)?), - None => None, - }; - - f(after, before, first, last).await.map_err(Into::into) -} diff --git a/src/types/connection/page_info.rs b/src/types/connection/page_info.rs deleted file mode 100644 index 2d1a2a95d..000000000 --- a/src/types/connection/page_info.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::SimpleObject; - -/// Information about pagination in a connection -#[derive(SimpleObject)] -#[graphql(internal, shareable)] -pub struct PageInfo { - /// When paginating backwards, are there more items? - pub has_previous_page: bool, - - /// When paginating forwards, are there more items? - pub has_next_page: bool, - - /// When paginating backwards, the cursor to continue. - pub start_cursor: Option, - - /// When paginating forwards, the cursor to continue. - pub end_cursor: Option, -} diff --git a/src/types/empty_mutation.rs b/src/types/empty_mutation.rs deleted file mode 100644 index 7825cb269..000000000 --- a/src/types/empty_mutation.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::borrow::Cow; - -use crate::{ - Context, ContextSelectionSet, ObjectType, OutputType, Positioned, ServerError, ServerResult, - Value, parser::types::Field, registry, registry::MetaTypeId, resolver_utils::ContainerType, -}; - -/// Empty mutation -/// -/// Only the parameters used to construct the Schema, representing an -/// unconfigured mutation. -/// -/// # Examples -/// -/// ```rust -/// use async_graphql::*; -/// -/// struct Query; -/// -/// #[Object] -/// impl Query { -/// async fn value(&self) -> i32 { -/// // A GraphQL Object type must define one or more fields. -/// 100 -/// } -/// } -/// -/// let schema = Schema::new(Query, EmptyMutation, EmptySubscription); -/// ``` -#[derive(Default, Copy, Clone)] -pub struct EmptyMutation; - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl ContainerType for EmptyMutation { - fn is_empty() -> bool { - true - } - - async fn resolve_field(&self, _ctx: &Context<'_>) -> ServerResult> { - Ok(None) - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for EmptyMutation { - fn type_name() -> Cow<'static, str> { - Cow::Borrowed("EmptyMutation") - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - registry.create_output_type::(MetaTypeId::Object, |_| registry::MetaType::Object { - name: "EmptyMutation".to_string(), - description: None, - fields: Default::default(), - cache_control: Default::default(), - extends: false, - shareable: false, - resolvable: true, - keys: None, - visible: None, - inaccessible: false, - interface_object: false, - tags: Default::default(), - is_subscription: false, - rust_typename: Some(std::any::type_name::()), - directive_invocations: Default::default(), - requires_scopes: Default::default(), - }) - } - - async fn resolve( - &self, - _ctx: &ContextSelectionSet<'_>, - _field: &Positioned, - ) -> ServerResult { - Err(ServerError::new( - "Schema is not configured for mutations.", - None, - )) - } -} - -impl ObjectType for EmptyMutation {} diff --git a/src/types/empty_subscription.rs b/src/types/empty_subscription.rs deleted file mode 100644 index ab9b3e8c6..000000000 --- a/src/types/empty_subscription.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::{borrow::Cow, pin::Pin}; - -use futures_util::stream::{self, Stream}; - -use crate::{Context, Response, ServerError, SubscriptionType, registry}; - -/// Empty subscription -/// -/// Only the parameters used to construct the Schema, representing an -/// unconfigured subscription. -#[derive(Default, Copy, Clone)] -pub struct EmptySubscription; - -impl SubscriptionType for EmptySubscription { - fn type_name() -> Cow<'static, str> { - Cow::Borrowed("EmptySubscription") - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - registry.create_subscription_type::(|_| registry::MetaType::Object { - name: "EmptySubscription".to_string(), - description: None, - fields: Default::default(), - cache_control: Default::default(), - extends: false, - shareable: false, - resolvable: true, - keys: None, - visible: None, - inaccessible: false, - interface_object: false, - tags: Default::default(), - is_subscription: true, - rust_typename: Some(std::any::type_name::()), - directive_invocations: Default::default(), - requires_scopes: Default::default(), - }) - } - - fn is_empty() -> bool { - true - } - - fn create_field_stream<'a>( - &'a self, - _ctx: &'a Context<'_>, - ) -> Option + Send + 'a>>> - where - Self: Send + Sync + 'static + Sized, - { - Some(Box::pin(stream::once(async move { - let err = ServerError::new("Schema is not configured for subscription.", None); - Response::from_errors(vec![err]) - }))) - } -} diff --git a/src/types/external/big_decimal.rs b/src/types/external/big_decimal.rs deleted file mode 100644 index 049ad0a0c..000000000 --- a/src/types/external/big_decimal.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::str::FromStr; - -use bigdecimal::BigDecimal; - -use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; - -#[Scalar(internal, name = "BigDecimal")] -impl ScalarType for BigDecimal { - fn parse(value: Value) -> InputValueResult { - match &value { - Value::Number(n) => { - if let Some(f) = n.as_f64() { - return BigDecimal::try_from(f).map_err(InputValueError::custom); - } - - if let Some(f) = n.as_i64() { - return Ok(BigDecimal::from(f)); - } - - // unwrap safe here, because we have check the other possibility - Ok(BigDecimal::from(n.as_u64().unwrap())) - } - Value::String(s) => Ok(BigDecimal::from_str(s)?), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - Value::String(self.to_string()) - } -} diff --git a/src/types/external/bool.rs b/src/types/external/bool.rs deleted file mode 100644 index 048eeff92..000000000 --- a/src/types/external/bool.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; - -/// The `Boolean` scalar type represents `true` or `false`. -#[Scalar(internal, name = "Boolean")] -impl ScalarType for bool { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Boolean(n) => Ok(n), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Boolean(_)) - } - - fn to_value(&self) -> Value { - Value::Boolean(*self) - } -} diff --git a/src/types/external/bson.rs b/src/types/external/bson.rs deleted file mode 100644 index e25e792fb..000000000 --- a/src/types/external/bson.rs +++ /dev/null @@ -1,120 +0,0 @@ -#[cfg(feature = "chrono")] -use bson::DateTime as UtcDateTime; -use bson::{Bson, Document, Uuid, oid::ObjectId}; -#[cfg(feature = "chrono")] -use chrono::{DateTime, Utc}; - -use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; - -#[Scalar(internal)] -impl ScalarType for ObjectId { - fn parse(value: Value) -> InputValueResult { - match value { - Value::String(s) => Ok(ObjectId::parse_str(s)?), - Value::Object(o) => { - let json = Value::Object(o).into_json()?; - let bson = Bson::try_from(json)?; - bson.as_object_id().ok_or_else(|| { - InputValueError::custom("could not parse the value as a BSON ObjectId") - }) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - Value::String(self.to_string()) - } -} - -#[Scalar(internal, name = "UUID")] -impl ScalarType for Uuid { - fn parse(value: Value) -> InputValueResult { - match value { - Value::String(s) => Ok(Uuid::parse_str(s)?), - Value::Object(o) => { - let json = Value::Object(o).into_json()?; - let Bson::Binary(binary) = Bson::try_from(json)? else { - return Err(InputValueError::custom( - "could not parse the value as BSON Binary", - )); - }; - binary.to_uuid().map_err(|_| { - InputValueError::custom("could not deserialize BSON Binary to Uuid") - }) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - Value::String(self.to_string()) - } -} - -#[cfg(feature = "chrono")] -#[Scalar(internal, name = "DateTime")] -impl ScalarType for UtcDateTime { - fn parse(value: Value) -> InputValueResult { - >::parse(value) - .map_err(InputValueError::propagate) - .map(UtcDateTime::from_chrono) - } - - fn to_value(&self) -> Value { - self.to_chrono().to_value() - } -} - -#[Scalar(internal, name = "JSON")] -impl ScalarType for Bson { - fn parse(value: Value) -> InputValueResult { - bson::to_bson(&value).map_err(InputValueError::custom) - } - - fn to_value(&self) -> Value { - bson::from_bson(self.clone()).unwrap_or_default() - } -} - -#[Scalar(internal, name = "JSONObject")] -impl ScalarType for Document { - fn parse(value: Value) -> InputValueResult { - bson::to_document(&value).map_err(InputValueError::custom) - } - - fn to_value(&self) -> Value { - bson::from_document(self.clone()).unwrap_or_default() - } -} - -#[cfg(test)] -mod tests { - use serde_json::json; - - use super::*; - - #[test] - fn test_parse_bson_uuid() { - let id = Uuid::new(); - let bson_value = bson::bson!(id); - let extended_json_value = json!(bson_value); - let gql_value = Value::from_json(extended_json_value).expect("valid json"); - assert_eq!( - id, - ::parse(gql_value).expect("parsing succeeds") - ); - } - - #[test] - fn test_parse_bson_object_id() { - let id = ObjectId::from_bytes([42; 12]); - let bson_value = bson::bson!(id); - let extended_json_value = json!(bson_value); - let gql_value = Value::from_json(extended_json_value).expect("valid json"); - assert_eq!( - id, - ::parse(gql_value).expect("parsing succeeds") - ); - } -} diff --git a/src/types/external/bytes.rs b/src/types/external/bytes.rs deleted file mode 100644 index 3a8bfd471..000000000 --- a/src/types/external/bytes.rs +++ /dev/null @@ -1,22 +0,0 @@ -use bytes::Bytes; - -use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; - -/// The `Binary` scalar type represents binary data. -#[Scalar(internal)] -impl ScalarType for Bytes { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Binary(data) => Ok(data), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Binary(_)) - } - - fn to_value(&self) -> Value { - Value::Binary(self.clone()) - } -} diff --git a/src/types/external/char.rs b/src/types/external/char.rs deleted file mode 100644 index 05b9cc9c3..000000000 --- a/src/types/external/char.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; - -/// The `Char` scalar type represents a unicode char. -/// The input and output values are a string, and there can only be one unicode -/// character in this string. -#[Scalar(internal)] -impl ScalarType for char { - fn parse(value: Value) -> InputValueResult { - match value { - Value::String(s) => { - let mut chars = s.chars(); - match chars.next() { - Some(ch) if chars.next().is_none() => Ok(ch), - Some(_) => Err(InputValueError::custom( - "There can only be one unicode character in the string.", - )), - None => Err(InputValueError::custom("A unicode character is required.")), - } - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::String(_)) - } - - fn to_value(&self) -> Value { - Value::String((*self).into()) - } -} diff --git a/src/types/external/chrono_tz.rs b/src/types/external/chrono_tz.rs deleted file mode 100644 index fafefbefe..000000000 --- a/src/types/external/chrono_tz.rs +++ /dev/null @@ -1,21 +0,0 @@ -use chrono_tz::Tz; - -use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; - -#[Scalar( - internal, - name = "TimeZone", - specified_by_url = "http://www.iana.org/time-zones" -)] -impl ScalarType for Tz { - fn parse(value: Value) -> InputValueResult { - match value { - Value::String(s) => Ok(s.parse()?), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - Value::String(self.name().to_owned()) - } -} diff --git a/src/types/external/cow.rs b/src/types/external/cow.rs deleted file mode 100644 index 2bfc6f998..000000000 --- a/src/types/external/cow.rs +++ /dev/null @@ -1,96 +0,0 @@ -use std::borrow::Cow; - -use async_graphql_parser::types::Field; - -use crate::{ContextSelectionSet, OutputType, Positioned, ServerResult, Value, registry}; - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for Cow<'_, T> -where - T: OutputType + ToOwned + ?Sized, - ::Owned: Send + Sync, -{ - fn type_name() -> Cow<'static, str> { - T::type_name() - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - ::create_type_info(registry) - } - - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { - self.as_ref().resolve(ctx, field).await - } -} - -#[cfg(test)] -mod test { - use std::borrow::Cow; - - use crate::*; - - #[tokio::test] - async fn test_cow_type() { - struct Query { - obj: MyObj, - } - - #[derive(SimpleObject, Clone)] - #[graphql(internal)] - struct MyObj { - a: i32, - b: i32, - } - - #[Object(internal)] - impl Query { - async fn value1(&self) -> Cow<'_, str> { - Cow::Borrowed("abc") - } - - async fn value2(&self) -> Cow<'_, str> { - Cow::Owned("def".to_string()) - } - - async fn obj1(&self) -> Cow<'_, MyObj> { - Cow::Borrowed(&self.obj) - } - - async fn obj2(&self) -> Cow<'_, MyObj> { - Cow::Owned(MyObj { a: 300, b: 400 }) - } - } - - let query = r#"{ - value1 - value2 - obj1 { - a b - } - obj2 { - a b - } - }"#; - let schema = Schema::new( - Query { - obj: MyObj { a: 100, b: 200 }, - }, - EmptyMutation, - EmptySubscription, - ); - - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "value1": "abc", - "value2": "def", - "obj1": {"a": 100, "b": 200}, - "obj2": {"a": 300, "b": 400}, - }) - ); - } -} diff --git a/src/types/external/datetime.rs b/src/types/external/datetime.rs deleted file mode 100644 index cdcb2de49..000000000 --- a/src/types/external/datetime.rs +++ /dev/null @@ -1,66 +0,0 @@ -use chrono::{DateTime, FixedOffset, Local, Utc}; - -use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; - -/// Implement the DateTime scalar -/// -/// The input/output is a string in RFC3339 format. -#[Scalar( - internal, - name = "DateTime", - specified_by_url = "https://datatracker.ietf.org/doc/html/rfc3339" -)] -impl ScalarType for DateTime { - fn parse(value: Value) -> InputValueResult { - match &value { - Value::String(s) => Ok(s.parse::>()?), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - Value::String(self.to_rfc3339()) - } -} - -/// Implement the DateTime scalar -/// -/// The input/output is a string in RFC3339 format. -#[Scalar( - internal, - name = "DateTime", - specified_by_url = "https://datatracker.ietf.org/doc/html/rfc3339" -)] -impl ScalarType for DateTime { - fn parse(value: Value) -> InputValueResult { - match &value { - Value::String(s) => Ok(s.parse::>()?), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - Value::String(self.to_rfc3339()) - } -} - -/// Implement the DateTime scalar -/// -/// The input/output is a string in RFC3339 format. -#[Scalar( - internal, - name = "DateTime", - specified_by_url = "https://datatracker.ietf.org/doc/html/rfc3339" -)] -impl ScalarType for DateTime { - fn parse(value: Value) -> InputValueResult { - match &value { - Value::String(s) => Ok(s.parse::>()?), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - Value::String(self.to_rfc3339()) - } -} diff --git a/src/types/external/decimal.rs b/src/types/external/decimal.rs deleted file mode 100644 index dca544b35..000000000 --- a/src/types/external/decimal.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::str::FromStr; - -use rust_decimal::Decimal; - -use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; - -#[Scalar(internal, name = "Decimal")] -impl ScalarType for Decimal { - fn parse(value: Value) -> InputValueResult { - match &value { - Value::String(s) => Ok(Decimal::from_str(s)?), - Value::Number(n) => { - if let Some(f) = n.as_f64() { - return Decimal::try_from(f).map_err(InputValueError::custom); - } - - if let Some(f) = n.as_i64() { - return Ok(Decimal::from(f)); - } - - // unwrap safe here, because we have check the other possibility - Ok(Decimal::from(n.as_u64().unwrap())) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - Value::String(self.to_string()) - } -} diff --git a/src/types/external/duration.rs b/src/types/external/duration.rs deleted file mode 100644 index 1de9ba68f..000000000 --- a/src/types/external/duration.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::str::FromStr; - -use chrono::Duration; - -use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; - -/// Implement the Duration scalar -/// -/// The input/output is a string in ISO8601 format. -#[Scalar( - internal, - name = "Duration", - specified_by_url = "https://en.wikipedia.org/wiki/ISO_8601#Durations" -)] -impl ScalarType for Duration { - fn parse(value: Value) -> InputValueResult { - match &value { - Value::String(s) => Ok(Duration::from_std(std::time::Duration::from( - iso8601::Duration::from_str(s)?, - ))?), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - Value::String(self.to_string()) - } -} diff --git a/src/types/external/floats.rs b/src/types/external/floats.rs deleted file mode 100644 index 7fd46900e..000000000 --- a/src/types/external/floats.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::{InputValueError, InputValueResult, Number, Scalar, ScalarType, Value}; - -/// The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). -#[Scalar(internal, name = "Float")] -impl ScalarType for f32 { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => Ok(n - .as_f64() - .ok_or_else(|| InputValueError::from("Invalid number"))? - as Self), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(_)) - } - - fn to_value(&self) -> Value { - match Number::from_f64(*self as f64) { - Some(n) => Value::Number(n), - None => Value::Null, - } - } -} - -/// The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). -#[Scalar(internal, name = "Float")] -impl ScalarType for f64 { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => Ok(n - .as_f64() - .ok_or_else(|| InputValueError::from("Invalid number"))? - as Self), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(_)) - } - - fn to_value(&self) -> Value { - match Number::from_f64(*self) { - Some(n) => Value::Number(n), - None => Value::Null, - } - } -} diff --git a/src/types/external/integers.rs b/src/types/external/integers.rs deleted file mode 100644 index 334ce9d80..000000000 --- a/src/types/external/integers.rs +++ /dev/null @@ -1,297 +0,0 @@ -use crate::{InputValueError, InputValueResult, Number, Scalar, ScalarType, Value}; - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for i8 { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_i64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - if n < Self::MIN as i64 || n > Self::MAX as i64 { - return Err(InputValueError::from(format!( - "Only integers from {} to {} are accepted.", - Self::MIN, - Self::MAX - ))); - } - Ok(n as Self) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_i64()) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(*self as i64)) - } -} - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for i16 { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_i64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - if n < Self::MIN as i64 || n > Self::MAX as i64 { - return Err(InputValueError::from(format!( - "Only integers from {} to {} are accepted.", - Self::MIN, - Self::MAX - ))); - } - Ok(n as Self) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_i64() ) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(*self as i64)) - } -} - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for i32 { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_i64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - if n < Self::MIN as i64 || n > Self::MAX as i64 { - return Err(InputValueError::from(format!( - "Only integers from {} to {} are accepted.", - Self::MIN, - Self::MAX - ))); - } - Ok(n as Self) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_i64()) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(*self as i64)) - } -} - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for i64 { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_i64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - Ok(n as Self) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_i64()) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(*self)) - } -} - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for u8 { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_u64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - if n > Self::MAX as u64 { - return Err(InputValueError::from(format!( - "Only integers from {} to {} are accepted.", - 0, - Self::MAX - ))); - } - Ok(n as Self) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_u64()) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(*self as u64)) - } -} - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for u16 { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_u64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - if n > Self::MAX as u64 { - return Err(InputValueError::from(format!( - "Only integers from {} to {} are accepted.", - 0, - Self::MAX - ))); - } - Ok(n as Self) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_u64()) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(*self as u64)) - } -} - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for u32 { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_u64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - if n > Self::MAX as u64 { - return Err(InputValueError::from(format!( - "Only integers from {} to {} are accepted.", - 0, - Self::MAX - ))); - } - Ok(n as Self) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_u64()) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(*self as u64)) - } -} - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for u64 { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_u64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - Ok(n as Self) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_u64()) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(*self)) - } -} - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for usize { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_u64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - if n > Self::MAX as u64 { - return Err(InputValueError::from(format!( - "Only integers from {} to {} are accepted.", - 0, - Self::MAX - ))); - } - Ok(n as Self) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_u64()) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(*self as u64)) - } -} - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for isize { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_i64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - if n < Self::MIN as i64 || n > Self::MAX as i64 { - return Err(InputValueError::from(format!( - "Only integers from {} to {} are accepted.", - Self::MIN, - Self::MAX - ))); - } - Ok(n as Self) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_i64()) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(*self as i64)) - } -} diff --git a/src/types/external/json_object/btreemap.rs b/src/types/external/json_object/btreemap.rs deleted file mode 100644 index 80a203361..000000000 --- a/src/types/external/json_object/btreemap.rs +++ /dev/null @@ -1,113 +0,0 @@ -use std::{borrow::Cow, collections::BTreeMap, fmt::Display, str::FromStr}; - -use async_graphql_parser::{Positioned, types::Field}; -use async_graphql_value::{from_value, to_value}; -use indexmap::IndexMap; -use serde::{Serialize, de::DeserializeOwned}; - -use crate::{ - ContextSelectionSet, InputType, InputValueError, InputValueResult, Name, OutputType, - ServerResult, Value, - registry::{MetaType, MetaTypeId, Registry}, -}; - -impl InputType for BTreeMap -where - K: ToString + FromStr + Ord + Send + Sync, - K::Err: Display, - V: Serialize + DeserializeOwned + Send + Sync, -{ - type RawValueType = Self; - - fn type_name() -> Cow<'static, str> { - Cow::Borrowed("JSONObject") - } - - fn create_type_info(registry: &mut Registry) -> String { - registry.create_input_type::(MetaTypeId::Scalar, |_| MetaType::Scalar { - name: ::type_name().to_string(), - description: Some("A scalar that can represent any JSON Object value.".to_string()), - is_valid: None, - visible: None, - inaccessible: false, - tags: Default::default(), - specified_by_url: None, - directive_invocations: Default::default(), - requires_scopes: Default::default(), - }) - } - - fn parse(value: Option) -> InputValueResult { - let value = value.unwrap_or_default(); - match value { - Value::Object(map) => map - .into_iter() - .map(|(name, value)| { - Ok(( - K::from_str(&name).map_err(|err| { - InputValueError::::custom(format!("object key: {}", err)) - })?, - from_value(value).map_err(|err| format!("object value: {}", err))?, - )) - }) - .collect::>() - .map_err(InputValueError::propagate), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - let mut map = IndexMap::new(); - for (name, value) in self { - map.insert( - Name::new(name.to_string()), - to_value(value).unwrap_or_default(), - ); - } - Value::Object(map) - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - Some(self) - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for BTreeMap -where - K: ToString + Ord + Send + Sync, - V: Serialize + Send + Sync, -{ - fn type_name() -> Cow<'static, str> { - Cow::Borrowed("JSONObject") - } - - fn create_type_info(registry: &mut Registry) -> String { - registry.create_output_type::(MetaTypeId::Scalar, |_| MetaType::Scalar { - name: ::type_name().to_string(), - description: Some("A scalar that can represent any JSON Object value.".to_string()), - is_valid: None, - visible: None, - inaccessible: false, - tags: Default::default(), - specified_by_url: None, - directive_invocations: Default::default(), - requires_scopes: Default::default(), - }) - } - - async fn resolve( - &self, - _ctx: &ContextSelectionSet<'_>, - _field: &Positioned, - ) -> ServerResult { - let mut map = IndexMap::new(); - for (name, value) in self { - map.insert( - Name::new(name.to_string()), - to_value(value).unwrap_or_default(), - ); - } - Ok(Value::Object(map)) - } -} diff --git a/src/types/external/json_object/hashbrown_hashmap.rs b/src/types/external/json_object/hashbrown_hashmap.rs deleted file mode 100644 index 9c9977e09..000000000 --- a/src/types/external/json_object/hashbrown_hashmap.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::{ - borrow::Cow, collections::HashMap as StdHashMap, fmt::Display, hash::Hash, str::FromStr, -}; - -use async_graphql_parser::{Positioned, types::Field}; -use async_graphql_value::{from_value, to_value}; -use hashbrown::HashMap; -use indexmap::IndexMap; -use serde::{Serialize, de::DeserializeOwned}; - -use crate::{ - ContextSelectionSet, InputType, InputValueError, InputValueResult, Name, OutputType, - ServerResult, Value, registry::Registry, -}; - -impl InputType for HashMap -where - K: ToString + FromStr + Eq + Hash + Send + Sync, - K::Err: Display, - V: Serialize + DeserializeOwned + Send + Sync, -{ - type RawValueType = Self; - - fn type_name() -> Cow<'static, str> { - as InputType>::type_name() - } - - fn create_type_info(registry: &mut Registry) -> String { - as InputType>::create_type_info(registry) - } - - fn parse(value: Option) -> InputValueResult { - let value = value.unwrap_or_default(); - match value { - Value::Object(map) => map - .into_iter() - .map(|(name, value)| { - Ok(( - K::from_str(&name).map_err(|err| { - InputValueError::::custom(format!("object key: {}", err)) - })?, - from_value(value).map_err(|err| format!("object value: {}", err))?, - )) - }) - .collect::>() - .map_err(InputValueError::propagate), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - let mut map = IndexMap::new(); - for (name, value) in self { - map.insert( - Name::new(name.to_string()), - to_value(value).unwrap_or_default(), - ); - } - Value::Object(map) - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - Some(self) - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for HashMap -where - K: ToString + Eq + Hash + Send + Sync, - V: Serialize + Send + Sync, -{ - fn type_name() -> Cow<'static, str> { - as OutputType>::type_name() - } - - fn create_type_info(registry: &mut Registry) -> String { - as OutputType>::create_type_info(registry) - } - - async fn resolve( - &self, - _ctx: &ContextSelectionSet<'_>, - _field: &Positioned, - ) -> ServerResult { - let mut map = IndexMap::new(); - for (name, value) in self { - map.insert( - Name::new(name.to_string()), - to_value(value).unwrap_or_default(), - ); - } - Ok(Value::Object(map)) - } -} diff --git a/src/types/external/json_object/hashmap.rs b/src/types/external/json_object/hashmap.rs deleted file mode 100644 index 925761656..000000000 --- a/src/types/external/json_object/hashmap.rs +++ /dev/null @@ -1,121 +0,0 @@ -use std::{ - borrow::Cow, - collections::HashMap, - fmt::Display, - hash::{BuildHasher, Hash}, - str::FromStr, -}; - -use async_graphql_parser::{Positioned, types::Field}; -use async_graphql_value::{from_value, to_value}; -use indexmap::IndexMap; -use serde::{Serialize, de::DeserializeOwned}; - -use crate::{ - ContextSelectionSet, InputType, InputValueError, InputValueResult, Name, OutputType, - ServerResult, Value, - registry::{MetaType, MetaTypeId, Registry}, -}; - -impl InputType for HashMap -where - K: ToString + FromStr + Eq + Hash + Send + Sync, - K::Err: Display, - V: Serialize + DeserializeOwned + Send + Sync, - S: Default + BuildHasher + Send + Sync, -{ - type RawValueType = Self; - - fn type_name() -> Cow<'static, str> { - Cow::Borrowed("JSONObject") - } - - fn create_type_info(registry: &mut Registry) -> String { - registry.create_input_type::(MetaTypeId::Scalar, |_| MetaType::Scalar { - name: ::type_name().to_string(), - description: Some("A scalar that can represent any JSON Object value.".to_string()), - is_valid: None, - visible: None, - inaccessible: false, - tags: Default::default(), - specified_by_url: None, - directive_invocations: Default::default(), - requires_scopes: Default::default(), - }) - } - - fn parse(value: Option) -> InputValueResult { - let value = value.unwrap_or_default(); - match value { - Value::Object(map) => map - .into_iter() - .map(|(name, value)| { - Ok(( - K::from_str(&name).map_err(|err| { - InputValueError::::custom(format!("object key: {}", err)) - })?, - from_value(value).map_err(|err| format!("object value: {}", err))?, - )) - }) - .collect::>() - .map_err(InputValueError::propagate), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - let mut map = IndexMap::new(); - for (name, value) in self { - map.insert( - Name::new(name.to_string()), - to_value(value).unwrap_or_default(), - ); - } - Value::Object(map) - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - Some(self) - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for HashMap -where - K: ToString + Eq + Hash + Send + Sync, - V: Serialize + Send + Sync, - S: Send + Sync, -{ - fn type_name() -> Cow<'static, str> { - Cow::Borrowed("JSONObject") - } - - fn create_type_info(registry: &mut Registry) -> String { - registry.create_output_type::(MetaTypeId::Scalar, |_| MetaType::Scalar { - name: ::type_name().to_string(), - description: Some("A scalar that can represent any JSON Object value.".to_string()), - is_valid: None, - visible: None, - inaccessible: false, - tags: Default::default(), - specified_by_url: None, - directive_invocations: Default::default(), - requires_scopes: Default::default(), - }) - } - - async fn resolve( - &self, - _ctx: &ContextSelectionSet<'_>, - _field: &Positioned, - ) -> ServerResult { - let mut map = IndexMap::new(); - for (name, value) in self { - map.insert( - Name::new(name.to_string()), - to_value(value).unwrap_or_default(), - ); - } - Ok(Value::Object(map)) - } -} diff --git a/src/types/external/json_object/mod.rs b/src/types/external/json_object/mod.rs deleted file mode 100644 index 1a7507945..000000000 --- a/src/types/external/json_object/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod btreemap; -#[cfg(feature = "hashbrown")] -mod hashbrown_hashmap; -mod hashmap; diff --git a/src/types/external/list/array.rs b/src/types/external/list/array.rs deleted file mode 100644 index 8a5596576..000000000 --- a/src/types/external/list/array.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::borrow::Cow; - -use crate::{ - ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType, Positioned, - ServerResult, Value, parser::types::Field, registry, resolver_utils::resolve_list, -}; - -impl InputType for [T; N] { - type RawValueType = Self; - - fn type_name() -> Cow<'static, str> { - Cow::Owned(format!("[{}]", T::qualified_type_name())) - } - - fn qualified_type_name() -> String { - format!("[{}]!", T::qualified_type_name()) - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - T::create_type_info(registry); - Self::qualified_type_name() - } - - fn parse(value: Option) -> InputValueResult { - if let Some(Value::List(values)) = value { - let items: Vec = values - .into_iter() - .map(|value| InputType::parse(Some(value))) - .collect::>() - .map_err(InputValueError::propagate)?; - let len = items.len(); - items.try_into().map_err(|_| { - InputValueError::custom(format!( - "Expected input type \"[{}; {}]\", found [{}; {}].", - T::type_name(), - N, - T::type_name(), - len - )) - }) - } else { - Err(InputValueError::expected_type(value.unwrap_or_default())) - } - } - - fn to_value(&self) -> Value { - Value::List(self.iter().map(InputType::to_value).collect()) - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - Some(self) - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for [T; N] { - fn type_name() -> Cow<'static, str> { - Cow::Owned(format!("[{}]", T::qualified_type_name())) - } - - fn qualified_type_name() -> String { - format!("[{}]!", T::qualified_type_name()) - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - T::create_type_info(registry); - Self::qualified_type_name() - } - - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { - resolve_list(ctx, field, self.iter(), Some(self.len())).await - } -} diff --git a/src/types/external/list/btree_set.rs b/src/types/external/list/btree_set.rs deleted file mode 100644 index a35b212c8..000000000 --- a/src/types/external/list/btree_set.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::{borrow::Cow, collections::BTreeSet}; - -use crate::{ - ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType, Positioned, - ServerResult, Value, parser::types::Field, registry, resolver_utils::resolve_list, -}; - -impl InputType for BTreeSet { - type RawValueType = Self; - - fn type_name() -> Cow<'static, str> { - Cow::Owned(format!("[{}]", T::qualified_type_name())) - } - - fn qualified_type_name() -> String { - format!("[{}]!", T::qualified_type_name()) - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - T::create_type_info(registry); - Self::qualified_type_name() - } - - fn parse(value: Option) -> InputValueResult { - match value.unwrap_or_default() { - Value::List(values) => values - .into_iter() - .map(|value| InputType::parse(Some(value))) - .collect::>() - .map_err(InputValueError::propagate), - value => Ok({ - let mut result = Self::default(); - result.insert(InputType::parse(Some(value)).map_err(InputValueError::propagate)?); - result - }), - } - } - - fn to_value(&self) -> Value { - Value::List(self.iter().map(InputType::to_value).collect()) - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - Some(self) - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for BTreeSet { - fn type_name() -> Cow<'static, str> { - Cow::Owned(format!("[{}]", T::qualified_type_name())) - } - - fn qualified_type_name() -> String { - format!("[{}]!", T::qualified_type_name()) - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - T::create_type_info(registry); - Self::qualified_type_name() - } - - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { - resolve_list(ctx, field, self, Some(self.len())).await - } -} diff --git a/src/types/external/list/hash_set.rs b/src/types/external/list/hash_set.rs deleted file mode 100644 index d8c9bd0f9..000000000 --- a/src/types/external/list/hash_set.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::{borrow::Cow, collections::HashSet, hash::Hash}; - -use crate::{ - ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType, Positioned, - Result, ServerResult, Value, parser::types::Field, registry, resolver_utils::resolve_list, -}; - -impl InputType for HashSet { - type RawValueType = Self; - - fn type_name() -> Cow<'static, str> { - Cow::Owned(format!("[{}]", T::qualified_type_name())) - } - - fn qualified_type_name() -> String { - format!("[{}]!", T::qualified_type_name()) - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - T::create_type_info(registry); - Self::qualified_type_name() - } - - fn parse(value: Option) -> InputValueResult { - match value.unwrap_or_default() { - Value::List(values) => values - .into_iter() - .map(|value| InputType::parse(Some(value))) - .collect::>() - .map_err(InputValueError::propagate), - value => Ok({ - let mut result = Self::default(); - result.insert(InputType::parse(Some(value)).map_err(InputValueError::propagate)?); - result - }), - } - } - - fn to_value(&self) -> Value { - Value::List(self.iter().map(InputType::to_value).collect()) - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - Some(self) - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for HashSet { - fn type_name() -> Cow<'static, str> { - Cow::Owned(format!("[{}]", T::qualified_type_name())) - } - - fn qualified_type_name() -> String { - format!("[{}]!", T::qualified_type_name()) - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - T::create_type_info(registry); - Self::qualified_type_name() - } - - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { - resolve_list(ctx, field, self, Some(self.len())).await - } -} diff --git a/src/types/external/list/hashbrown_hash_set.rs b/src/types/external/list/hashbrown_hash_set.rs deleted file mode 100644 index 63895a9ec..000000000 --- a/src/types/external/list/hashbrown_hash_set.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::{borrow::Cow, collections::HashSet as StdHashSet, hash::Hash}; - -use hashbrown::HashSet; - -use crate::{ - ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType, Positioned, - Result, ServerResult, Value, parser::types::Field, registry, resolver_utils::resolve_list, -}; - -impl InputType for HashSet { - type RawValueType = Self; - - fn type_name() -> Cow<'static, str> { - as InputType>::type_name() - } - - fn qualified_type_name() -> String { - as InputType>::qualified_type_name() - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - as InputType>::create_type_info(registry) - } - - fn parse(value: Option) -> InputValueResult { - match value.unwrap_or_default() { - Value::List(values) => values - .into_iter() - .map(|value| InputType::parse(Some(value))) - .collect::>() - .map_err(InputValueError::propagate), - value => Ok({ - let mut result = Self::default(); - result.insert(InputType::parse(Some(value)).map_err(InputValueError::propagate)?); - result - }), - } - } - - fn to_value(&self) -> Value { - Value::List(self.iter().map(InputType::to_value).collect()) - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - Some(self) - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for HashSet { - fn type_name() -> Cow<'static, str> { - as OutputType>::type_name() - } - - fn qualified_type_name() -> String { - as OutputType>::qualified_type_name() - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - as OutputType>::create_type_info(registry) - } - - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { - resolve_list(ctx, field, self, Some(self.len())).await - } -} diff --git a/src/types/external/list/linked_list.rs b/src/types/external/list/linked_list.rs deleted file mode 100644 index dabbb5150..000000000 --- a/src/types/external/list/linked_list.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::{borrow::Cow, collections::LinkedList}; - -use crate::{ - ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType, Positioned, - ServerResult, Value, parser::types::Field, registry, resolver_utils::resolve_list, -}; - -impl InputType for LinkedList { - type RawValueType = Self; - - fn type_name() -> Cow<'static, str> { - Cow::Owned(format!("[{}]", T::qualified_type_name())) - } - - fn qualified_type_name() -> String { - format!("[{}]!", T::qualified_type_name()) - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - T::create_type_info(registry); - Self::qualified_type_name() - } - - fn parse(value: Option) -> InputValueResult { - match value.unwrap_or_default() { - Value::List(values) => values - .into_iter() - .map(|value| InputType::parse(Some(value))) - .collect::>() - .map_err(InputValueError::propagate), - value => Ok({ - let mut result = Self::default(); - result - .push_front(InputType::parse(Some(value)).map_err(InputValueError::propagate)?); - result - }), - } - } - - fn to_value(&self) -> Value { - Value::List(self.iter().map(InputType::to_value).collect()) - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - Some(self) - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for LinkedList { - fn type_name() -> Cow<'static, str> { - Cow::Owned(format!("[{}]", T::qualified_type_name())) - } - - fn qualified_type_name() -> String { - format!("[{}]!", T::qualified_type_name()) - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - T::create_type_info(registry); - Self::qualified_type_name() - } - - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { - resolve_list(ctx, field, self, Some(self.len())).await - } -} diff --git a/src/types/external/list/mod.rs b/src/types/external/list/mod.rs deleted file mode 100644 index d80bb0c65..000000000 --- a/src/types/external/list/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod array; -mod btree_set; -mod hash_set; -#[cfg(feature = "hashbrown")] -mod hashbrown_hash_set; -mod linked_list; -mod slice; -mod vec; -mod vec_deque; diff --git a/src/types/external/list/slice.rs b/src/types/external/list/slice.rs deleted file mode 100644 index 6bb36560b..000000000 --- a/src/types/external/list/slice.rs +++ /dev/null @@ -1,112 +0,0 @@ -use std::{borrow::Cow, sync::Arc}; - -use crate::{ - ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType, Positioned, - ServerResult, Value, parser::types::Field, registry, resolver_utils::resolve_list, -}; - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl<'a, T: OutputType + 'a> OutputType for &'a [T] { - fn type_name() -> Cow<'static, str> { - Cow::Owned(format!("[{}]", T::qualified_type_name())) - } - - fn qualified_type_name() -> String { - format!("[{}]!", T::qualified_type_name()) - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - T::create_type_info(registry); - Self::qualified_type_name() - } - - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { - resolve_list(ctx, field, self.iter(), Some(self.len())).await - } -} - -macro_rules! impl_output_slice_for_smart_ptr { - ($ty:ty) => { - #[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] - impl OutputType for $ty { - fn type_name() -> Cow<'static, str> { - Cow::Owned(format!("[{}]", T::qualified_type_name())) - } - - fn qualified_type_name() -> String { - format!("[{}]!", T::qualified_type_name()) - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - T::create_type_info(registry); - Self::qualified_type_name() - } - - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { - resolve_list(ctx, field, self.iter(), Some(self.len())).await - } - } - }; -} - -impl_output_slice_for_smart_ptr!(Box<[T]>); -impl_output_slice_for_smart_ptr!(Arc<[T]>); - -macro_rules! impl_input_slice_for_smart_ptr { - ($ty:ty) => { - impl InputType for $ty { - type RawValueType = Self; - - fn type_name() -> Cow<'static, str> { - Cow::Owned(format!("[{}]", T::qualified_type_name())) - } - - fn qualified_type_name() -> String { - format!("[{}]!", T::qualified_type_name()) - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - T::create_type_info(registry); - Self::qualified_type_name() - } - - fn parse(value: Option) -> InputValueResult { - match value.unwrap_or_default() { - Value::List(values) => values - .into_iter() - .map(|value| InputType::parse(Some(value))) - .collect::>() - .map_err(InputValueError::propagate), - value => { - Ok( - vec![ - InputType::parse(Some(value)) - .map_err(InputValueError::propagate)?, - ] - .into(), - ) - } - } - } - - fn to_value(&self) -> Value { - Value::List(self.iter().map(InputType::to_value).collect()) - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - Some(self) - } - } - }; -} - -impl_input_slice_for_smart_ptr!(Box<[T]>); -impl_input_slice_for_smart_ptr!(Arc<[T]>); diff --git a/src/types/external/list/vec.rs b/src/types/external/list/vec.rs deleted file mode 100644 index 8ba495ec5..000000000 --- a/src/types/external/list/vec.rs +++ /dev/null @@ -1,68 +0,0 @@ -use std::borrow::Cow; - -use crate::{ - ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType, Positioned, - Result, ServerResult, Value, parser::types::Field, registry, resolver_utils::resolve_list, -}; - -impl InputType for Vec { - type RawValueType = Self; - - fn type_name() -> Cow<'static, str> { - Cow::Owned(format!("[{}]", T::qualified_type_name())) - } - - fn qualified_type_name() -> String { - format!("[{}]!", T::qualified_type_name()) - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - T::create_type_info(registry); - Self::qualified_type_name() - } - - fn parse(value: Option) -> InputValueResult { - match value.unwrap_or_default() { - Value::List(values) => values - .into_iter() - .map(|value| InputType::parse(Some(value))) - .collect::>() - .map_err(InputValueError::propagate), - value => Ok(vec![ - InputType::parse(Some(value)).map_err(InputValueError::propagate)?, - ]), - } - } - - fn to_value(&self) -> Value { - Value::List(self.iter().map(InputType::to_value).collect()) - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - Some(self) - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for Vec { - fn type_name() -> Cow<'static, str> { - Cow::Owned(format!("[{}]", T::qualified_type_name())) - } - - fn qualified_type_name() -> String { - format!("[{}]!", T::qualified_type_name()) - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - T::create_type_info(registry); - Self::qualified_type_name() - } - - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { - resolve_list(ctx, field, self, Some(self.len())).await - } -} diff --git a/src/types/external/list/vec_deque.rs b/src/types/external/list/vec_deque.rs deleted file mode 100644 index 9a73305c8..000000000 --- a/src/types/external/list/vec_deque.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::{borrow::Cow, collections::VecDeque}; - -use crate::{ - ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType, Positioned, - ServerResult, Value, parser::types::Field, registry, resolver_utils::resolve_list, -}; - -impl InputType for VecDeque { - type RawValueType = Self; - - fn type_name() -> Cow<'static, str> { - Cow::Owned(format!("[{}]", T::qualified_type_name())) - } - - fn qualified_type_name() -> String { - format!("[{}]!", T::qualified_type_name()) - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - T::create_type_info(registry); - Self::qualified_type_name() - } - - fn parse(value: Option) -> InputValueResult { - match value.unwrap_or_default() { - Value::List(values) => values - .into_iter() - .map(|value| InputType::parse(Some(value))) - .collect::>() - .map_err(InputValueError::propagate), - value => Ok({ - let mut result = Self::default(); - result - .push_back(InputType::parse(Some(value)).map_err(InputValueError::propagate)?); - result - }), - } - } - - fn to_value(&self) -> Value { - Value::List(self.iter().map(InputType::to_value).collect()) - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - Some(self) - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for VecDeque { - fn type_name() -> Cow<'static, str> { - Cow::Owned(format!("[{}]", T::qualified_type_name())) - } - - fn qualified_type_name() -> String { - format!("[{}]!", T::qualified_type_name()) - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - T::create_type_info(registry); - Self::qualified_type_name() - } - - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { - resolve_list(ctx, field, self, Some(self.len())).await - } -} diff --git a/src/types/external/mod.rs b/src/types/external/mod.rs deleted file mode 100644 index 00d0da69d..000000000 --- a/src/types/external/mod.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! Implementations of `Type`, `ScalarType`, etc on external types. - -mod bool; -mod bytes; -mod char; -mod cow; -mod floats; -mod integers; -mod json_object; -mod list; -mod non_zero_integers; -mod optional; -mod string; - -#[cfg(feature = "tokio-sync")] -mod tokio; - -#[cfg(feature = "bigdecimal")] -mod big_decimal; -#[cfg(feature = "bson")] -mod bson; -#[cfg(feature = "chrono-tz")] -mod chrono_tz; -#[cfg(feature = "chrono")] -mod datetime; -#[cfg(feature = "decimal")] -mod decimal; -#[cfg(feature = "chrono-duration")] -mod duration; -#[cfg(feature = "chrono")] -mod naive_time; -#[cfg(feature = "secrecy")] -mod secrecy; -#[cfg(feature = "smol_str")] -mod smol_str; -#[cfg(feature = "time")] -mod time_date; -#[cfg(feature = "time")] -mod time_offset_date_time; -#[cfg(feature = "time")] -mod time_primitive_date_time; -#[cfg(feature = "url")] -mod url; -#[cfg(feature = "uuid")] -mod uuid; diff --git a/src/types/external/naive_time.rs b/src/types/external/naive_time.rs deleted file mode 100644 index ee82b3a25..000000000 --- a/src/types/external/naive_time.rs +++ /dev/null @@ -1,64 +0,0 @@ -use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; - -use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; - -#[Scalar(internal)] -/// ISO 8601 calendar date without timezone. -/// Format: %Y-%m-%d -/// -/// # Examples -/// -/// * `1994-11-13` -/// * `2000-02-24` -impl ScalarType for NaiveDate { - fn parse(value: Value) -> InputValueResult { - match value { - Value::String(s) => Ok(NaiveDate::parse_from_str(&s, "%Y-%m-%d")?), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - Value::String(self.format("%Y-%m-%d").to_string()) - } -} - -#[Scalar(internal)] -/// ISO 8601 time without timezone. -/// Allows for the nanosecond precision and optional leap second representation. -/// Format: %H:%M:%S%.f -/// -/// # Examples -/// -/// * `08:59:60.123` -impl ScalarType for NaiveTime { - fn parse(value: Value) -> InputValueResult { - match value { - Value::String(s) => Ok(NaiveTime::parse_from_str(&s, "%H:%M:%S%.f")?), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - Value::String(self.format("%H:%M:%S%.f").to_string()) - } -} - -#[Scalar(internal)] -/// ISO 8601 combined date and time without timezone. -/// -/// # Examples -/// -/// * `2015-07-01T08:59:60.123`, -impl ScalarType for NaiveDateTime { - fn parse(value: Value) -> InputValueResult { - match value { - Value::String(s) => Ok(NaiveDateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S%.f")?), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - Value::String(self.format("%Y-%m-%dT%H:%M:%S%.f").to_string()) - } -} diff --git a/src/types/external/non_zero_integers.rs b/src/types/external/non_zero_integers.rs deleted file mode 100644 index 4aae024e9..000000000 --- a/src/types/external/non_zero_integers.rs +++ /dev/null @@ -1,308 +0,0 @@ -use std::num::{ - NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroIsize, NonZeroU8, NonZeroU16, NonZeroU32, - NonZeroU64, NonZeroUsize, -}; - -use crate::{InputValueError, InputValueResult, Number, Scalar, ScalarType, Value}; - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for NonZeroI8 { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_i64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - if n < i8::MIN as i64 || n > i8::MAX as i64 || n == 0 { - return Err(InputValueError::from(format!( - "Only integers from {} to {} or non zero are accepted.", - i8::MIN, - i8::MAX - ))); - } - Ok(NonZeroI8::new(n as i8).unwrap()) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_i64()) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(self.get() as i64)) - } -} - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for NonZeroI16 { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_i64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - if n < i16::MIN as i64 || n > i16::MAX as i64 || n == 0 { - return Err(InputValueError::from(format!( - "Only integers from {} to {} or non zero are accepted.", - i16::MIN, - i16::MAX - ))); - } - Ok(NonZeroI16::new(n as i16).unwrap()) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_i64()) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(self.get() as i64)) - } -} - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for NonZeroI32 { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_i64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - if n < i32::MIN as i64 || n > i32::MAX as i64 || n == 0 { - return Err(InputValueError::from(format!( - "Only integers from {} to {} or non zero are accepted.", - i32::MIN, - i32::MAX - ))); - } - Ok(NonZeroI32::new(n as i32).unwrap()) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_i64()) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(self.get() as i64)) - } -} - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for NonZeroI64 { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_i64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - if n == 0 { - return Err(InputValueError::from("Only non zero are accepted.")); - } - Ok(NonZeroI64::new(n).unwrap()) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_i64()) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(self.get())) - } -} - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for NonZeroIsize { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_i64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - if n < isize::MIN as i64 || n > isize::MAX as i64 || n == 0 { - return Err(InputValueError::from(format!( - "Only integers from {} to {} or non zero are accepted.", - isize::MIN, - isize::MAX - ))); - } - Ok(NonZeroIsize::new(n as isize).unwrap()) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_i64()) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(self.get() as i64)) - } -} - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for NonZeroU8 { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_u64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - if n > u8::MAX as u64 || n == 0 { - return Err(InputValueError::from(format!( - "Only integers from {} to {} or non zero are accepted.", - 1, - u8::MAX - ))); - } - Ok(NonZeroU8::new(n as u8).unwrap()) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_i64()) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(self.get() as u64)) - } -} - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for NonZeroU16 { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_u64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - if n > u16::MAX as u64 || n == 0 { - return Err(InputValueError::from(format!( - "Only integers from {} to {} or non zero are accepted.", - 1, - u16::MAX - ))); - } - Ok(NonZeroU16::new(n as u16).unwrap()) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_i64()) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(self.get() as u64)) - } -} - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for NonZeroU32 { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_u64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - if n > u32::MAX as u64 || n == 0 { - return Err(InputValueError::from(format!( - "Only integers from {} to {} or non zero are accepted.", - 1, - u32::MAX - ))); - } - Ok(NonZeroU32::new(n as u32).unwrap()) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_i64()) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(self.get() as u64)) - } -} - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for NonZeroU64 { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_u64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - if n == 0 { - return Err(InputValueError::from("Only non zero are accepted.")); - } - Ok(NonZeroU64::new(n).unwrap()) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_i64()) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(self.get())) - } -} - -/// The `Int` scalar type represents non-fractional whole numeric values. -#[Scalar(internal, name = "Int")] -impl ScalarType for NonZeroUsize { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) => { - let n = n - .as_u64() - .ok_or_else(|| InputValueError::from("Invalid number"))?; - if n > usize::MAX as u64 || n == 0 { - return Err(InputValueError::from(format!( - "Only integers from {} to {} or non zero are accepted.", - 1, - usize::MAX - ))); - } - Ok(NonZeroUsize::new(n as usize).unwrap()) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::Number(n) if n.is_i64()) - } - - fn to_value(&self) -> Value { - Value::Number(Number::from(self.get() as u64)) - } -} diff --git a/src/types/external/optional.rs b/src/types/external/optional.rs deleted file mode 100644 index b4a5a8b52..000000000 --- a/src/types/external/optional.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::borrow::Cow; - -use crate::{ - ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType, Positioned, - ServerResult, Value, parser::types::Field, registry, -}; - -impl InputType for Option { - type RawValueType = T::RawValueType; - - fn type_name() -> Cow<'static, str> { - T::type_name() - } - - fn qualified_type_name() -> String { - T::type_name().to_string() - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - T::create_type_info(registry); - T::type_name().to_string() - } - - fn parse(value: Option) -> InputValueResult { - match value.unwrap_or_default() { - Value::Null => Ok(None), - value => Ok(Some( - T::parse(Some(value)).map_err(InputValueError::propagate)?, - )), - } - } - - fn to_value(&self) -> Value { - match self { - Some(value) => value.to_value(), - None => Value::Null, - } - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - match self { - Some(value) => value.as_raw_value(), - None => None, - } - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for Option { - fn type_name() -> Cow<'static, str> { - T::type_name() - } - - fn qualified_type_name() -> String { - T::type_name().to_string() - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - T::create_type_info(registry); - T::type_name().to_string() - } - - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { - if let Some(inner) = self { - match OutputType::resolve(inner, ctx, field).await { - Ok(value) => Ok(value), - Err(err) => { - ctx.add_error(err); - Ok(Value::Null) - } - } - } else { - Ok(Value::Null) - } - } -} - -#[cfg(test)] -mod tests { - use crate::InputType; - - #[test] - fn test_optional_type() { - assert_eq!(Option::::type_name(), "Int"); - assert_eq!(Option::::qualified_type_name(), "Int"); - assert_eq!(&Option::::type_name(), "Int"); - assert_eq!(&Option::::qualified_type_name(), "Int"); - } -} diff --git a/src/types/external/secrecy.rs b/src/types/external/secrecy.rs deleted file mode 100644 index 008503eb9..000000000 --- a/src/types/external/secrecy.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::borrow::Cow; - -use secrecy::{ExposeSecret, SecretBox, SecretString, zeroize::Zeroize}; - -use crate::{InputType, InputValueError, InputValueResult, Value, registry}; - -impl InputType for SecretBox { - type RawValueType = T::RawValueType; - - fn type_name() -> Cow<'static, str> { - T::type_name() - } - - fn qualified_type_name() -> String { - T::qualified_type_name() - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - T::create_type_info(registry) - } - - fn parse(value: Option) -> InputValueResult { - T::parse(value) - .map(|value| SecretBox::new(Box::new(value))) - .map_err(InputValueError::propagate) - } - - fn to_value(&self) -> Value { - Value::Null - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - self.expose_secret().as_raw_value() - } -} - -impl InputType for SecretString { - type RawValueType = str; - - fn type_name() -> Cow<'static, str> { - String::type_name() - } - - fn qualified_type_name() -> String { - String::qualified_type_name() - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - String::create_type_info(registry) - } - - fn parse(value: Option) -> InputValueResult { - String::parse(value) - .map(SecretString::from) - .map_err(InputValueError::propagate) - } - - fn to_value(&self) -> Value { - Value::Null - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - Some(self.expose_secret()) - } -} diff --git a/src/types/external/smol_str.rs b/src/types/external/smol_str.rs deleted file mode 100644 index 9a4df853b..000000000 --- a/src/types/external/smol_str.rs +++ /dev/null @@ -1,25 +0,0 @@ -use smol_str::SmolStr; - -use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; - -#[Scalar( - internal, - name = "SmolStr", - specified_by_url = "https://docs.rs/smol_str/latest/smol_str/struct.SmolStr.html" -)] -impl ScalarType for SmolStr { - fn parse(value: Value) -> InputValueResult { - match value { - Value::String(s) => Ok(SmolStr::new(s)), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::String(_)) - } - - fn to_value(&self) -> Value { - Value::String(self.to_string()) - } -} diff --git a/src/types/external/string.rs b/src/types/external/string.rs deleted file mode 100644 index b2b36264d..000000000 --- a/src/types/external/string.rs +++ /dev/null @@ -1,81 +0,0 @@ -use std::borrow::Cow; - -use crate::{ - ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType, Positioned, - Scalar, ScalarType, ServerResult, Value, parser::types::Field, registry, registry::Registry, -}; - -/// The `String` scalar type represents textual data, represented as UTF-8 -/// character sequences. The String type is most often used by GraphQL to -/// represent free-form human-readable text. -#[Scalar(internal)] -impl ScalarType for String { - fn parse(value: Value) -> InputValueResult { - match value { - Value::String(s) => Ok(s), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::String(_)) - } - - fn to_value(&self) -> Value { - Value::String(self.clone()) - } -} - -macro_rules! impl_input_string_for_smart_ptr { - ($ty:ty) => { - impl InputType for $ty { - type RawValueType = Self; - - fn type_name() -> Cow<'static, str> { - Cow::Borrowed("String") - } - - fn create_type_info(registry: &mut Registry) -> String { - ::create_type_info(registry) - } - - fn parse(value: Option) -> InputValueResult { - let value = value.unwrap_or_default(); - match value { - Value::String(s) => Ok(s.into()), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - Value::String(self.to_string()) - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - Some(self) - } - } - }; -} - -impl_input_string_for_smart_ptr!(Box); -impl_input_string_for_smart_ptr!(std::sync::Arc); - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for str { - fn type_name() -> Cow<'static, str> { - Cow::Borrowed("String") - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - ::create_type_info(registry) - } - - async fn resolve( - &self, - _: &ContextSelectionSet<'_>, - _field: &Positioned, - ) -> ServerResult { - Ok(Value::String(self.to_string())) - } -} diff --git a/src/types/external/time_date.rs b/src/types/external/time_date.rs deleted file mode 100644 index 71635e594..000000000 --- a/src/types/external/time_date.rs +++ /dev/null @@ -1,69 +0,0 @@ -use time::{Date, format_description::FormatItem, macros::format_description}; - -use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; - -const DATE_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day]"); - -/// ISO 8601 calendar date without timezone. -/// Format: %Y-%m-%d -/// -/// # Examples -/// -/// * `1994-11-13` -/// * `2000-02-24` -#[Scalar(internal, name = "Date")] -impl ScalarType for Date { - fn parse(value: Value) -> InputValueResult { - match &value { - Value::String(s) => Ok(Self::parse(s, &DATE_FORMAT)?), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - Value::String( - self.format(&DATE_FORMAT) - .unwrap_or_else(|e| panic!("Failed to format `Date`: {}", e)), - ) - } -} - -#[cfg(test)] -mod tests { - use time::{Date, macros::date}; - - use crate::{ScalarType, Value}; - - #[test] - fn test_date_to_value() { - let cases = [ - (date!(1994 - 11 - 13), "1994-11-13"), - (date!(2000 - 01 - 24), "2000-01-24"), - ]; - for (value, expected) in cases { - let value = value.to_value(); - - if let Value::String(s) = value { - assert_eq!(s, expected); - } else { - panic!( - "Unexpected Value type when formatting PrimitiveDateTime: {:?}", - value - ); - } - } - } - - #[test] - fn test_date_parse() { - let cases = [ - ("1994-11-13", date!(1994 - 11 - 13)), - ("2000-01-24", date!(2000 - 01 - 24)), - ]; - for (value, expected) in cases { - let value = Value::String(value.to_string()); - let parsed = ::parse(value).unwrap(); - assert_eq!(parsed, expected); - } - } -} diff --git a/src/types/external/time_offset_date_time.rs b/src/types/external/time_offset_date_time.rs deleted file mode 100644 index 08db5600f..000000000 --- a/src/types/external/time_offset_date_time.rs +++ /dev/null @@ -1,80 +0,0 @@ -use time::{OffsetDateTime, UtcOffset, format_description::well_known::Rfc3339}; - -use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; - -/// A datetime with timezone offset. -/// -/// The input is a string in RFC3339 format, e.g. "2022-01-12T04:00:19.12345Z" -/// or "2022-01-12T04:00:19+03:00". The output is also a string in RFC3339 -/// format, but it is always normalized to the UTC (Z) offset, e.g. -/// "2022-01-12T04:00:19.12345Z". -#[Scalar( - internal, - name = "DateTime", - specified_by_url = "https://datatracker.ietf.org/doc/html/rfc3339" -)] -impl ScalarType for OffsetDateTime { - fn parse(value: Value) -> InputValueResult { - match &value { - Value::String(s) => Ok(Self::parse(s, &Rfc3339)?), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - Value::String( - self.to_offset(UtcOffset::UTC) - .format(&Rfc3339) - .unwrap_or_else(|e| panic!("Failed to format `OffsetDateTime`: {}", e)), - ) - } -} - -#[cfg(test)] -mod tests { - use time::{OffsetDateTime, macros::datetime}; - - use crate::{ScalarType, Value}; - - #[test] - fn test_offset_date_time_to_value() { - let cases = [ - ( - datetime!(2022-01-12 07:30:19.12345 +3:30), - "2022-01-12T04:00:19.12345Z", - ), - (datetime!(2022-01-12 07:30:19-0), "2022-01-12T07:30:19Z"), - ]; - for (value, expected) in cases { - let value = value.to_value(); - - if let Value::String(s) = value { - assert_eq!(s, expected); - } else { - panic!( - "Unexpected Value type when formatting OffsetDateTime: {:?}", - value - ); - } - } - } - - #[test] - fn test_offset_date_time_parse() { - let cases = [ - ( - "2022-01-12T04:00:19.12345Z", - datetime!(2022-01-12 07:30:19.12345 +3:30), - ), - ( - "2022-01-12T23:22:19.12345-00:00", - datetime!(2022-01-12 23:22:19.12345-0), - ), - ]; - for (value, expected) in cases { - let value = Value::String(value.to_string()); - let parsed = ::parse(value).unwrap(); - assert_eq!(parsed, expected); - } - } -} diff --git a/src/types/external/time_primitive_date_time.rs b/src/types/external/time_primitive_date_time.rs deleted file mode 100644 index dcd6bd3d2..000000000 --- a/src/types/external/time_primitive_date_time.rs +++ /dev/null @@ -1,73 +0,0 @@ -use time::{PrimitiveDateTime, format_description::FormatItem, macros::format_description}; - -use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; - -const PRIMITIVE_DATE_TIME_FORMAT: &[FormatItem<'_>] = - format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond]"); - -/// A local datetime without timezone offset. -/// -/// The input/output is a string in ISO 8601 format without timezone, including -/// subseconds. E.g. "2022-01-12T07:30:19.12345". -#[Scalar(internal, name = "LocalDateTime")] -impl ScalarType for PrimitiveDateTime { - fn parse(value: Value) -> InputValueResult { - match &value { - Value::String(s) => Ok(Self::parse(s, &PRIMITIVE_DATE_TIME_FORMAT)?), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - Value::String( - self.format(&PRIMITIVE_DATE_TIME_FORMAT) - .unwrap_or_else(|e| panic!("Failed to format `PrimitiveDateTime`: {}", e)), - ) - } -} - -#[cfg(test)] -mod tests { - use time::{PrimitiveDateTime, macros::datetime}; - - use crate::{ScalarType, Value}; - - #[test] - fn test_primitive_date_time_to_value() { - let cases = [ - ( - datetime!(2022-01-12 07:30:19.12345), - "2022-01-12T07:30:19.12345", - ), - (datetime!(2022-01-12 07:30:19), "2022-01-12T07:30:19.0"), - ]; - for (value, expected) in cases { - let value = value.to_value(); - - if let Value::String(s) = value { - assert_eq!(s, expected); - } else { - panic!( - "Unexpected Value type when formatting PrimitiveDateTime: {:?}", - value - ); - } - } - } - - #[test] - fn test_primitive_date_time_parse() { - let cases = [ - ( - "2022-01-12T07:30:19.12345", - datetime!(2022-01-12 07:30:19.12345), - ), - ("2022-01-12T07:30:19.0", datetime!(2022-01-12 07:30:19)), - ]; - for (value, expected) in cases { - let value = Value::String(value.to_string()); - let parsed = ::parse(value).unwrap(); - assert_eq!(parsed, expected); - } - } -} diff --git a/src/types/external/tokio/mod.rs b/src/types/external/tokio/mod.rs deleted file mode 100644 index d086d5bd6..000000000 --- a/src/types/external/tokio/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod sync; diff --git a/src/types/external/tokio/sync/mod.rs b/src/types/external/tokio/sync/mod.rs deleted file mode 100644 index 8cf0884d0..000000000 --- a/src/types/external/tokio/sync/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod mutex; -mod rw_lock; diff --git a/src/types/external/tokio/sync/mutex.rs b/src/types/external/tokio/sync/mutex.rs deleted file mode 100644 index 7504e1caa..000000000 --- a/src/types/external/tokio/sync/mutex.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::borrow::Cow; - -use async_graphql_parser::types::Field; -use tokio::sync::Mutex; - -use crate::{ContextSelectionSet, OutputType, Positioned, ServerResult, Value, registry}; - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for Mutex { - fn type_name() -> Cow<'static, str> { - T::type_name() - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - ::create_type_info(registry) - } - - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { - self.lock().await.resolve(ctx, field).await - } -} diff --git a/src/types/external/tokio/sync/rw_lock.rs b/src/types/external/tokio/sync/rw_lock.rs deleted file mode 100644 index 5c613e84d..000000000 --- a/src/types/external/tokio/sync/rw_lock.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::borrow::Cow; - -use async_graphql_parser::types::Field; -use tokio::sync::RwLock; - -use crate::{ContextSelectionSet, OutputType, Positioned, ServerResult, Value, registry}; - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for RwLock { - fn type_name() -> Cow<'static, str> { - T::type_name() - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - ::create_type_info(registry) - } - - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - field: &Positioned, - ) -> ServerResult { - self.read().await.resolve(ctx, field).await - } -} diff --git a/src/types/external/url.rs b/src/types/external/url.rs deleted file mode 100644 index 110a6e234..000000000 --- a/src/types/external/url.rs +++ /dev/null @@ -1,18 +0,0 @@ -use url::Url; - -use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; - -#[Scalar(internal, specified_by_url = "http://url.spec.whatwg.org/")] -/// URL is a String implementing the [URL Standard](http://url.spec.whatwg.org/) -impl ScalarType for Url { - fn parse(value: Value) -> InputValueResult { - match value { - Value::String(s) => Ok(Url::parse(&s)?), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - Value::String(self.to_string()) - } -} diff --git a/src/types/external/uuid.rs b/src/types/external/uuid.rs deleted file mode 100644 index fd9b06236..000000000 --- a/src/types/external/uuid.rs +++ /dev/null @@ -1,29 +0,0 @@ -use uuid::Uuid; - -use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; - -#[Scalar( - internal, - name = "UUID", - specified_by_url = "http://tools.ietf.org/html/rfc4122" -)] -/// A UUID is a unique 128-bit number, stored as 16 octets. UUIDs are parsed as -/// Strings within GraphQL. UUIDs are used to assign unique identifiers to -/// entities without requiring a central allocating authority. -/// -/// # References -/// -/// * [Wikipedia: Universally Unique Identifier](http://en.wikipedia.org/wiki/Universally_unique_identifier) -/// * [RFC4122: A Universally Unique Identifier (UUID) URN Namespace](http://tools.ietf.org/html/rfc4122) -impl ScalarType for Uuid { - fn parse(value: Value) -> InputValueResult { - match value { - Value::String(s) => Ok(Uuid::parse_str(&s)?), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> Value { - Value::String(self.to_string()) - } -} diff --git a/src/types/id.rs b/src/types/id.rs deleted file mode 100644 index cc1b1543b..000000000 --- a/src/types/id.rs +++ /dev/null @@ -1,122 +0,0 @@ -use std::{ - num::ParseIntError, - ops::{Deref, DerefMut}, -}; - -use async_graphql_value::ConstValue; -#[cfg(feature = "bson")] -use bson::oid::{self, ObjectId}; -use serde::{Deserialize, Serialize}; - -use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; - -/// ID scalar -/// -/// The input is a `&str`, `String`, `usize` or `uuid::UUID`, and the output is -/// a string. -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Serialize, Deserialize, Default)] -#[serde(transparent)] -pub struct ID(pub String); - -impl AsRef for ID { - fn as_ref(&self) -> &str { - self.0.as_str() - } -} - -impl Deref for ID { - type Target = String; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for ID { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl From for ID { - fn from(value: T) -> Self { - ID(value.to_string()) - } -} - -impl From for String { - fn from(id: ID) -> Self { - id.0 - } -} - -impl From for ConstValue { - fn from(id: ID) -> Self { - ConstValue::String(id.0) - } -} - -macro_rules! try_from_integers { - ($($ty:ty),*) => { - $( - impl TryFrom for $ty { - type Error = ParseIntError; - - fn try_from(id: ID) -> Result { - id.0.parse() - } - } - )* - }; -} - -try_from_integers!( - i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, isize, usize -); - -#[cfg(feature = "uuid")] -impl TryFrom for uuid::Uuid { - type Error = uuid::Error; - - fn try_from(id: ID) -> Result { - uuid::Uuid::parse_str(&id.0) - } -} - -#[cfg(feature = "bson")] -impl TryFrom for ObjectId { - type Error = oid::Error; - - fn try_from(id: ID) -> std::result::Result { - ObjectId::parse_str(id.0) - } -} - -impl PartialEq<&str> for ID { - fn eq(&self, other: &&str) -> bool { - self.0.as_str() == *other - } -} - -#[Scalar(internal, name = "ID")] -impl ScalarType for ID { - fn parse(value: Value) -> InputValueResult { - match value { - Value::Number(n) if n.is_i64() => Ok(ID(n.to_string())), - Value::String(s) => Ok(ID(s)), - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - match value { - Value::Number(n) if n.is_i64() => true, - Value::String(_) => true, - _ => false, - } - } - - fn to_value(&self) -> Value { - Value::String(self.0.clone()) - } -} diff --git a/src/types/json.rs b/src/types/json.rs deleted file mode 100644 index b68e1d9a7..000000000 --- a/src/types/json.rs +++ /dev/null @@ -1,255 +0,0 @@ -use std::{ - borrow::Cow, - ops::{Deref, DerefMut}, -}; - -use serde::{Deserialize, Serialize, de::DeserializeOwned}; - -use crate::{ - ContextSelectionSet, InputType, InputValueResult, OutputType, Positioned, ServerResult, Value, - from_value, - parser::types::Field, - registry::{MetaType, MetaTypeId, Registry}, - to_value, -}; - -/// A scalar that can represent any JSON value. -/// -/// If the inner type cannot be serialized as JSON (e.g. it has non-string keys) -/// it will be `null`. -#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Hash, Default)] -#[serde(transparent)] -pub struct Json(pub T); - -impl Deref for Json { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Json { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl From for Json { - fn from(value: T) -> Self { - Self(value) - } -} - -impl InputType for Json { - type RawValueType = T; - - fn type_name() -> Cow<'static, str> { - Cow::Borrowed("JSON") - } - - fn create_type_info(registry: &mut Registry) -> String { - registry.create_input_type::, _>(MetaTypeId::Scalar, |_| MetaType::Scalar { - name: ::type_name().to_string(), - description: Some("A scalar that can represent any JSON value.".to_string()), - is_valid: None, - visible: None, - inaccessible: false, - tags: Default::default(), - specified_by_url: None, - directive_invocations: Default::default(), - requires_scopes: Default::default(), - }) - } - - fn parse(value: Option) -> InputValueResult { - Ok(from_value(value.unwrap_or_default())?) - } - - fn to_value(&self) -> Value { - Value::String(serde_json::to_string(&self.0).unwrap_or_default()) - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - Some(&self.0) - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for Json { - fn type_name() -> Cow<'static, str> { - Cow::Borrowed("JSON") - } - - fn create_type_info(registry: &mut Registry) -> String { - registry.create_output_type::, _>(MetaTypeId::Scalar, |_| MetaType::Scalar { - name: ::type_name().to_string(), - description: Some("A scalar that can represent any JSON value.".to_string()), - is_valid: None, - visible: None, - inaccessible: false, - tags: Default::default(), - specified_by_url: None, - directive_invocations: Default::default(), - requires_scopes: Default::default(), - }) - } - - async fn resolve( - &self, - _ctx: &ContextSelectionSet<'_>, - _field: &Positioned, - ) -> ServerResult { - Ok(to_value(&self.0).ok().unwrap_or_default()) - } -} - -impl InputType for serde_json::Value { - type RawValueType = serde_json::Value; - - fn type_name() -> Cow<'static, str> { - Cow::Borrowed("JSON") - } - - fn create_type_info(registry: &mut Registry) -> String { - registry.create_input_type::(MetaTypeId::Scalar, |_| { - MetaType::Scalar { - name: ::type_name().to_string(), - description: Some("A scalar that can represent any JSON value.".to_string()), - is_valid: None, - visible: None, - inaccessible: false, - tags: Default::default(), - specified_by_url: None, - directive_invocations: Default::default(), - requires_scopes: Default::default(), - } - }) - } - - fn parse(value: Option) -> InputValueResult { - Ok(from_value(value.unwrap_or_default())?) - } - - fn to_value(&self) -> Value { - Value::String(self.to_string()) - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - Some(&self) - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for serde_json::Value { - fn type_name() -> Cow<'static, str> { - Cow::Borrowed("JSON") - } - - fn create_type_info(registry: &mut Registry) -> String { - registry.create_output_type::(MetaTypeId::Scalar, |_| { - MetaType::Scalar { - name: ::type_name().to_string(), - description: Some("A scalar that can represent any JSON value.".to_string()), - is_valid: None, - visible: None, - inaccessible: false, - tags: Default::default(), - specified_by_url: None, - directive_invocations: Default::default(), - requires_scopes: Default::default(), - } - }) - } - - async fn resolve( - &self, - _ctx: &ContextSelectionSet<'_>, - _field: &Positioned, - ) -> ServerResult { - Ok(to_value(self).ok().unwrap_or_default()) - } -} - -#[cfg(test)] -mod test { - use std::collections::HashMap; - - use serde::{Deserialize, Serialize}; - - use crate::*; - - #[tokio::test] - async fn test_json_type() { - #[derive(Serialize, Deserialize)] - struct MyStruct { - a: i32, - b: i32, - c: HashMap, - } - - struct Query; - - #[Object(internal)] - impl Query { - async fn obj(&self, input: Json) -> Json { - input - } - } - - let query = r#"{ obj(input: { a: 1, b: 2, c: { a: 11, b: 22 } } ) }"#; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "obj": { - "a": 1, - "b": 2, - "c": { "a": 11, "b": 22 } - } - }) - ); - } - - #[tokio::test] - async fn test_json_type_for_serialize_only() { - #[derive(Serialize)] - struct MyStruct { - a: i32, - b: i32, - c: HashMap, - } - - struct Query; - - #[Object(internal)] - impl Query { - async fn obj(&self) -> Json { - MyStruct { - a: 1, - b: 2, - c: { - let mut values = HashMap::new(); - values.insert("a".to_string(), 11); - values.insert("b".to_string(), 22); - values - }, - } - .into() - } - } - - let query = r#"{ obj }"#; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "obj": { - "a": 1, - "b": 2, - "c": { "a": 11, "b": 22 } - } - }) - ); - } -} diff --git a/src/types/maybe_undefined.rs b/src/types/maybe_undefined.rs deleted file mode 100644 index 267f058ac..000000000 --- a/src/types/maybe_undefined.rs +++ /dev/null @@ -1,515 +0,0 @@ -use std::{borrow::Cow, ops::Deref}; - -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -use crate::{InputType, InputValueError, InputValueResult, Value, registry}; - -/// Similar to `Option`, but it has three states, `undefined`, `null` and `x`. -/// -/// **Reference:** -/// -/// # Examples -/// -/// ```rust -/// use async_graphql::*; -/// -/// struct Query; -/// -/// #[Object] -/// impl Query { -/// async fn value1(&self, input: MaybeUndefined) -> i32 { -/// if input.is_null() { -/// 1 -/// } else if input.is_undefined() { -/// 2 -/// } else { -/// input.take().unwrap() -/// } -/// } -/// } -/// -/// # tokio::runtime::Runtime::new().unwrap().block_on(async { -/// let schema = Schema::new(Query, EmptyMutation, EmptySubscription); -/// let query = r#" -/// { -/// v1:value1(input: 99) -/// v2:value1(input: null) -/// v3:value1 -/// }"#; -/// assert_eq!( -/// schema.execute(query).await.into_result().unwrap().data, -/// value!({ -/// "v1": 99, -/// "v2": 1, -/// "v3": 2, -/// }) -/// ); -/// # }); -/// ``` -#[allow(missing_docs)] -#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)] -pub enum MaybeUndefined { - Undefined, - Null, - Value(T), -} - -impl Default for MaybeUndefined { - fn default() -> Self { - Self::Undefined - } -} - -impl MaybeUndefined { - /// Returns true if the `MaybeUndefined` is undefined. - #[inline] - pub const fn is_undefined(&self) -> bool { - matches!(self, MaybeUndefined::Undefined) - } - - /// Returns true if the `MaybeUndefined` is null. - #[inline] - pub const fn is_null(&self) -> bool { - matches!(self, MaybeUndefined::Null) - } - - /// Returns true if the `MaybeUndefined` contains value. - #[inline] - pub const fn is_value(&self) -> bool { - matches!(self, MaybeUndefined::Value(_)) - } - - /// Borrow the value, returns `None` if the the `MaybeUndefined` is - /// `undefined` or `null`, otherwise returns `Some(T)`. - #[inline] - pub const fn value(&self) -> Option<&T> { - match self { - MaybeUndefined::Value(value) => Some(value), - _ => None, - } - } - - /// Converts the `MaybeUndefined` to `Option`. - #[inline] - pub fn take(self) -> Option { - match self { - MaybeUndefined::Value(value) => Some(value), - _ => None, - } - } - - /// Converts the `MaybeUndefined` to `Option>`. - #[inline] - pub const fn as_opt_ref(&self) -> Option> { - match self { - MaybeUndefined::Undefined => None, - MaybeUndefined::Null => Some(None), - MaybeUndefined::Value(value) => Some(Some(value)), - } - } - - /// Converts the `MaybeUndefined` to `Option>`. - #[inline] - pub fn as_opt_deref(&self) -> Option> - where - U: ?Sized, - T: Deref, - { - match self { - MaybeUndefined::Undefined => None, - MaybeUndefined::Null => Some(None), - MaybeUndefined::Value(value) => Some(Some(value.deref())), - } - } - - /// Returns `true` if the `MaybeUndefined` contains the given value. - #[inline] - pub fn contains_value(&self, x: &U) -> bool - where - U: PartialEq, - { - match self { - MaybeUndefined::Value(y) => x == y, - _ => false, - } - } - - /// Returns `true` if the `MaybeUndefined` contains the given nullable - /// value. - #[inline] - pub fn contains(&self, x: &Option) -> bool - where - U: PartialEq, - { - match self { - MaybeUndefined::Value(y) => matches!(x, Some(v) if v == y), - MaybeUndefined::Null => x.is_none(), - MaybeUndefined::Undefined => false, - } - } - - /// Maps a `MaybeUndefined` to `MaybeUndefined` by applying a function - /// to the contained nullable value - #[inline] - pub fn map) -> Option>(self, f: F) -> MaybeUndefined { - match self { - MaybeUndefined::Value(v) => match f(Some(v)) { - Some(v) => MaybeUndefined::Value(v), - None => MaybeUndefined::Null, - }, - MaybeUndefined::Null => match f(None) { - Some(v) => MaybeUndefined::Value(v), - None => MaybeUndefined::Null, - }, - MaybeUndefined::Undefined => MaybeUndefined::Undefined, - } - } - - /// Maps a `MaybeUndefined` to `MaybeUndefined` by applying a function - /// to the contained value - #[inline] - pub fn map_value U>(self, f: F) -> MaybeUndefined { - match self { - MaybeUndefined::Value(v) => MaybeUndefined::Value(f(v)), - MaybeUndefined::Null => MaybeUndefined::Null, - MaybeUndefined::Undefined => MaybeUndefined::Undefined, - } - } - - /// Update `value` if the `MaybeUndefined` is not undefined. - /// - /// # Example - /// - /// ```rust - /// use async_graphql::MaybeUndefined; - /// - /// let mut value = None; - /// - /// MaybeUndefined::Value(10i32).update_to(&mut value); - /// assert_eq!(value, Some(10)); - /// - /// MaybeUndefined::Undefined.update_to(&mut value); - /// assert_eq!(value, Some(10)); - /// - /// MaybeUndefined::Null.update_to(&mut value); - /// assert_eq!(value, None); - /// ``` - pub fn update_to(self, value: &mut Option) { - match self { - MaybeUndefined::Value(new) => *value = Some(new), - MaybeUndefined::Null => *value = None, - MaybeUndefined::Undefined => {} - }; - } -} - -impl InputType for MaybeUndefined { - type RawValueType = T::RawValueType; - - fn type_name() -> Cow<'static, str> { - T::type_name() - } - - fn qualified_type_name() -> String { - T::type_name().to_string() - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - T::create_type_info(registry); - T::type_name().to_string() - } - - fn parse(value: Option) -> InputValueResult { - match value { - None => Ok(MaybeUndefined::Undefined), - Some(Value::Null) => Ok(MaybeUndefined::Null), - Some(value) => Ok(MaybeUndefined::Value( - T::parse(Some(value)).map_err(InputValueError::propagate)?, - )), - } - } - - fn to_value(&self) -> Value { - match self { - MaybeUndefined::Value(value) => value.to_value(), - _ => Value::Null, - } - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - if let MaybeUndefined::Value(value) = self { - value.as_raw_value() - } else { - None - } - } -} - -impl MaybeUndefined> { - /// Transposes a `MaybeUndefined` of a [`Result`] into a [`Result`] of a - /// `MaybeUndefined`. - /// - /// [`MaybeUndefined::Undefined`] will be mapped to - /// [`Ok`]`(`[`MaybeUndefined::Undefined`]`)`. [`MaybeUndefined::Null`] - /// will be mapped to [`Ok`]`(`[`MaybeUndefined::Null`]`)`. - /// [`MaybeUndefined::Value`]`(`[`Ok`]`(_))` and - /// [`MaybeUndefined::Value`]`(`[`Err`]`(_))` will be mapped to - /// [`Ok`]`(`[`MaybeUndefined::Value`]`(_))` and [`Err`]`(_)`. - #[inline] - pub fn transpose(self) -> Result, E> { - match self { - MaybeUndefined::Undefined => Ok(MaybeUndefined::Undefined), - MaybeUndefined::Null => Ok(MaybeUndefined::Null), - MaybeUndefined::Value(Ok(v)) => Ok(MaybeUndefined::Value(v)), - MaybeUndefined::Value(Err(e)) => Err(e), - } - } -} - -impl Serialize for MaybeUndefined { - fn serialize(&self, serializer: S) -> Result { - match self { - MaybeUndefined::Value(value) => value.serialize(serializer), - _ => serializer.serialize_none(), - } - } -} - -impl<'de, T> Deserialize<'de> for MaybeUndefined -where - T: Deserialize<'de>, -{ - fn deserialize(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - Option::::deserialize(deserializer).map(|value| match value { - Some(value) => MaybeUndefined::Value(value), - None => MaybeUndefined::Null, - }) - } -} - -impl From> for Option> { - fn from(maybe_undefined: MaybeUndefined) -> Self { - match maybe_undefined { - MaybeUndefined::Undefined => None, - MaybeUndefined::Null => Some(None), - MaybeUndefined::Value(value) => Some(Some(value)), - } - } -} - -impl From>> for MaybeUndefined { - fn from(value: Option>) -> Self { - match value { - Some(Some(value)) => Self::Value(value), - Some(None) => Self::Null, - None => Self::Undefined, - } - } -} - -#[cfg(test)] -mod tests { - use serde::{Deserialize, Serialize}; - - use crate::*; - - #[test] - fn test_maybe_undefined_type() { - assert_eq!(MaybeUndefined::::type_name(), "Int"); - assert_eq!(MaybeUndefined::::qualified_type_name(), "Int"); - assert_eq!(&MaybeUndefined::::type_name(), "Int"); - assert_eq!(&MaybeUndefined::::qualified_type_name(), "Int"); - } - - #[test] - fn test_maybe_undefined_serde() { - assert_eq!( - to_value(MaybeUndefined::Value(100i32)).unwrap(), - value!(100) - ); - - assert_eq!( - from_value::>(value!(100)).unwrap(), - MaybeUndefined::Value(100) - ); - assert_eq!( - from_value::>(value!(null)).unwrap(), - MaybeUndefined::Null - ); - - #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] - struct A { - a: MaybeUndefined, - } - - assert_eq!( - to_value(&A { - a: MaybeUndefined::Value(100i32) - }) - .unwrap(), - value!({"a": 100}) - ); - - assert_eq!( - to_value(&A { - a: MaybeUndefined::Null, - }) - .unwrap(), - value!({ "a": null }) - ); - - assert_eq!( - to_value(&A { - a: MaybeUndefined::Undefined, - }) - .unwrap(), - value!({ "a": null }) - ); - - assert_eq!( - from_value::(value!({"a": 100})).unwrap(), - A { - a: MaybeUndefined::Value(100i32) - } - ); - - assert_eq!( - from_value::(value!({ "a": null })).unwrap(), - A { - a: MaybeUndefined::Null - } - ); - - assert_eq!( - from_value::(value!({})).unwrap(), - A { - a: MaybeUndefined::Null - } - ); - } - - #[test] - fn test_maybe_undefined_to_nested_option() { - assert_eq!(Option::>::from(MaybeUndefined::Undefined), None); - - assert_eq!( - Option::>::from(MaybeUndefined::Null), - Some(None) - ); - - assert_eq!( - Option::>::from(MaybeUndefined::Value(42)), - Some(Some(42)) - ); - } - - #[test] - fn test_as_opt_ref() { - let value = MaybeUndefined::::Undefined; - let r = value.as_opt_ref(); - assert_eq!(r, None); - - let value = MaybeUndefined::::Null; - let r = value.as_opt_ref(); - assert_eq!(r, Some(None)); - - let value = MaybeUndefined::::Value("abc".to_string()); - let r = value.as_opt_ref(); - assert_eq!(r, Some(Some(&"abc".to_string()))); - } - - #[test] - fn test_as_opt_deref() { - let value = MaybeUndefined::::Undefined; - let r = value.as_opt_deref(); - assert_eq!(r, None); - - let value = MaybeUndefined::::Null; - let r = value.as_opt_deref(); - assert_eq!(r, Some(None)); - - let value = MaybeUndefined::::Value("abc".to_string()); - let r = value.as_opt_deref(); - assert_eq!(r, Some(Some("abc"))); - } - - #[test] - fn test_contains_value() { - let test = "abc"; - - let mut value: MaybeUndefined = MaybeUndefined::Undefined; - assert!(!value.contains_value(&test)); - - value = MaybeUndefined::Null; - assert!(!value.contains_value(&test)); - - value = MaybeUndefined::Value("abc".to_string()); - assert!(value.contains_value(&test)); - } - - #[test] - fn test_contains() { - let test = Some("abc"); - let none: Option<&str> = None; - - let mut value: MaybeUndefined = MaybeUndefined::Undefined; - assert!(!value.contains(&test)); - assert!(!value.contains(&none)); - - value = MaybeUndefined::Null; - assert!(!value.contains(&test)); - assert!(value.contains(&none)); - - value = MaybeUndefined::Value("abc".to_string()); - assert!(value.contains(&test)); - assert!(!value.contains(&none)); - } - - #[test] - fn test_map_value() { - let mut value: MaybeUndefined = MaybeUndefined::Undefined; - assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Undefined); - - value = MaybeUndefined::Null; - assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Null); - - value = MaybeUndefined::Value(5); - assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Value(true)); - } - - #[test] - fn test_map() { - let mut value: MaybeUndefined = MaybeUndefined::Undefined; - assert_eq!(value.map(|v| Some(v.is_some())), MaybeUndefined::Undefined); - - value = MaybeUndefined::Null; - assert_eq!( - value.map(|v| Some(v.is_some())), - MaybeUndefined::Value(false) - ); - - value = MaybeUndefined::Value(5); - assert_eq!( - value.map(|v| Some(v.is_some())), - MaybeUndefined::Value(true) - ); - } - - #[test] - fn test_transpose() { - let mut value: MaybeUndefined> = MaybeUndefined::Undefined; - assert_eq!(value.transpose(), Ok(MaybeUndefined::Undefined)); - - value = MaybeUndefined::Null; - assert_eq!(value.transpose(), Ok(MaybeUndefined::Null)); - - value = MaybeUndefined::Value(Ok(5)); - assert_eq!(value.transpose(), Ok(MaybeUndefined::Value(5))); - - value = MaybeUndefined::Value(Err("error")); - assert_eq!(value.transpose(), Err("error")); - } -} diff --git a/src/types/merged_object.rs b/src/types/merged_object.rs deleted file mode 100644 index df7926ff4..000000000 --- a/src/types/merged_object.rs +++ /dev/null @@ -1,204 +0,0 @@ -use std::{borrow::Cow, pin::Pin}; - -use indexmap::IndexMap; - -use crate::{ - CacheControl, ContainerType, Context, ContextSelectionSet, OutputType, Positioned, Response, - ServerResult, SimpleObject, SubscriptionType, Value, - futures_util::stream::Stream, - parser::types::Field, - registry::{MetaType, MetaTypeId, Registry}, -}; - -#[doc(hidden)] -pub struct MergedObject(pub A, pub B); - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl ContainerType for MergedObject -where - A: ContainerType, - B: ContainerType, -{ - async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult> { - match self.0.resolve_field(ctx).await { - Ok(Some(value)) => Ok(Some(value)), - Ok(None) => self.1.resolve_field(ctx).await, - Err(err) => Err(err), - } - } - - async fn find_entity(&self, ctx: &Context<'_>, params: &Value) -> ServerResult> { - match self.0.find_entity(ctx, params).await { - Ok(Some(value)) => Ok(Some(value)), - Ok(None) => self.1.find_entity(ctx, params).await, - Err(err) => Err(err), - } - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for MergedObject -where - A: OutputType, - B: OutputType, -{ - fn type_name() -> Cow<'static, str> { - Cow::Owned(format!("{}_{}", A::type_name(), B::type_name())) - } - - fn create_type_info(registry: &mut Registry) -> String { - registry.create_output_type::(MetaTypeId::Object, |registry| { - let mut fields = IndexMap::new(); - let mut cc = CacheControl::default(); - - if let MetaType::Object { - fields: b_fields, - cache_control: b_cc, - .. - } = registry.create_fake_output_type::() - { - fields.extend(b_fields); - cc = cc.merge(&b_cc); - } - - if let MetaType::Object { - fields: a_fields, - cache_control: a_cc, - .. - } = registry.create_fake_output_type::() - { - fields.extend(a_fields); - cc = cc.merge(&a_cc); - } - - MetaType::Object { - name: Self::type_name().to_string(), - description: None, - fields, - cache_control: cc, - extends: false, - shareable: false, - resolvable: true, - keys: None, - visible: None, - inaccessible: false, - interface_object: false, - tags: Default::default(), - is_subscription: false, - rust_typename: Some(std::any::type_name::()), - directive_invocations: Default::default(), - requires_scopes: Default::default(), - } - }) - } - - async fn resolve( - &self, - _ctx: &ContextSelectionSet<'_>, - _field: &Positioned, - ) -> ServerResult { - unreachable!() - } -} - -impl SubscriptionType for MergedObject -where - A: SubscriptionType, - B: SubscriptionType, -{ - fn type_name() -> Cow<'static, str> { - Cow::Owned(format!("{}_{}", A::type_name(), B::type_name())) - } - - fn create_type_info(registry: &mut Registry) -> String { - registry.create_subscription_type::(|registry| { - let mut fields = IndexMap::new(); - let mut cc = CacheControl::default(); - - if let MetaType::Object { - fields: b_fields, - cache_control: b_cc, - .. - } = registry.create_fake_subscription_type::() - { - fields.extend(b_fields); - cc = cc.merge(&b_cc); - } - - if let MetaType::Object { - fields: a_fields, - cache_control: a_cc, - .. - } = registry.create_fake_subscription_type::() - { - fields.extend(a_fields); - cc = cc.merge(&a_cc); - } - - MetaType::Object { - name: Self::type_name().to_string(), - description: None, - fields, - cache_control: cc, - extends: false, - shareable: false, - resolvable: true, - keys: None, - visible: None, - inaccessible: false, - interface_object: false, - tags: Default::default(), - is_subscription: false, - rust_typename: Some(std::any::type_name::()), - directive_invocations: Default::default(), - requires_scopes: Default::default(), - } - }) - } - - fn create_field_stream<'a>( - &'a self, - _ctx: &'a Context<'_>, - ) -> Option + Send + 'a>>> { - unreachable!() - } -} - -#[doc(hidden)] -#[derive(SimpleObject, Default)] -#[graphql(internal, fake)] -pub struct MergedObjectTail; - -impl SubscriptionType for MergedObjectTail { - fn type_name() -> Cow<'static, str> { - Cow::Borrowed("MergedSubscriptionTail") - } - - fn create_type_info(registry: &mut Registry) -> String { - registry.create_subscription_type::(|_| MetaType::Object { - name: "MergedSubscriptionTail".to_string(), - description: None, - fields: Default::default(), - cache_control: Default::default(), - extends: false, - shareable: false, - resolvable: true, - keys: None, - visible: None, - inaccessible: false, - interface_object: false, - tags: Default::default(), - is_subscription: false, - rust_typename: Some(std::any::type_name::()), - directive_invocations: Default::default(), - requires_scopes: Default::default(), - }) - } - - fn create_field_stream<'a>( - &'a self, - _ctx: &'a Context<'_>, - ) -> Option + Send + 'a>>> { - unreachable!() - } -} diff --git a/src/types/mod.rs b/src/types/mod.rs deleted file mode 100644 index f2fddc689..000000000 --- a/src/types/mod.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Useful GraphQL types. - -pub mod connection; - -mod any; -mod empty_mutation; -mod empty_subscription; -mod id; -mod json; -mod maybe_undefined; -mod merged_object; -mod query_root; -#[cfg(feature = "string_number")] -mod string_number; -mod upload; - -mod external; - -pub use any::Any; -pub use empty_mutation::EmptyMutation; -pub use empty_subscription::EmptySubscription; -pub use id::ID; -pub use json::Json; -pub use maybe_undefined::MaybeUndefined; -pub use merged_object::{MergedObject, MergedObjectTail}; -pub(crate) use query_root::QueryRoot; -#[cfg(feature = "string_number")] -pub use string_number::StringNumber; -pub use upload::{Upload, UploadValue}; diff --git a/src/types/query_root.rs b/src/types/query_root.rs deleted file mode 100644 index c458a1081..000000000 --- a/src/types/query_root.rs +++ /dev/null @@ -1,134 +0,0 @@ -use std::borrow::Cow; - -use crate::{ - Any, Context, ContextSelectionSet, ObjectType, OutputType, Positioned, ServerError, - ServerResult, SimpleObject, Value, - model::{__Schema, __Type}, - parser::types::Field, - registry::{self, SDLExportOptions}, - resolver_utils::{ContainerType, resolve_container}, - schema::IntrospectionMode, -}; - -/// Federation service -#[derive(SimpleObject)] -#[graphql(internal, name = "_Service")] -struct Service { - sdl: Option, -} - -pub(crate) struct QueryRoot { - pub(crate) inner: T, -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl ContainerType for QueryRoot { - async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult> { - if matches!( - ctx.schema_env.registry.introspection_mode, - IntrospectionMode::Enabled | IntrospectionMode::IntrospectionOnly - ) && matches!( - ctx.query_env.introspection_mode, - IntrospectionMode::Enabled | IntrospectionMode::IntrospectionOnly, - ) { - if ctx.item.node.name.node == "__schema" { - let mut ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set); - ctx_obj.is_for_introspection = true; - let visible_types = ctx.schema_env.registry.find_visible_types(ctx); - return OutputType::resolve( - &__Schema::new(&ctx.schema_env.registry, &visible_types), - &ctx_obj, - ctx.item, - ) - .await - .map(Some); - } else if ctx.item.node.name.node == "__type" { - let (_, type_name) = ctx.param_value::("name", None)?; - let mut ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set); - ctx_obj.is_for_introspection = true; - let visible_types = ctx.schema_env.registry.find_visible_types(ctx); - return OutputType::resolve( - &ctx.schema_env - .registry - .types - .get(&type_name) - .filter(|_| visible_types.contains(type_name.as_str())) - .map(|ty| __Type::new_simple(&ctx.schema_env.registry, &visible_types, ty)), - &ctx_obj, - ctx.item, - ) - .await - .map(Some); - } - } - - if ctx.schema_env.registry.introspection_mode == IntrospectionMode::IntrospectionOnly - || ctx.query_env.introspection_mode == IntrospectionMode::IntrospectionOnly - { - return Ok(None); - } - - if ctx.schema_env.registry.enable_federation || ctx.schema_env.registry.has_entities() { - if ctx.item.node.name.node == "_entities" { - let (_, representations) = ctx.param_value::>("representations", None)?; - let res = futures_util::future::try_join_all(representations.iter().map( - |item| async move { - self.inner.find_entity(ctx, &item.0).await?.ok_or_else(|| { - ServerError::new("Entity not found.", Some(ctx.item.pos)) - }) - }, - )) - .await?; - return Ok(Some(Value::List(res))); - } else if ctx.item.node.name.node == "_service" { - let mut ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set); - ctx_obj.is_for_introspection = true; - return OutputType::resolve( - &Service { - sdl: Some( - ctx.schema_env.registry.export_sdl( - SDLExportOptions::new().federation().compose_directive(), - ), - ), - }, - &ctx_obj, - ctx.item, - ) - .await - .map(Some); - } - } - - self.inner.resolve_field(ctx).await - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl OutputType for QueryRoot { - fn type_name() -> Cow<'static, str> { - T::type_name() - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - let root = T::create_type_info(registry); - - if matches!( - registry.introspection_mode, - IntrospectionMode::Enabled | IntrospectionMode::IntrospectionOnly - ) { - registry.create_introspection_types(); - } - - root - } - - async fn resolve( - &self, - ctx: &ContextSelectionSet<'_>, - _field: &Positioned, - ) -> ServerResult { - resolve_container(ctx, self).await - } -} - -impl ObjectType for QueryRoot {} diff --git a/src/types/string_number.rs b/src/types/string_number.rs deleted file mode 100644 index a92052dbc..000000000 --- a/src/types/string_number.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::fmt::Display; - -use num_traits::Num; -use serde::{Deserialize, Serialize}; - -use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; - -/// A numeric value represented by a string. -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)] -#[serde(transparent)] -#[cfg_attr(docsrs, doc(cfg(feature = "string_number")))] -pub struct StringNumber(pub T); - -impl Default for StringNumber { - #[inline] - fn default() -> Self { - Self(Default::default()) - } -} - -#[Scalar(internal)] -impl ScalarType for StringNumber -where - ::FromStrRadixErr: Display, -{ - fn parse(value: Value) -> InputValueResult { - match value { - Value::String(s) => { - let n = T::from_str_radix(&s, 10) - .map_err(|err| InputValueError::custom(err.to_string()))?; - Ok(StringNumber(n)) - } - _ => Err(InputValueError::expected_type(value)), - } - } - - fn is_valid(value: &Value) -> bool { - matches!(value, Value::String(_)) - } - - fn to_value(&self) -> Value { - Value::String(self.0.to_string()) - } -} - -#[cfg(test)] -mod test { - use crate::*; - - #[tokio::test] - async fn test_string_number() { - struct Query; - - #[Object(internal)] - impl Query { - async fn value(&self, n: StringNumber) -> StringNumber { - n - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute( - r#"{ - value1: value(n: "100") - value2: value(n: "-100") - value3: value(n: "0") - value4: value(n: "1") - }"# - ) - .await - .into_result() - .unwrap() - .data, - value!({ - "value1": "100", - "value2": "-100", - "value3": "0", - "value4": "1", - }) - ); - } -} diff --git a/src/types/upload.rs b/src/types/upload.rs deleted file mode 100644 index e6a21e9ac..000000000 --- a/src/types/upload.rs +++ /dev/null @@ -1,193 +0,0 @@ -use std::{borrow::Cow, io::Read, ops::Deref, sync::Arc}; - -#[cfg(feature = "unblock")] -use futures_util::io::AsyncRead; - -use crate::{ - Context, InputType, InputValueError, InputValueResult, Value, registry, registry::MetaTypeId, -}; - -/// A file upload value. -pub struct UploadValue { - /// The name of the file. - pub filename: String, - /// The content type of the file. - pub content_type: Option, - /// The file data. - #[cfg(feature = "tempfile")] - pub content: std::fs::File, - /// The file data. - #[cfg(not(feature = "tempfile"))] - pub content: bytes::Bytes, -} - -impl UploadValue { - /// Attempt to clone the upload value. This type's `Clone` implementation - /// simply calls this and panics on failure. - /// - /// # Errors - /// - /// Fails if cloning the inner `File` fails. - pub fn try_clone(&self) -> std::io::Result { - #[cfg(feature = "tempfile")] - { - Ok(Self { - filename: self.filename.clone(), - content_type: self.content_type.clone(), - content: self.content.try_clone()?, - }) - } - - #[cfg(not(feature = "tempfile"))] - { - Ok(Self { - filename: self.filename.clone(), - content_type: self.content_type.clone(), - content: self.content.clone(), - }) - } - } - - /// Convert to a `Read`. - /// - /// **Note**: this is a *synchronous/blocking* reader. - pub fn into_read(self) -> impl Read + Sync + Send + 'static { - #[cfg(feature = "tempfile")] - { - self.content - } - - #[cfg(not(feature = "tempfile"))] - { - std::io::Cursor::new(self.content) - } - } - - /// Convert to a `AsyncRead`. - #[cfg(feature = "unblock")] - #[cfg_attr(docsrs, doc(cfg(feature = "unblock")))] - pub fn into_async_read(self) -> impl AsyncRead + Sync + Send + 'static { - #[cfg(feature = "tempfile")] - { - blocking::Unblock::new(self.content) - } - - #[cfg(not(feature = "tempfile"))] - { - std::io::Cursor::new(self.content) - } - } - - /// Returns the size of the file, in bytes. - pub fn size(&self) -> std::io::Result { - #[cfg(feature = "tempfile")] - { - self.content.metadata().map(|meta| meta.len()) - } - - #[cfg(not(feature = "tempfile"))] - { - Ok(self.content.len() as u64) - } - } -} - -/// Uploaded file -/// -/// **Reference:** -/// -/// -/// Graphql supports file uploads via `multipart/form-data`. -/// Enable this feature by accepting an argument of type `Upload` (single file) -/// or `Vec` (multiple files) in your mutation like in the example blow. -/// -/// -/// # Example -/// *[Full Example]()* -/// -/// ``` -/// use async_graphql::*; -/// -/// struct Mutation; -/// -/// #[Object] -/// impl Mutation { -/// async fn upload(&self, ctx: &Context<'_>, file: Upload) -> bool { -/// println!("upload: filename={}", file.value(ctx).unwrap().filename); -/// true -/// } -/// } -/// ``` -/// # Example Curl Request -/// -/// Assuming you have defined your Mutation like in the example above, -/// you can now upload a file `myFile.txt` with the below curl command: -/// -/// ```curl -/// curl 'localhost:8000' \ -/// --form 'operations={ -/// "query": "mutation ($file: Upload!) { upload(file: $file) }", -/// "variables": { "file": null }}' \ -/// --form 'map={ "0": ["variables.file"] }' \ -/// --form '0=@myFile.txt' -/// ``` -#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub struct Upload(pub usize); - -impl Upload { - /// Get the upload value. - pub fn value(&self, ctx: &Context<'_>) -> std::io::Result { - ctx.query_env.uploads[self.0].try_clone() - } -} - -impl Deref for Upload { - type Target = usize; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl InputType for Upload { - type RawValueType = Self; - - fn type_name() -> Cow<'static, str> { - Cow::Borrowed("Upload") - } - - fn create_type_info(registry: &mut registry::Registry) -> String { - registry.create_input_type::(MetaTypeId::Scalar, |_| registry::MetaType::Scalar { - name: Self::type_name().to_string(), - description: None, - is_valid: Some(Arc::new(|value| matches!(value, Value::String(_)))), - visible: None, - inaccessible: false, - tags: Default::default(), - specified_by_url: Some( - "https://github.com/jaydenseric/graphql-multipart-request-spec".to_string(), - ), - directive_invocations: Default::default(), - requires_scopes: Default::default(), - }) - } - - fn parse(value: Option) -> InputValueResult { - const PREFIX: &str = "#__graphql_file__:"; - let value = value.unwrap_or_default(); - if let Value::String(s) = &value { - if let Some(filename) = s.strip_prefix(PREFIX) { - return Ok(Upload(filename.parse::().unwrap())); - } - } - Err(InputValueError::expected_type(value)) - } - - fn to_value(&self) -> Value { - Value::Null - } - - fn as_raw_value(&self) -> Option<&Self::RawValueType> { - Some(self) - } -} diff --git a/src/validation/mod.rs b/src/validation/mod.rs deleted file mode 100644 index a7569f158..000000000 --- a/src/validation/mod.rs +++ /dev/null @@ -1,128 +0,0 @@ -#[cfg(test)] -#[macro_use] -mod test_harness; - -mod rules; -mod suggestion; -mod utils; -mod visitor; -mod visitors; - -pub use visitor::VisitorContext; -use visitor::{VisitorNil, visit}; - -use crate::{ - CacheControl, ServerError, Variables, parser::types::ExecutableDocument, registry::Registry, -}; - -/// Validation results. -#[derive(Debug, Copy, Clone)] -pub struct ValidationResult { - /// Cache control - pub cache_control: CacheControl, - - /// Query complexity - pub complexity: usize, - - /// Query depth - pub depth: usize, -} - -/// Validation mode -#[derive(Copy, Clone, Debug)] -pub enum ValidationMode { - /// Execute all validation rules. - Strict, - - /// The executor itself also has error handling, so it can improve - /// performance, but it can lose some error messages. - Fast, -} - -pub(crate) fn check_rules( - registry: &Registry, - doc: &ExecutableDocument, - variables: Option<&Variables>, - mode: ValidationMode, - limit_complexity: Option, - limit_depth: Option, -) -> Result> { - let mut cache_control = CacheControl::default(); - let mut complexity = 0; - let mut depth = 0; - - let errors = match mode { - ValidationMode::Strict => { - let mut ctx = VisitorContext::new(registry, doc, variables); - let mut visitor = VisitorNil - .with(rules::ArgumentsOfCorrectType::default()) - .with(rules::DefaultValuesOfCorrectType) - .with(rules::FieldsOnCorrectType) - .with(rules::FragmentsOnCompositeTypes) - .with(rules::KnownArgumentNames::default()) - .with(rules::NoFragmentCycles::default()) - .with(rules::KnownFragmentNames) - .with(rules::KnownTypeNames) - .with(rules::NoUndefinedVariables::default()) - .with(rules::NoUnusedFragments::default()) - .with(rules::NoUnusedVariables::default()) - .with(rules::UniqueArgumentNames::default()) - .with(rules::UniqueVariableNames::default()) - .with(rules::VariablesAreInputTypes) - .with(rules::VariableInAllowedPosition::default()) - .with(rules::ScalarLeafs) - .with(rules::PossibleFragmentSpreads::default()) - .with(rules::ProvidedNonNullArguments) - .with(rules::KnownDirectives::default()) - .with(rules::DirectivesUnique) - .with(rules::OverlappingFieldsCanBeMerged) - .with(rules::UploadFile); - visit(&mut visitor, &mut ctx, doc); - - let mut visitor = VisitorNil - .with(visitors::CacheControlCalculate { - cache_control: &mut cache_control, - }) - .with(visitors::ComplexityCalculate::new(&mut complexity)) - .with(visitors::DepthCalculate::new(&mut depth)); - visit(&mut visitor, &mut ctx, doc); - ctx.errors - } - ValidationMode::Fast => { - let mut ctx = VisitorContext::new(registry, doc, variables); - let mut visitor = VisitorNil - .with(rules::NoFragmentCycles::default()) - .with(rules::UploadFile) - .with(visitors::CacheControlCalculate { - cache_control: &mut cache_control, - }) - .with(visitors::ComplexityCalculate::new(&mut complexity)) - .with(visitors::DepthCalculate::new(&mut depth)); - visit(&mut visitor, &mut ctx, doc); - ctx.errors - } - }; - - // check limit - if let Some(limit_complexity) = limit_complexity { - if complexity > limit_complexity { - return Err(vec![ServerError::new("Query is too complex.", None)]); - } - } - - if let Some(limit_depth) = limit_depth { - if depth > limit_depth { - return Err(vec![ServerError::new("Query is nested too deep.", None)]); - } - } - - if !errors.is_empty() { - return Err(errors.into_iter().map(Into::into).collect()); - } - - Ok(ValidationResult { - cache_control, - complexity, - depth, - }) -} diff --git a/src/validation/rules/arguments_of_correct_type.rs b/src/validation/rules/arguments_of_correct_type.rs deleted file mode 100644 index a810d124c..000000000 --- a/src/validation/rules/arguments_of_correct_type.rs +++ /dev/null @@ -1,1067 +0,0 @@ -use async_graphql_value::Value; -use indexmap::map::IndexMap; - -use crate::{ - Name, Positioned, QueryPathSegment, - context::QueryPathNode, - parser::types::{Directive, Field}, - registry::MetaInputValue, - validation::{ - utils::is_valid_input_value, - visitor::{Visitor, VisitorContext}, - }, -}; - -#[derive(Default)] -pub struct ArgumentsOfCorrectType<'a> { - current_args: Option<&'a IndexMap>, -} - -impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> { - fn enter_directive( - &mut self, - ctx: &mut VisitorContext<'a>, - directive: &'a Positioned, - ) { - self.current_args = ctx - .registry - .directives - .get(directive.node.name.node.as_str()) - .map(|d| &d.args); - } - - fn exit_directive( - &mut self, - _ctx: &mut VisitorContext<'a>, - _directive: &'a Positioned, - ) { - self.current_args = None; - } - - fn enter_argument( - &mut self, - ctx: &mut VisitorContext<'a>, - name: &'a Positioned, - value: &'a Positioned, - ) { - if let Some(arg) = self - .current_args - .and_then(|args| args.get(name.node.as_str())) - { - let value = value - .node - .clone() - .into_const_with(|var_name| { - ctx.variables - .and_then(|variables| variables.get(&var_name)) - .cloned() - .ok_or(()) - }) - .ok(); - - if let Some(reason) = value.and_then(|value| { - is_valid_input_value( - ctx.registry, - &arg.ty, - &value, - QueryPathNode { - parent: None, - segment: QueryPathSegment::Name(&arg.name), - }, - ) - }) { - ctx.report_error( - vec![name.pos], - format!("Invalid value for argument {}", reason), - ); - } - } - } - - fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Positioned) { - self.current_args = ctx - .parent_type() - .and_then(|p| p.field_by_name(&field.node.name.node)) - .map(|f| &f.args); - } - - fn exit_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Positioned) { - self.current_args = None; - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory<'a>() -> ArgumentsOfCorrectType<'a> { - ArgumentsOfCorrectType::default() - } - - #[test] - fn good_null_value() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - intArgField(intArg: null) - } - } - "#, - ); - } - - #[test] - fn null_into_int() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - nonNullIntArgField(nonNullIntArg: null) - } - } - "#, - ); - } - - #[test] - fn good_int_value() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - intArgField(intArg: 2) - } - } - "#, - ); - } - - #[test] - fn good_boolean_value() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - booleanArgField(booleanArg: true) - } - } - "#, - ); - } - - #[test] - fn good_string_value() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - stringArgField(stringArg: "foo") - } - } - "#, - ); - } - - #[test] - fn good_float_value() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - floatArgField(floatArg: 1.1) - } - } - "#, - ); - } - - #[test] - fn int_into_float() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - floatArgField(floatArg: 1) - } - } - "#, - ); - } - - #[test] - fn int_into_id() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - idArgField(idArg: 1) - } - } - "#, - ); - } - - #[test] - fn string_into_id() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - idArgField(idArg: "someIdString") - } - } - "#, - ); - } - - #[test] - fn good_enum_value() { - expect_passes_rule!( - factory, - r#" - { - dog { - doesKnowCommand(dogCommand: SIT) - } - } - "#, - ); - } - - #[test] - fn int_into_string() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - stringArgField(stringArg: 1) - } - } - "#, - ); - } - - #[test] - fn float_into_string() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - stringArgField(stringArg: 1.0) - } - } - "#, - ); - } - - #[test] - fn boolean_into_string() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - stringArgField(stringArg: true) - } - } - "#, - ); - } - - #[test] - fn unquoted_string_into_string() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - stringArgField(stringArg: BAR) - } - } - "#, - ); - } - - #[test] - fn string_into_int() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - intArgField(intArg: "3") - } - } - "#, - ); - } - - #[test] - fn unquoted_string_into_int() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - intArgField(intArg: FOO) - } - } - "#, - ); - } - - #[test] - fn simple_float_into_int() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - intArgField(intArg: 3.0) - } - } - "#, - ); - } - - #[test] - fn float_into_int() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - intArgField(intArg: 3.333) - } - } - "#, - ); - } - - #[test] - fn string_into_float() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - floatArgField(floatArg: "3.333") - } - } - "#, - ); - } - - #[test] - fn boolean_into_float() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - floatArgField(floatArg: true) - } - } - "#, - ); - } - - #[test] - fn unquoted_into_float() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - floatArgField(floatArg: FOO) - } - } - "#, - ); - } - - #[test] - fn int_into_boolean() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - booleanArgField(booleanArg: 2) - } - } - "#, - ); - } - - #[test] - fn float_into_boolean() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - booleanArgField(booleanArg: 1.0) - } - } - "#, - ); - } - - #[test] - fn string_into_boolean() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - booleanArgField(booleanArg: "true") - } - } - "#, - ); - } - - #[test] - fn unquoted_into_boolean() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - booleanArgField(booleanArg: TRUE) - } - } - "#, - ); - } - - #[test] - fn float_into_id() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - idArgField(idArg: 1.0) - } - } - "#, - ); - } - - #[test] - fn boolean_into_id() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - idArgField(idArg: true) - } - } - "#, - ); - } - - #[test] - fn unquoted_into_id() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - idArgField(idArg: SOMETHING) - } - } - "#, - ); - } - - #[test] - fn int_into_enum() { - expect_fails_rule!( - factory, - r#" - { - dog { - doesKnowCommand(dogCommand: 2) - } - } - "#, - ); - } - - #[test] - fn float_into_enum() { - expect_fails_rule!( - factory, - r#" - { - dog { - doesKnowCommand(dogCommand: 1.0) - } - } - "#, - ); - } - - // #[test] - // fn string_into_enum() { - // expect_fails_rule!( - // factory, - // r#" - // { - // dog { - // doesKnowCommand(dogCommand: "SIT") - // } - // } - // "#, - // ); - // } - - #[test] - fn boolean_into_enum() { - expect_fails_rule!( - factory, - r#" - { - dog { - doesKnowCommand(dogCommand: true) - } - } - "#, - ); - } - - #[test] - fn unknown_enum_value_into_enum() { - expect_fails_rule!( - factory, - r#" - { - dog { - doesKnowCommand(dogCommand: JUGGLE) - } - } - "#, - ); - } - - #[test] - fn different_case_enum_value_into_enum() { - expect_fails_rule!( - factory, - r#" - { - dog { - doesKnowCommand(dogCommand: sit) - } - } - "#, - ); - } - - #[test] - fn good_list_value() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - stringListArgField(stringListArg: ["one", "two"]) - } - } - "#, - ); - } - - #[test] - fn empty_list_value() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - stringListArgField(stringListArg: []) - } - } - "#, - ); - } - - #[test] - fn single_value_into_list() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - stringListArgField(stringListArg: "one") - } - } - "#, - ); - } - - #[test] - fn incorrect_item_type() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - stringListArgField(stringListArg: ["one", 2]) - } - } - "#, - ); - } - - #[test] - fn single_value_of_incorrect_type() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - stringListArgField(stringListArg: 1) - } - } - "#, - ); - } - - #[test] - fn arg_on_optional_arg() { - expect_passes_rule!( - factory, - r#" - { - dog { - isHousetrained(atOtherHomes: true) - } - } - "#, - ); - } - - #[test] - fn no_arg_on_optional_arg() { - expect_passes_rule!( - factory, - r#" - { - dog { - isHousetrained - } - } - "#, - ); - } - - #[test] - fn multiple_args() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - multipleReqs(req1: 1, req2: 2) - } - } - "#, - ); - } - - #[test] - fn multiple_args_reverse_order() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - multipleReqs(req2: 2, req1: 1) - } - } - "#, - ); - } - - #[test] - fn no_args_on_multiple_optional() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - multipleOpts - } - } - "#, - ); - } - - #[test] - fn one_arg_on_multiple_optional() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - multipleOpts(opt1: 1) - } - } - "#, - ); - } - - #[test] - fn second_arg_on_multiple_optional() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - multipleOpts(opt2: 1) - } - } - "#, - ); - } - - #[test] - fn multiple_reqs_on_mixed_list() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - multipleOptAndReq(req1: 3, req2: 4) - } - } - "#, - ); - } - - #[test] - fn multiple_reqs_and_one_opt_on_mixed_list() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - multipleOptAndReq(req1: 3, req2: 4, opt1: 5) - } - } - "#, - ); - } - - #[test] - fn all_reqs_and_opts_on_mixed_list() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - multipleOptAndReq(req1: 3, req2: 4, opt1: 5, opt2: 6) - } - } - "#, - ); - } - - #[test] - fn incorrect_value_type() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - multipleReqs(req2: "two", req1: "one") - } - } - "#, - ); - } - - #[test] - fn incorrect_value_and_missing_argument() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - multipleReqs(req1: "one") - } - } - "#, - ); - } - - #[test] - fn optional_arg_despite_required_field_in_type() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - complexArgField - } - } - "#, - ); - } - - #[test] - fn partial_object_only_required() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - complexArgField(complexArg: { requiredField: true }) - } - } - "#, - ); - } - - #[test] - fn partial_object_required_field_can_be_falsy() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - complexArgField(complexArg: { requiredField: false }) - } - } - "#, - ); - } - - #[test] - fn partial_object_including_required() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - complexArgField(complexArg: { requiredField: true, intField: 4 }) - } - } - "#, - ); - } - - #[test] - fn full_object() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - complexArgField(complexArg: { - requiredField: true, - intField: 4, - stringField: "foo", - booleanField: false, - stringListField: ["one", "two"] - }) - } - } - "#, - ); - } - - #[test] - fn full_object_with_fields_in_different_order() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - complexArgField(complexArg: { - stringListField: ["one", "two"], - booleanField: false, - requiredField: true, - stringField: "foo", - intField: 4, - }) - } - } - "#, - ); - } - - #[test] - fn partial_object_missing_required() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - complexArgField(complexArg: { intField: 4 }) - } - } - "#, - ); - } - - #[test] - fn partial_object_invalid_field_type() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - complexArgField(complexArg: { - stringListField: ["one", 2], - requiredField: true, - }) - } - } - "#, - ); - } - - #[test] - fn partial_object_unknown_field_arg() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - complexArgField(complexArg: { - requiredField: true, - unknownField: "value" - }) - } - } - "#, - ); - } - - #[test] - fn directive_with_valid_types() { - expect_passes_rule!( - factory, - r#" - { - dog @include(if: true) { - name - } - human @skip(if: false) { - name - } - } - "#, - ); - } - - #[test] - fn directive_with_incorrect_types() { - expect_fails_rule!( - factory, - r#" - { - dog @include(if: "yes") { - name @skip(if: ENUM) - } - } - "#, - ); - } - - #[test] - fn oneof() { - expect_passes_rule!( - factory, - r#" - { - oneofArg(arg: {a: 10}) - } - "#, - ); - - expect_passes_rule!( - factory, - r#" - { - oneofArg(arg: {b: "abc"}) - } - "#, - ); - - expect_fails_rule!( - factory, - r#" - { - oneofArg(arg: {a: 10, b: "abc"}) - } - "#, - ); - } - - #[test] - fn oneof_opt() { - expect_passes_rule!( - factory, - r#" - { - oneofOpt(arg: {a: 10}) - } - "#, - ); - - expect_passes_rule!( - factory, - r#" - { - oneofOpt(arg: {b: "abc"}) - } - "#, - ); - - expect_passes_rule!( - factory, - r#" - { - oneofOpt - } - "#, - ); - - expect_passes_rule!( - factory, - r#" - { - oneofOpt(arg: null) - } - "#, - ); - - expect_fails_rule!( - factory, - r#" - { - oneofOpt(arg: {a: 10, b: "abc"}) - } - "#, - ); - } -} diff --git a/src/validation/rules/default_values_of_correct_type.rs b/src/validation/rules/default_values_of_correct_type.rs deleted file mode 100644 index 653dfec80..000000000 --- a/src/validation/rules/default_values_of_correct_type.rs +++ /dev/null @@ -1,149 +0,0 @@ -use async_graphql_parser::types::BaseType; - -use crate::{ - Positioned, QueryPathSegment, - context::QueryPathNode, - parser::types::VariableDefinition, - validation::{ - utils::is_valid_input_value, - visitor::{Visitor, VisitorContext}, - }, -}; - -pub struct DefaultValuesOfCorrectType; - -impl<'a> Visitor<'a> for DefaultValuesOfCorrectType { - fn enter_variable_definition( - &mut self, - ctx: &mut VisitorContext<'a>, - variable_definition: &'a Positioned, - ) { - if let BaseType::Named(vtype_name) = &variable_definition.node.var_type.node.base { - if !ctx.registry.types.contains_key(vtype_name.as_str()) { - ctx.report_error( - vec![variable_definition.pos], - format!(r#"Unknown type "{}""#, vtype_name), - ); - return; - } - } - - if let Some(value) = &variable_definition.node.default_value { - if let Some(reason) = is_valid_input_value( - ctx.registry, - &variable_definition.node.var_type.to_string(), - &value.node, - QueryPathNode { - parent: None, - segment: QueryPathSegment::Name(&variable_definition.node.name.node), - }, - ) { - ctx.report_error( - vec![variable_definition.pos], - format!("Invalid default value for argument: {}", reason), - ) - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory() -> DefaultValuesOfCorrectType { - DefaultValuesOfCorrectType - } - - #[test] - fn variables_with_no_default_values() { - expect_passes_rule!( - factory, - r#" - query NullableValues($a: Int, $b: String, $c: ComplexInput) { - dog { name } - } - "#, - ); - } - - #[test] - fn required_variables_without_default_values() { - expect_passes_rule!( - factory, - r#" - query RequiredValues($a: Int!, $b: String!) { - dog { name } - } - "#, - ); - } - - #[test] - fn variables_with_valid_default_values() { - expect_passes_rule!( - factory, - r#" - query WithDefaultValues( - $a: Int = 1, - $b: String = "ok", - $c: ComplexInput = { requiredField: true, intField: 3 } - ) { - dog { name } - } - "#, - ); - } - - #[test] - fn required_variables_with_default_values() { - expect_passes_rule!( - factory, - r#" - query UnreachableDefaultValues($a: Int! = 3, $b: String! = "default") { - dog { name } - } - "#, - ); - } - - #[test] - fn variables_with_invalid_default_values() { - expect_fails_rule!( - factory, - r#" - query InvalidDefaultValues( - $a: Int = "one", - $b: String = 4, - $c: ComplexInput = "notverycomplex" - ) { - dog { name } - } - "#, - ); - } - - #[test] - fn complex_variables_missing_required_field() { - expect_fails_rule!( - factory, - r#" - query MissingRequiredField($a: ComplexInput = {intField: 3}) { - dog { name } - } - "#, - ); - } - - #[test] - fn list_variables_with_invalid_item() { - expect_fails_rule!( - factory, - r#" - query InvalidItem($a: [String] = ["one", 2]) { - dog { name } - } - "#, - ); - } -} diff --git a/src/validation/rules/directives_unique.rs b/src/validation/rules/directives_unique.rs deleted file mode 100644 index 11ee9f139..000000000 --- a/src/validation/rules/directives_unique.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::collections::HashSet; - -use crate::{ - Name, Positioned, VisitorContext, - parser::types::{ - Directive, Field, FragmentDefinition, FragmentSpread, InlineFragment, OperationDefinition, - VariableDefinition, - }, - validation::visitor::Visitor, -}; - -#[derive(Default)] -pub struct DirectivesUnique; - -impl<'a> Visitor<'a> for DirectivesUnique { - fn enter_operation_definition( - &mut self, - ctx: &mut VisitorContext<'a>, - _name: Option<&'a Name>, - operation_definition: &'a Positioned, - ) { - check_duplicate_directive(ctx, &operation_definition.node.directives); - } - - fn enter_fragment_definition( - &mut self, - ctx: &mut VisitorContext<'a>, - _name: &'a Name, - fragment_definition: &'a Positioned, - ) { - check_duplicate_directive(ctx, &fragment_definition.node.directives); - } - - fn enter_variable_definition( - &mut self, - ctx: &mut VisitorContext<'a>, - variable_definition: &'a Positioned, - ) { - check_duplicate_directive(ctx, &variable_definition.node.directives); - } - - fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Positioned) { - check_duplicate_directive(ctx, &field.node.directives); - } - - fn enter_fragment_spread( - &mut self, - ctx: &mut VisitorContext<'a>, - fragment_spread: &'a Positioned, - ) { - check_duplicate_directive(ctx, &fragment_spread.node.directives); - } - - fn enter_inline_fragment( - &mut self, - ctx: &mut VisitorContext<'a>, - inline_fragment: &'a Positioned, - ) { - check_duplicate_directive(ctx, &inline_fragment.node.directives); - } -} - -fn check_duplicate_directive(ctx: &mut VisitorContext<'_>, directives: &[Positioned]) { - let mut exists = HashSet::new(); - - for directive in directives { - let name = &directive.node.name.node; - if let Some(meta_directive) = ctx.registry.directives.get(name.as_str()) { - if !meta_directive.is_repeatable { - if exists.contains(name) { - ctx.report_error( - vec![directive.pos], - format!("Duplicate directive \"{}\"", name), - ); - continue; - } - exists.insert(name); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory() -> DirectivesUnique { - DirectivesUnique - } - - #[test] - fn skip_on_field() { - expect_passes_rule!( - factory, - r#" - { - dog { - name @skip(if: true) - } - } - "#, - ); - } - - #[test] - fn duplicate_skip_on_field() { - expect_fails_rule!( - factory, - r#" - { - dog { - name @skip(if: true) @skip(if: false) - } - } - "#, - ); - } - - #[test] - fn skip_on_fragment_spread() { - expect_passes_rule!( - factory, - r#" - fragment A on Dog { - name - } - - query { - dog ... A @skip(if: true) - } - "#, - ); - } - - #[test] - fn duplicate_skip_on_fragment_spread() { - expect_fails_rule!( - factory, - r#" - fragment A on Dog { - name - } - - query { - dog ... A @skip(if: true) @skip(if: false) - } - "#, - ); - } - - #[test] - fn skip_on_inline_fragment() { - expect_passes_rule!( - factory, - r#" - query { - dog ... @skip(if: true) { - name - } - } - "#, - ); - } - - #[test] - fn duplicate_skip_on_inline_fragment() { - expect_fails_rule!( - factory, - r#" - query { - dog ... @skip(if: true) @skip(if: false) { - name - } - } - "#, - ); - } -} diff --git a/src/validation/rules/fields_on_correct_type.rs b/src/validation/rules/fields_on_correct_type.rs deleted file mode 100644 index a05084314..000000000 --- a/src/validation/rules/fields_on_correct_type.rs +++ /dev/null @@ -1,346 +0,0 @@ -use crate::{ - Positioned, - parser::types::Field, - registry, - validation::{ - suggestion::make_suggestion, - visitor::{Visitor, VisitorContext}, - }, -}; - -#[derive(Default)] -pub struct FieldsOnCorrectType; - -impl<'a> Visitor<'a> for FieldsOnCorrectType { - fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Positioned) { - if let Some(parent_type) = ctx.parent_type() { - if let Some(registry::MetaType::Union { .. }) - | Some(registry::MetaType::Interface { .. }) = ctx.parent_type() - { - if field.node.name.node == "__typename" { - return; - } - } - - if parent_type - .fields() - .and_then(|fields| fields.get(field.node.name.node.as_str())) - .is_none() - && !field - .node - .directives - .iter() - .any(|directive| directive.node.name.node == "ifdef") - { - ctx.report_error( - vec![field.pos], - format!( - "Unknown field \"{}\" on type \"{}\".{}", - field.node.name, - parent_type.name(), - if ctx.registry.enable_suggestions { - make_suggestion( - " Did you mean", - parent_type - .fields() - .iter() - .map(|fields| fields.keys()) - .flatten() - .map(String::as_str), - &field.node.name.node, - ) - .unwrap_or_default() - } else { - String::new() - } - ), - ); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory() -> FieldsOnCorrectType { - FieldsOnCorrectType - } - - #[test] - fn selection_on_object() { - expect_passes_rule!( - factory, - r#" - fragment objectFieldSelection on Dog { - __typename - name - } - { __typename } - "#, - ); - } - - #[test] - fn aliased_selection_on_object() { - expect_passes_rule!( - factory, - r#" - fragment aliasedObjectFieldSelection on Dog { - tn : __typename - otherName : name - } - { __typename } - "#, - ); - } - - #[test] - fn selection_on_interface() { - expect_passes_rule!( - factory, - r#" - fragment interfaceFieldSelection on Pet { - __typename - name - } - { __typename } - "#, - ); - } - - #[test] - fn aliased_selection_on_interface() { - expect_passes_rule!( - factory, - r#" - fragment interfaceFieldSelection on Pet { - otherName : name - } - { __typename } - "#, - ); - } - - #[test] - fn lying_alias_selection() { - expect_passes_rule!( - factory, - r#" - fragment lyingAliasSelection on Dog { - name : nickname - } - { __typename } - "#, - ); - } - - #[test] - fn ignores_unknown_type() { - expect_passes_rule!( - factory, - r#" - fragment unknownSelection on UnknownType { - unknownField - } - { __typename } - "#, - ); - } - - #[test] - fn nested_unknown_fields() { - expect_fails_rule!( - factory, - r#" - fragment typeKnownAgain on Pet { - unknown_pet_field { - ... on Cat { - unknown_cat_field - } - } - } - { __typename } - "#, - ); - } - - #[test] - fn unknown_field_on_fragment() { - expect_fails_rule!( - factory, - r#" - fragment fieldNotDefined on Dog { - meowVolume - } - { __typename } - "#, - ); - } - - #[test] - fn ignores_deeply_unknown_field() { - expect_fails_rule!( - factory, - r#" - fragment deepFieldNotDefined on Dog { - unknown_field { - deeper_unknown_field - } - } - { __typename } - "#, - ); - } - - #[test] - fn unknown_subfield() { - expect_fails_rule!( - factory, - r#" - fragment subFieldNotDefined on Human { - pets { - unknown_field - } - } - { __typename } - "#, - ); - } - - #[test] - fn unknown_field_on_inline_fragment() { - expect_fails_rule!( - factory, - r#" - fragment fieldNotDefined on Pet { - ... on Dog { - meowVolume - } - } - { __typename } - "#, - ); - } - - #[test] - fn unknown_aliased_target() { - expect_fails_rule!( - factory, - r#" - fragment aliasedFieldTargetNotDefined on Dog { - volume : mooVolume - } - { __typename } - "#, - ); - } - - #[test] - fn unknown_aliased_lying_field_target() { - expect_fails_rule!( - factory, - r#" - fragment aliasedLyingFieldTargetNotDefined on Dog { - barkVolume : kawVolume - } - { __typename } - "#, - ); - } - - #[test] - fn not_defined_on_interface() { - expect_fails_rule!( - factory, - r#" - fragment notDefinedOnInterface on Pet { - tailLength - } - { __typename } - "#, - ); - } - - #[test] - fn defined_in_concrete_types_but_not_interface() { - expect_fails_rule!( - factory, - r#" - fragment definedOnImplementorsButNotInterface on Pet { - nickname - } - { __typename } - "#, - ); - } - - #[test] - fn meta_field_on_union() { - expect_passes_rule!( - factory, - r#" - fragment definedOnImplementorsButNotInterface on Pet { - __typename - } - { __typename } - "#, - ); - } - - #[test] - fn fields_on_union() { - expect_fails_rule!( - factory, - r#" - fragment definedOnImplementorsQueriedOnUnion on CatOrDog { - name - } - { __typename } - "#, - ); - } - - #[test] - fn typename_on_union() { - expect_passes_rule!( - factory, - r#" - fragment objectFieldSelection on Pet { - __typename - ... on Dog { - name - } - ... on Cat { - name - } - } - { __typename } - "#, - ); - } - - #[test] - fn valid_field_in_inline_fragment() { - expect_passes_rule!( - factory, - r#" - fragment objectFieldSelection on Pet { - ... on Dog { - name - } - ... { - name - } - } - { __typename } - "#, - ); - } - - #[test] - fn typename_in_subscription_root() { - expect_fails_rule!(factory, "subscription { __typename }"); - } -} diff --git a/src/validation/rules/fragments_on_composite_types.rs b/src/validation/rules/fragments_on_composite_types.rs deleted file mode 100644 index 0c97dd8c0..000000000 --- a/src/validation/rules/fragments_on_composite_types.rs +++ /dev/null @@ -1,179 +0,0 @@ -use crate::{ - Name, Positioned, - parser::types::{FragmentDefinition, InlineFragment}, - validation::visitor::{Visitor, VisitorContext}, -}; - -#[derive(Default)] -pub struct FragmentsOnCompositeTypes; - -impl<'a> Visitor<'a> for FragmentsOnCompositeTypes { - fn enter_fragment_definition( - &mut self, - ctx: &mut VisitorContext<'a>, - name: &'a Name, - fragment_definition: &'a Positioned, - ) { - if let Some(current_type) = ctx.current_type() { - if !current_type.is_composite() { - ctx.report_error( - vec![fragment_definition.pos], - format!( - "Fragment \"{}\" cannot condition non composite type \"{}\"", - name, fragment_definition.node.type_condition.node.on.node, - ), - ); - } - } - } - - fn enter_inline_fragment( - &mut self, - ctx: &mut VisitorContext<'a>, - inline_fragment: &'a Positioned, - ) { - if let Some(current_type) = ctx.current_type() { - if !current_type.is_composite() { - ctx.report_error( - vec![inline_fragment.pos], - format!( - "Fragment cannot condition non composite type \"{}\"", - current_type.name() - ), - ); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn factory() -> FragmentsOnCompositeTypes { - FragmentsOnCompositeTypes - } - - #[test] - fn on_object() { - expect_passes_rule!( - factory, - r#" - fragment validFragment on Dog { - barks - } - { __typename } - "#, - ); - } - - #[test] - fn on_interface() { - expect_passes_rule!( - factory, - r#" - fragment validFragment on Pet { - name - } - { __typename } - "#, - ); - } - - #[test] - fn on_object_inline() { - expect_passes_rule!( - factory, - r#" - fragment validFragment on Pet { - ... on Dog { - barks - } - } - { __typename } - "#, - ); - } - - #[test] - fn on_inline_without_type_cond() { - expect_passes_rule!( - factory, - r#" - fragment validFragment on Pet { - ... { - name - } - } - { __typename } - "#, - ); - } - - #[test] - fn on_union() { - expect_passes_rule!( - factory, - r#" - fragment validFragment on CatOrDog { - __typename - } - { __typename } - "#, - ); - } - - #[test] - fn not_on_scalar() { - expect_fails_rule!( - factory, - r#" - fragment scalarFragment on Boolean { - bad - } - { __typename } - "#, - ); - } - - #[test] - fn not_on_enum() { - expect_fails_rule!( - factory, - r#" - fragment scalarFragment on FurColor { - bad - } - { __typename } - "#, - ); - } - - #[test] - fn not_on_input_object() { - expect_fails_rule!( - factory, - r#" - fragment inputFragment on ComplexInput { - stringField - } - { __typename } - "#, - ); - } - - #[test] - fn not_on_scalar_inline() { - expect_fails_rule!( - factory, - r#" - fragment invalidFragment on Pet { - ... on String { - barks - } - } - { __typename } - "#, - ); - } -} diff --git a/src/validation/rules/known_argument_names.rs b/src/validation/rules/known_argument_names.rs deleted file mode 100644 index 79f4a1f20..000000000 --- a/src/validation/rules/known_argument_names.rs +++ /dev/null @@ -1,289 +0,0 @@ -use async_graphql_value::Value; -use indexmap::map::IndexMap; - -use crate::{ - Name, Positioned, - parser::types::{Directive, Field}, - registry::MetaInputValue, - validation::{ - suggestion::make_suggestion, - visitor::{Visitor, VisitorContext}, - }, -}; - -enum ArgsType<'a> { - Directive(&'a str), - Field { - field_name: &'a str, - type_name: &'a str, - }, -} - -#[derive(Default)] -pub struct KnownArgumentNames<'a> { - current_args: Option<(&'a IndexMap, ArgsType<'a>)>, -} - -impl KnownArgumentNames<'_> { - fn get_suggestion(&self, name: &str) -> String { - make_suggestion( - " Did you mean", - self.current_args - .iter() - .map(|(args, _)| args.iter().map(|arg| arg.0.as_str())) - .flatten(), - name, - ) - .unwrap_or_default() - } -} - -impl<'a> Visitor<'a> for KnownArgumentNames<'a> { - fn enter_directive( - &mut self, - ctx: &mut VisitorContext<'a>, - directive: &'a Positioned, - ) { - self.current_args = ctx - .registry - .directives - .get(directive.node.name.node.as_str()) - .map(|d| (&d.args, ArgsType::Directive(&directive.node.name.node))); - } - - fn exit_directive( - &mut self, - _ctx: &mut VisitorContext<'a>, - _directive: &'a Positioned, - ) { - self.current_args = None; - } - - fn enter_argument( - &mut self, - ctx: &mut VisitorContext<'a>, - name: &'a Positioned, - _value: &'a Positioned, - ) { - if let Some((args, arg_type)) = &self.current_args { - if !args.contains_key(name.node.as_str()) { - match arg_type { - ArgsType::Field { - field_name, - type_name, - } => { - ctx.report_error( - vec![name.pos], - format!( - "Unknown argument \"{}\" on field \"{}\" of type \"{}\".{}", - name, - field_name, - type_name, - if ctx.registry.enable_suggestions { - self.get_suggestion(name.node.as_str()) - } else { - String::new() - } - ), - ); - } - ArgsType::Directive(directive_name) => { - ctx.report_error( - vec![name.pos], - format!( - "Unknown argument \"{}\" on directive \"{}\".{}", - name, - directive_name, - self.get_suggestion(name.node.as_str()) - ), - ); - } - } - } - } - } - - fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Positioned) { - if let Some(parent_type) = ctx.parent_type() { - if let Some(schema_field) = parent_type.field_by_name(&field.node.name.node) { - self.current_args = Some(( - &schema_field.args, - ArgsType::Field { - field_name: &field.node.name.node, - type_name: ctx.parent_type().unwrap().name(), - }, - )); - } - } - } - - fn exit_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Positioned) { - self.current_args = None; - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory<'a>() -> KnownArgumentNames<'a> { - KnownArgumentNames::default() - } - - #[test] - fn single_arg_is_known() { - expect_passes_rule!( - factory, - r#" - fragment argOnRequiredArg on Dog { - doesKnowCommand(dogCommand: SIT) - } - { __typename } - "#, - ); - } - - #[test] - fn multiple_args_are_known() { - expect_passes_rule!( - factory, - r#" - fragment multipleArgs on ComplicatedArgs { - multipleReqs(req1: 1, req2: 2) - } - { __typename } - "#, - ); - } - - #[test] - fn ignores_args_of_unknown_fields() { - expect_passes_rule!( - factory, - r#" - fragment argOnUnknownField on Dog { - unknownField(unknownArg: SIT) - } - { __typename } - "#, - ); - } - - #[test] - fn multiple_args_in_reverse_order_are_known() { - expect_passes_rule!( - factory, - r#" - fragment multipleArgsReverseOrder on ComplicatedArgs { - multipleReqs(req2: 2, req1: 1) - } - { __typename } - "#, - ); - } - - #[test] - fn no_args_on_optional_arg() { - expect_passes_rule!( - factory, - r#" - fragment noArgOnOptionalArg on Dog { - isHousetrained - } - { __typename } - "#, - ); - } - - #[test] - fn args_are_known_deeply() { - expect_passes_rule!( - factory, - r#" - { - dog { - doesKnowCommand(dogCommand: SIT) - } - human { - pet { - ... on Dog { - doesKnowCommand(dogCommand: SIT) - } - } - } - } - "#, - ); - } - - #[test] - fn directive_args_are_known() { - expect_passes_rule!( - factory, - r#" - { - dog @skip(if: true) - } - "#, - ); - } - - #[test] - fn undirective_args_are_invalid() { - expect_fails_rule!( - factory, - r#" - { - dog @skip(unless: true) - } - "#, - ); - } - - #[test] - fn invalid_arg_name() { - expect_fails_rule!( - factory, - r#" - fragment invalidArgName on Dog { - doesKnowCommand(unknown: true) - } - { __typename } - "#, - ); - } - - #[test] - fn unknown_args_amongst_known_args() { - expect_fails_rule!( - factory, - r#" - fragment oneGoodArgOneInvalidArg on Dog { - doesKnowCommand(whoknows: 1, dogCommand: SIT, unknown: true) - } - { __typename } - "#, - ); - } - - #[test] - fn unknown_args_deeply() { - expect_fails_rule!( - factory, - r#" - { - dog { - doesKnowCommand(unknown: true) - } - human { - pet { - ... on Dog { - doesKnowCommand(unknown: true) - } - } - } - } - "#, - ); - } -} diff --git a/src/validation/rules/known_directives.rs b/src/validation/rules/known_directives.rs deleted file mode 100644 index 61dd09de5..000000000 --- a/src/validation/rules/known_directives.rs +++ /dev/null @@ -1,239 +0,0 @@ -use crate::{ - Name, Positioned, - model::__DirectiveLocation, - parser::types::{ - Directive, Field, FragmentDefinition, FragmentSpread, InlineFragment, OperationDefinition, - OperationType, - }, - validation::visitor::{Visitor, VisitorContext}, -}; - -#[derive(Default)] -pub struct KnownDirectives { - location_stack: Vec<__DirectiveLocation>, -} - -impl<'a> Visitor<'a> for KnownDirectives { - fn enter_operation_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - _name: Option<&'a Name>, - operation_definition: &'a Positioned, - ) { - self.location_stack - .push(match &operation_definition.node.ty { - OperationType::Query => __DirectiveLocation::QUERY, - OperationType::Mutation => __DirectiveLocation::MUTATION, - OperationType::Subscription => __DirectiveLocation::SUBSCRIPTION, - }); - } - - fn exit_operation_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - _name: Option<&'a Name>, - _operation_definition: &'a Positioned, - ) { - self.location_stack.pop(); - } - - fn enter_fragment_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - _name: &'a Name, - _fragment_definition: &'a Positioned, - ) { - self.location_stack - .push(__DirectiveLocation::FRAGMENT_DEFINITION); - } - - fn exit_fragment_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - _name: &'a Name, - _fragment_definition: &'a Positioned, - ) { - self.location_stack.pop(); - } - - fn enter_directive( - &mut self, - ctx: &mut VisitorContext<'a>, - directive: &'a Positioned, - ) { - if let Some(schema_directive) = ctx - .registry - .directives - .get(directive.node.name.node.as_str()) - { - if let Some(current_location) = self.location_stack.last() { - if !schema_directive.locations.contains(current_location) { - ctx.report_error( - vec![directive.pos], - format!( - "Directive \"{}\" may not be used on \"{:?}\"", - directive.node.name.node, current_location - ), - ) - } - } - } else { - ctx.report_error( - vec![directive.pos], - format!("Unknown directive \"{}\"", directive.node.name.node), - ); - } - } - - fn enter_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Positioned) { - self.location_stack.push(__DirectiveLocation::FIELD); - } - - fn exit_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Positioned) { - self.location_stack.pop(); - } - - fn enter_fragment_spread( - &mut self, - _ctx: &mut VisitorContext<'a>, - _fragment_spread: &'a Positioned, - ) { - self.location_stack - .push(__DirectiveLocation::FRAGMENT_SPREAD); - } - - fn exit_fragment_spread( - &mut self, - _ctx: &mut VisitorContext<'a>, - _fragment_spread: &'a Positioned, - ) { - self.location_stack.pop(); - } - - fn enter_inline_fragment( - &mut self, - _ctx: &mut VisitorContext<'a>, - _inline_fragment: &'a Positioned, - ) { - self.location_stack - .push(__DirectiveLocation::INLINE_FRAGMENT); - } - - fn exit_inline_fragment( - &mut self, - _ctx: &mut VisitorContext<'a>, - _inline_fragment: &'a Positioned, - ) { - self.location_stack.pop(); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory() -> KnownDirectives { - KnownDirectives::default() - } - - #[test] - fn with_no_directives() { - expect_passes_rule!( - factory, - r#" - query Foo { - name - ...Frag - } - fragment Frag on Dog { - name - } - "#, - ); - } - - #[test] - fn with_known_directives() { - expect_passes_rule!( - factory, - r#" - { - dog @include(if: true) { - name - } - human @skip(if: false) { - name - } - } - "#, - ); - } - - #[test] - fn with_unknown_directive() { - expect_fails_rule!( - factory, - r#" - { - dog @unknown(directive: "value") { - name - } - } - "#, - ); - } - - #[test] - fn with_many_unknown_directives() { - expect_fails_rule!( - factory, - r#" - { - dog @unknown(directive: "value") { - name - } - human @unknown(directive: "value") { - name - pets @unknown(directive: "value") { - name - } - } - } - "#, - ); - } - - #[test] - fn with_well_placed_directives() { - expect_passes_rule!( - factory, - r#" - query Foo { - name @include(if: true) - ...Frag @include(if: true) - skippedField @skip(if: true) - ...SkippedFrag @skip(if: true) - } - mutation Bar { - someField - } - "#, - ); - } - - #[test] - fn with_misplaced_directives() { - expect_fails_rule!( - factory, - r#" - query Foo @include(if: true) { - name - ...Frag - } - mutation Bar { - someField - } - "#, - ); - } -} diff --git a/src/validation/rules/known_fragment_names.rs b/src/validation/rules/known_fragment_names.rs deleted file mode 100644 index 401df4fa0..000000000 --- a/src/validation/rules/known_fragment_names.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::{ - Positioned, - parser::types::FragmentSpread, - validation::visitor::{Visitor, VisitorContext}, -}; - -#[derive(Default)] -pub struct KnownFragmentNames; - -impl<'a> Visitor<'a> for KnownFragmentNames { - fn enter_fragment_spread( - &mut self, - ctx: &mut VisitorContext<'a>, - fragment_spread: &'a Positioned, - ) { - if !ctx.is_known_fragment(&fragment_spread.node.fragment_name.node) { - ctx.report_error( - vec![fragment_spread.pos], - format!( - r#"Unknown fragment: "{}""#, - fragment_spread.node.fragment_name.node - ), - ); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory() -> KnownFragmentNames { - KnownFragmentNames - } - - #[test] - fn known() { - expect_passes_rule!( - factory, - r#" - { - human(id: 4) { - ...HumanFields1 - ... on Human { - ...HumanFields2 - } - ... { - name - } - } - } - fragment HumanFields1 on Human { - name - ...HumanFields3 - } - fragment HumanFields2 on Human { - name - } - fragment HumanFields3 on Human { - name - } - "#, - ); - } - - #[test] - fn unknown() { - expect_fails_rule!( - factory, - r#" - { - human(id: 4) { - ...UnknownFragment1 - ... on Human { - ...UnknownFragment2 - } - } - } - fragment HumanFields on Human { - name - ...UnknownFragment3 - } - "#, - ); - } -} diff --git a/src/validation/rules/known_type_names.rs b/src/validation/rules/known_type_names.rs deleted file mode 100644 index d5bb045d2..000000000 --- a/src/validation/rules/known_type_names.rs +++ /dev/null @@ -1,98 +0,0 @@ -use crate::{ - Name, Pos, Positioned, - parser::types::{FragmentDefinition, InlineFragment, TypeCondition, VariableDefinition}, - registry::MetaTypeName, - validation::visitor::{Visitor, VisitorContext}, -}; - -#[derive(Default)] -pub struct KnownTypeNames; - -impl<'a> Visitor<'a> for KnownTypeNames { - fn enter_fragment_definition( - &mut self, - ctx: &mut VisitorContext<'a>, - _name: &'a Name, - fragment_definition: &'a Positioned, - ) { - let TypeCondition { on: name } = &fragment_definition.node.type_condition.node; - validate_type(ctx, &name.node, fragment_definition.pos); - } - - fn enter_variable_definition( - &mut self, - ctx: &mut VisitorContext<'a>, - variable_definition: &'a Positioned, - ) { - validate_type( - ctx, - MetaTypeName::concrete_typename(&variable_definition.node.var_type.to_string()), - variable_definition.pos, - ); - } - - fn enter_inline_fragment( - &mut self, - ctx: &mut VisitorContext<'a>, - inline_fragment: &'a Positioned, - ) { - if let Some(TypeCondition { on: name }) = inline_fragment - .node - .type_condition - .as_ref() - .map(|c| &c.node) - { - validate_type(ctx, &name.node, inline_fragment.pos); - } - } -} - -fn validate_type(ctx: &mut VisitorContext<'_>, type_name: &str, pos: Pos) { - if !ctx.registry.types.contains_key(type_name) { - ctx.report_error(vec![pos], format!(r#"Unknown type "{}""#, type_name)); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory() -> KnownTypeNames { - KnownTypeNames - } - - #[test] - fn known_type_names_are_valid() { - expect_passes_rule!( - factory, - r#" - query Foo($var: String, $required: [String!]!) { - user(id: 4) { - pets { ... on Pet { name }, ...PetFields, ... { name } } - } - } - fragment PetFields on Pet { - name - } - "#, - ); - } - - #[test] - fn unknown_type_names_are_invalid() { - expect_fails_rule!( - factory, - r#" - query Foo($var: JumbledUpLetters) { - user(id: 4) { - name - pets { ... on Badger { name }, ...PetFields } - } - } - fragment PetFields on Peettt { - name - } - "#, - ); - } -} diff --git a/src/validation/rules/mod.rs b/src/validation/rules/mod.rs deleted file mode 100644 index e36f0391f..000000000 --- a/src/validation/rules/mod.rs +++ /dev/null @@ -1,45 +0,0 @@ -mod arguments_of_correct_type; -mod default_values_of_correct_type; -mod directives_unique; -mod fields_on_correct_type; -mod fragments_on_composite_types; -mod known_argument_names; -mod known_directives; -mod known_fragment_names; -mod known_type_names; -mod no_fragment_cycles; -mod no_undefined_variables; -mod no_unused_fragments; -mod no_unused_variables; -mod overlapping_fields_can_be_merged; -mod possible_fragment_spreads; -mod provided_non_null_arguments; -mod scalar_leafs; -mod unique_argument_names; -mod unique_variable_names; -mod upload_file; -mod variables_are_input_types; -mod variables_in_allowed_position; - -pub use arguments_of_correct_type::ArgumentsOfCorrectType; -pub use default_values_of_correct_type::DefaultValuesOfCorrectType; -pub use directives_unique::DirectivesUnique; -pub use fields_on_correct_type::FieldsOnCorrectType; -pub use fragments_on_composite_types::FragmentsOnCompositeTypes; -pub use known_argument_names::KnownArgumentNames; -pub use known_directives::KnownDirectives; -pub use known_fragment_names::KnownFragmentNames; -pub use known_type_names::KnownTypeNames; -pub use no_fragment_cycles::NoFragmentCycles; -pub use no_undefined_variables::NoUndefinedVariables; -pub use no_unused_fragments::NoUnusedFragments; -pub use no_unused_variables::NoUnusedVariables; -pub use overlapping_fields_can_be_merged::OverlappingFieldsCanBeMerged; -pub use possible_fragment_spreads::PossibleFragmentSpreads; -pub use provided_non_null_arguments::ProvidedNonNullArguments; -pub use scalar_leafs::ScalarLeafs; -pub use unique_argument_names::UniqueArgumentNames; -pub use unique_variable_names::UniqueVariableNames; -pub use upload_file::UploadFile; -pub use variables_are_input_types::VariablesAreInputTypes; -pub use variables_in_allowed_position::VariableInAllowedPosition; diff --git a/src/validation/rules/no_fragment_cycles.rs b/src/validation/rules/no_fragment_cycles.rs deleted file mode 100644 index 2a1a46d71..000000000 --- a/src/validation/rules/no_fragment_cycles.rs +++ /dev/null @@ -1,323 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use crate::{ - Name, Pos, Positioned, - parser::types::{ExecutableDocument, FragmentDefinition, FragmentSpread}, - validation::visitor::{RuleError, Visitor, VisitorContext}, -}; - -struct CycleDetector<'a> { - visited: HashSet<&'a str>, - spreads: &'a HashMap<&'a str, Vec<(&'a str, Pos)>>, - path_indices: HashMap<&'a str, usize>, - errors: Vec, -} - -impl<'a> CycleDetector<'a> { - fn detect_from(&mut self, from: &'a str, path: &mut Vec<(&'a str, Pos)>) { - self.visited.insert(from); - - if !self.spreads.contains_key(from) { - return; - } - - self.path_indices.insert(from, path.len()); - - for (name, pos) in &self.spreads[from] { - let index = self.path_indices.get(name).cloned(); - - if let Some(index) = index { - let err_pos = if index < path.len() { - path[index].1 - } else { - *pos - }; - - self.errors.push(RuleError::new( - vec![err_pos], - format!("Cannot spread fragment \"{}\"", name), - )); - } else if !self.visited.contains(name) { - path.push((name, *pos)); - self.detect_from(name, path); - path.pop(); - } - } - - self.path_indices.remove(from); - } -} - -#[derive(Default)] -pub struct NoFragmentCycles<'a> { - current_fragment: Option<&'a str>, - spreads: HashMap<&'a str, Vec<(&'a str, Pos)>>, - fragment_order: Vec<&'a str>, -} - -impl<'a> Visitor<'a> for NoFragmentCycles<'a> { - fn exit_document(&mut self, ctx: &mut VisitorContext<'a>, _doc: &'a ExecutableDocument) { - let mut detector = CycleDetector { - visited: HashSet::new(), - spreads: &self.spreads, - path_indices: HashMap::new(), - errors: Vec::new(), - }; - - for frag in &self.fragment_order { - if !detector.visited.contains(frag) { - let mut path = Vec::new(); - detector.detect_from(frag, &mut path); - } - } - - ctx.append_errors(detector.errors); - } - - fn enter_fragment_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - name: &'a Name, - _fragment_definition: &'a Positioned, - ) { - self.current_fragment = Some(name); - self.fragment_order.push(name); - } - - fn exit_fragment_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - _name: &'a Name, - _fragment_definition: &'a Positioned, - ) { - self.current_fragment = None; - } - - fn enter_fragment_spread( - &mut self, - _ctx: &mut VisitorContext<'a>, - fragment_spread: &'a Positioned, - ) { - if let Some(current_fragment) = self.current_fragment { - self.spreads.entry(current_fragment).or_default().push(( - &fragment_spread.node.fragment_name.node, - fragment_spread.pos, - )); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory<'a>() -> NoFragmentCycles<'a> { - NoFragmentCycles::default() - } - - #[test] - fn single_reference_is_valid() { - expect_passes_rule!( - factory, - r#" - fragment fragA on Dog { ...fragB } - fragment fragB on Dog { name } - { __typename } - "#, - ); - } - - #[test] - fn spreading_twice_is_not_circular() { - expect_passes_rule!( - factory, - r#" - fragment fragA on Dog { ...fragB, ...fragB } - fragment fragB on Dog { name } - { __typename } - "#, - ); - } - - #[test] - fn spreading_twice_indirectly_is_not_circular() { - expect_passes_rule!( - factory, - r#" - fragment fragA on Dog { ...fragB, ...fragC } - fragment fragB on Dog { ...fragC } - fragment fragC on Dog { name } - { __typename } - "#, - ); - } - - #[test] - fn double_spread_within_abstract_types() { - expect_passes_rule!( - factory, - r#" - fragment nameFragment on Pet { - ... on Dog { name } - ... on Cat { name } - } - fragment spreadsInAnon on Pet { - ... on Dog { ...nameFragment } - ... on Cat { ...nameFragment } - } - { __typename } - "#, - ); - } - - #[test] - fn does_not_false_positive_on_unknown_fragment() { - expect_passes_rule!( - factory, - r#" - fragment nameFragment on Pet { - ...UnknownFragment - } - { __typename } - "#, - ); - } - - #[test] - fn spreading_recursively_within_field_fails() { - expect_fails_rule!( - factory, - r#" - fragment fragA on Human { relatives { ...fragA } }, - { __typename } - "#, - ); - } - - #[test] - fn no_spreading_itself_directly() { - expect_fails_rule!( - factory, - r#" - fragment fragA on Dog { ...fragA } - { __typename } - "#, - ); - } - - #[test] - fn no_spreading_itself_directly_within_inline_fragment() { - expect_fails_rule!( - factory, - r#" - fragment fragA on Pet { - ... on Dog { - ...fragA - } - } - { __typename } - "#, - ); - } - - #[test] - fn no_spreading_itself_indirectly() { - expect_fails_rule!( - factory, - r#" - fragment fragA on Dog { ...fragB } - fragment fragB on Dog { ...fragA } - { __typename } - "#, - ); - } - - #[test] - fn no_spreading_itself_indirectly_reports_opposite_order() { - expect_fails_rule!( - factory, - r#" - fragment fragB on Dog { ...fragA } - fragment fragA on Dog { ...fragB } - { __typename } - "#, - ); - } - - #[test] - fn no_spreading_itself_indirectly_within_inline_fragment() { - expect_fails_rule!( - factory, - r#" - fragment fragA on Pet { - ... on Dog { - ...fragB - } - } - fragment fragB on Pet { - ... on Dog { - ...fragA - } - } - { __typename } - "#, - ); - } - - #[test] - fn no_spreading_itself_deeply() { - expect_fails_rule!( - factory, - r#" - fragment fragA on Dog { ...fragB } - fragment fragB on Dog { ...fragC } - fragment fragC on Dog { ...fragO } - fragment fragX on Dog { ...fragY } - fragment fragY on Dog { ...fragZ } - fragment fragZ on Dog { ...fragO } - fragment fragO on Dog { ...fragP } - fragment fragP on Dog { ...fragA, ...fragX } - { __typename } - "#, - ); - } - - #[test] - fn no_spreading_itself_deeply_two_paths() { - expect_fails_rule!( - factory, - r#" - fragment fragA on Dog { ...fragB, ...fragC } - fragment fragB on Dog { ...fragA } - fragment fragC on Dog { ...fragA } - { __typename } - "#, - ); - } - - #[test] - fn no_spreading_itself_deeply_two_paths_alt_traversal_order() { - expect_fails_rule!( - factory, - r#" - fragment fragA on Dog { ...fragC } - fragment fragB on Dog { ...fragC } - fragment fragC on Dog { ...fragA, ...fragB } - { __typename } - "#, - ); - } - - #[test] - fn no_spreading_itself_deeply_and_immediately() { - expect_fails_rule!( - factory, - r#" - fragment fragA on Dog { ...fragB } - fragment fragB on Dog { ...fragB, ...fragC } - fragment fragC on Dog { ...fragA, ...fragB } - { __typename } - "#, - ); - } -} diff --git a/src/validation/rules/no_undefined_variables.rs b/src/validation/rules/no_undefined_variables.rs deleted file mode 100644 index 01805f2c5..000000000 --- a/src/validation/rules/no_undefined_variables.rs +++ /dev/null @@ -1,462 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use async_graphql_value::Value; - -use crate::{ - Name, Pos, Positioned, - parser::types::{ - ExecutableDocument, FragmentDefinition, FragmentSpread, OperationDefinition, - VariableDefinition, - }, - validation::{ - utils::{Scope, referenced_variables}, - visitor::{Visitor, VisitorContext}, - }, -}; - -#[derive(Default)] -pub struct NoUndefinedVariables<'a> { - defined_variables: HashMap, (Pos, HashSet<&'a str>)>, - used_variables: HashMap, HashMap<&'a str, Pos>>, - current_scope: Option>, - spreads: HashMap, Vec<&'a str>>, -} - -impl<'a> NoUndefinedVariables<'a> { - fn find_undef_vars( - &'a self, - scope: &Scope<'a>, - defined: &HashSet<&'a str>, - undef: &mut Vec<(&'a str, Pos)>, - visited: &mut HashSet>, - ) { - if visited.contains(scope) { - return; - } - - visited.insert(*scope); - - if let Some(used_vars) = self.used_variables.get(scope) { - for (var, pos) in used_vars { - if !defined.contains(var) { - undef.push((*var, *pos)); - } - } - } - - if let Some(spreads) = self.spreads.get(scope) { - for spread in spreads { - self.find_undef_vars(&Scope::Fragment(spread), defined, undef, visited); - } - } - } -} - -impl<'a> Visitor<'a> for NoUndefinedVariables<'a> { - fn exit_document(&mut self, ctx: &mut VisitorContext<'a>, _doc: &'a ExecutableDocument) { - for (op_name, (def_pos, def_vars)) in &self.defined_variables { - let mut undef = Vec::new(); - let mut visited = HashSet::new(); - self.find_undef_vars( - &Scope::Operation(*op_name), - def_vars, - &mut undef, - &mut visited, - ); - - for (var, pos) in undef { - if let Some(op_name) = op_name { - ctx.report_error( - vec![*def_pos, pos], - format!( - r#"Variable "${}" is not defined by operation "{}""#, - var, op_name - ), - ); - } else { - ctx.report_error(vec![pos], format!(r#"Variable "${}" is not defined"#, var)); - } - } - } - } - - fn enter_operation_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - name: Option<&'a Name>, - operation_definition: &'a Positioned, - ) { - let name = name.map(Name::as_str); - self.current_scope = Some(Scope::Operation(name)); - self.defined_variables - .insert(name, (operation_definition.pos, HashSet::new())); - } - - fn enter_fragment_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - name: &'a Name, - _fragment_definition: &'a Positioned, - ) { - self.current_scope = Some(Scope::Fragment(name)); - } - - fn enter_variable_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - variable_definition: &'a Positioned, - ) { - if let Some(Scope::Operation(ref name)) = self.current_scope { - if let Some(&mut (_, ref mut vars)) = self.defined_variables.get_mut(name) { - vars.insert(&variable_definition.node.name.node); - } - } - } - - fn enter_argument( - &mut self, - _ctx: &mut VisitorContext<'a>, - name: &'a Positioned, - value: &'a Positioned, - ) { - if let Some(ref scope) = self.current_scope { - self.used_variables.entry(*scope).or_default().extend( - referenced_variables(&value.node) - .into_iter() - .map(|n| (n, name.pos)), - ); - } - } - - fn enter_fragment_spread( - &mut self, - _ctx: &mut VisitorContext<'a>, - fragment_spread: &'a Positioned, - ) { - if let Some(ref scope) = self.current_scope { - self.spreads - .entry(*scope) - .or_default() - .push(&fragment_spread.node.fragment_name.node); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory<'a>() -> NoUndefinedVariables<'a> { - NoUndefinedVariables::default() - } - - #[test] - fn all_variables_defined() { - expect_passes_rule!( - factory, - r#" - query Foo($a: String, $b: String, $c: String) { - field(a: $a, b: $b, c: $c) - } - "#, - ); - } - - #[test] - fn all_variables_deeply_defined() { - expect_passes_rule!( - factory, - r#" - query Foo($a: String, $b: String, $c: String) { - field(a: $a) { - field(b: $b) { - field(c: $c) - } - } - } - "#, - ); - } - - #[test] - fn all_variables_deeply_defined_in_inline_fragments_defined() { - expect_passes_rule!( - factory, - r#" - query Foo($a: String, $b: String, $c: String) { - ... on Type { - field(a: $a) { - field(b: $b) { - ... on Type { - field(c: $c) - } - } - } - } - } - "#, - ); - } - - #[test] - fn all_variables_in_fragments_deeply_defined() { - expect_passes_rule!( - factory, - r#" - query Foo($a: String, $b: String, $c: String) { - ...FragA - } - fragment FragA on Type { - field(a: $a) { - ...FragB - } - } - fragment FragB on Type { - field(b: $b) { - ...FragC - } - } - fragment FragC on Type { - field(c: $c) - } - "#, - ); - } - - #[test] - fn variable_within_single_fragment_defined_in_multiple_operations() { - expect_passes_rule!( - factory, - r#" - query Foo($a: String) { - ...FragA - } - query Bar($a: String) { - ...FragA - } - fragment FragA on Type { - field(a: $a) - } - "#, - ); - } - - #[test] - fn variable_within_fragments_defined_in_operations() { - expect_passes_rule!( - factory, - r#" - query Foo($a: String) { - ...FragA - } - query Bar($b: String) { - ...FragB - } - fragment FragA on Type { - field(a: $a) - } - fragment FragB on Type { - field(b: $b) - } - "#, - ); - } - - #[test] - fn variable_within_recursive_fragment_defined() { - expect_passes_rule!( - factory, - r#" - query Foo($a: String) { - ...FragA - } - fragment FragA on Type { - field(a: $a) { - ...FragA - } - } - "#, - ); - } - - #[test] - fn variable_not_defined() { - expect_fails_rule!( - factory, - r#" - query Foo($a: String, $b: String, $c: String) { - field(a: $a, b: $b, c: $c, d: $d) - } - "#, - ); - } - - #[test] - fn variable_not_defined_by_unnamed_query() { - expect_fails_rule!( - factory, - r#" - { - field(a: $a) - } - "#, - ); - } - - #[test] - fn multiple_variables_not_defined() { - expect_fails_rule!( - factory, - r#" - query Foo($b: String) { - field(a: $a, b: $b, c: $c) - } - "#, - ); - } - - #[test] - fn variable_in_fragment_not_defined_by_unnamed_query() { - expect_fails_rule!( - factory, - r#" - { - ...FragA - } - fragment FragA on Type { - field(a: $a) - } - "#, - ); - } - - #[test] - fn variable_in_fragment_not_defined_by_operation() { - expect_fails_rule!( - factory, - r#" - query Foo($a: String, $b: String) { - ...FragA - } - fragment FragA on Type { - field(a: $a) { - ...FragB - } - } - fragment FragB on Type { - field(b: $b) { - ...FragC - } - } - fragment FragC on Type { - field(c: $c) - } - "#, - ); - } - - #[test] - fn multiple_variables_in_fragments_not_defined() { - expect_fails_rule!( - factory, - r#" - query Foo($b: String) { - ...FragA - } - fragment FragA on Type { - field(a: $a) { - ...FragB - } - } - fragment FragB on Type { - field(b: $b) { - ...FragC - } - } - fragment FragC on Type { - field(c: $c) - } - "#, - ); - } - - #[test] - fn single_variable_in_fragment_not_defined_by_multiple_operations() { - expect_fails_rule!( - factory, - r#" - query Foo($a: String) { - ...FragAB - } - query Bar($a: String) { - ...FragAB - } - fragment FragAB on Type { - field(a: $a, b: $b) - } - "#, - ); - } - - #[test] - fn variables_in_fragment_not_defined_by_multiple_operations() { - expect_fails_rule!( - factory, - r#" - query Foo($b: String) { - ...FragAB - } - query Bar($a: String) { - ...FragAB - } - fragment FragAB on Type { - field(a: $a, b: $b) - } - "#, - ); - } - - #[test] - fn variable_in_fragment_used_by_other_operation() { - expect_fails_rule!( - factory, - r#" - query Foo($b: String) { - ...FragA - } - query Bar($a: String) { - ...FragB - } - fragment FragA on Type { - field(a: $a) - } - fragment FragB on Type { - field(b: $b) - } - "#, - ); - } - - #[test] - fn multiple_undefined_variables_produce_multiple_errors() { - expect_fails_rule!( - factory, - r#" - query Foo($b: String) { - ...FragAB - } - query Bar($a: String) { - ...FragAB - } - fragment FragAB on Type { - field1(a: $a, b: $b) - ...FragC - field3(a: $a, b: $b) - } - fragment FragC on Type { - field2(c: $c) - } - "#, - ); - } -} diff --git a/src/validation/rules/no_unused_fragments.rs b/src/validation/rules/no_unused_fragments.rs deleted file mode 100644 index e82b54bf2..000000000 --- a/src/validation/rules/no_unused_fragments.rs +++ /dev/null @@ -1,244 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use crate::{ - Name, Pos, Positioned, - parser::types::{ExecutableDocument, FragmentDefinition, FragmentSpread, OperationDefinition}, - validation::{ - utils::Scope, - visitor::{Visitor, VisitorContext}, - }, -}; - -#[derive(Default)] -pub struct NoUnusedFragments<'a> { - spreads: HashMap, Vec<&'a str>>, - defined_fragments: HashSet<(&'a str, Pos)>, - current_scope: Option>, -} - -impl<'a> NoUnusedFragments<'a> { - fn find_reachable_fragments(&self, from: &Scope<'a>, result: &mut HashSet<&'a str>) { - if let Scope::Fragment(name) = *from { - if result.contains(name) { - return; - } else { - result.insert(name); - } - } - - if let Some(spreads) = self.spreads.get(from) { - for spread in spreads { - self.find_reachable_fragments(&Scope::Fragment(spread), result) - } - } - } -} - -impl<'a> Visitor<'a> for NoUnusedFragments<'a> { - fn exit_document(&mut self, ctx: &mut VisitorContext<'a>, doc: &'a ExecutableDocument) { - let mut reachable = HashSet::new(); - - for (name, _) in doc.operations.iter() { - self.find_reachable_fragments( - &Scope::Operation(name.map(Name::as_str)), - &mut reachable, - ); - } - - for (fragment_name, pos) in &self.defined_fragments { - if !reachable.contains(fragment_name) { - ctx.report_error( - vec![*pos], - format!(r#"Fragment "{}" is never used"#, fragment_name), - ); - } - } - } - - fn enter_operation_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - name: Option<&'a Name>, - _operation_definition: &'a Positioned, - ) { - self.current_scope = Some(Scope::Operation(name.map(Name::as_str))); - } - - fn enter_fragment_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - name: &'a Name, - fragment_definition: &'a Positioned, - ) { - self.current_scope = Some(Scope::Fragment(name)); - self.defined_fragments - .insert((name, fragment_definition.pos)); - } - - fn enter_fragment_spread( - &mut self, - _ctx: &mut VisitorContext<'a>, - fragment_spread: &'a Positioned, - ) { - if let Some(ref scope) = self.current_scope { - self.spreads - .entry(*scope) - .or_default() - .push(&fragment_spread.node.fragment_name.node); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory<'a>() -> NoUnusedFragments<'a> { - NoUnusedFragments::default() - } - - #[test] - fn all_fragment_names_are_used() { - expect_passes_rule!( - factory, - r#" - { - human(id: 4) { - ...HumanFields1 - ... on Human { - ...HumanFields2 - } - } - } - fragment HumanFields1 on Human { - name - ...HumanFields3 - } - fragment HumanFields2 on Human { - name - } - fragment HumanFields3 on Human { - name - } - "#, - ); - } - - #[test] - fn all_fragment_names_are_used_by_multiple_operations() { - expect_passes_rule!( - factory, - r#" - query Foo { - human(id: 4) { - ...HumanFields1 - } - } - query Bar { - human(id: 4) { - ...HumanFields2 - } - } - fragment HumanFields1 on Human { - name - ...HumanFields3 - } - fragment HumanFields2 on Human { - name - } - fragment HumanFields3 on Human { - name - } - "#, - ); - } - - #[test] - fn contains_unknown_fragments() { - expect_fails_rule!( - factory, - r#" - query Foo { - human(id: 4) { - ...HumanFields1 - } - } - query Bar { - human(id: 4) { - ...HumanFields2 - } - } - fragment HumanFields1 on Human { - name - ...HumanFields3 - } - fragment HumanFields2 on Human { - name - } - fragment HumanFields3 on Human { - name - } - fragment Unused1 on Human { - name - } - fragment Unused2 on Human { - name - } - "#, - ); - } - - #[test] - fn contains_unknown_fragments_with_ref_cycle() { - expect_fails_rule!( - factory, - r#" - query Foo { - human(id: 4) { - ...HumanFields1 - } - } - query Bar { - human(id: 4) { - ...HumanFields2 - } - } - fragment HumanFields1 on Human { - name - ...HumanFields3 - } - fragment HumanFields2 on Human { - name - } - fragment HumanFields3 on Human { - name - } - fragment Unused1 on Human { - name - ...Unused2 - } - fragment Unused2 on Human { - name - ...Unused1 - } - "#, - ); - } - - #[test] - fn contains_unknown_and_undef_fragments() { - expect_fails_rule!( - factory, - r#" - query Foo { - human(id: 4) { - ...bar - } - } - fragment foo on Human { - name - } - "#, - ); - } -} diff --git a/src/validation/rules/no_unused_variables.rs b/src/validation/rules/no_unused_variables.rs deleted file mode 100644 index 515ec0100..000000000 --- a/src/validation/rules/no_unused_variables.rs +++ /dev/null @@ -1,390 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use async_graphql_value::Value; - -use crate::{ - Name, Pos, Positioned, - parser::types::{ - ExecutableDocument, FragmentDefinition, FragmentSpread, OperationDefinition, - VariableDefinition, - }, - validation::{ - utils::{Scope, referenced_variables}, - visitor::{Visitor, VisitorContext}, - }, -}; - -#[derive(Default)] -pub struct NoUnusedVariables<'a> { - defined_variables: HashMap, HashSet<(&'a str, Pos)>>, - used_variables: HashMap, Vec<&'a str>>, - current_scope: Option>, - spreads: HashMap, Vec<&'a str>>, -} - -impl<'a> NoUnusedVariables<'a> { - fn find_used_vars( - &self, - from: &Scope<'a>, - defined: &HashSet<&'a str>, - used: &mut HashSet<&'a str>, - visited: &mut HashSet>, - ) { - if visited.contains(from) { - return; - } - - visited.insert(*from); - - if let Some(used_vars) = self.used_variables.get(from) { - for var in used_vars { - if defined.contains(var) { - used.insert(var); - } - } - } - - if let Some(spreads) = self.spreads.get(from) { - for spread in spreads { - self.find_used_vars(&Scope::Fragment(spread), defined, used, visited); - } - } - } -} - -impl<'a> Visitor<'a> for NoUnusedVariables<'a> { - fn exit_document(&mut self, ctx: &mut VisitorContext<'a>, _doc: &'a ExecutableDocument) { - for (op_name, def_vars) in &self.defined_variables { - let mut used = HashSet::new(); - let mut visited = HashSet::new(); - self.find_used_vars( - &Scope::Operation(*op_name), - &def_vars.iter().map(|(name, _)| *name).collect(), - &mut used, - &mut visited, - ); - - for (var, pos) in def_vars.iter().filter(|(var, _)| !used.contains(var)) { - if let Some(op_name) = op_name { - ctx.report_error( - vec![*pos], - format!( - r#"Variable "${}" is not used by operation "{}""#, - var, op_name - ), - ); - } else { - ctx.report_error(vec![*pos], format!(r#"Variable "${}" is not used"#, var)); - } - } - } - } - - fn enter_operation_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - name: Option<&'a Name>, - _operation_definition: &'a Positioned, - ) { - let op_name = name.map(Name::as_str); - self.current_scope = Some(Scope::Operation(op_name)); - self.defined_variables.insert(op_name, HashSet::new()); - } - - fn enter_fragment_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - name: &'a Name, - _fragment_definition: &'a Positioned, - ) { - self.current_scope = Some(Scope::Fragment(name)); - } - - fn enter_variable_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - variable_definition: &'a Positioned, - ) { - if let Some(Scope::Operation(ref name)) = self.current_scope { - if let Some(vars) = self.defined_variables.get_mut(name) { - vars.insert((&variable_definition.node.name.node, variable_definition.pos)); - } - } - } - - fn enter_argument( - &mut self, - _ctx: &mut VisitorContext<'a>, - _name: &'a Positioned, - value: &'a Positioned, - ) { - if let Some(ref scope) = self.current_scope { - self.used_variables - .entry(*scope) - .or_default() - .append(&mut referenced_variables(&value.node)); - } - } - - fn enter_fragment_spread( - &mut self, - _ctx: &mut VisitorContext<'a>, - fragment_spread: &'a Positioned, - ) { - if let Some(ref scope) = self.current_scope { - self.spreads - .entry(*scope) - .or_default() - .push(&fragment_spread.node.fragment_name.node); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory<'a>() -> NoUnusedVariables<'a> { - NoUnusedVariables::default() - } - - #[test] - fn uses_all_variables() { - expect_passes_rule!( - factory, - r#" - query ($a: String, $b: String, $c: String) { - field(a: $a, b: $b, c: $c) - } - "#, - ); - } - - #[test] - fn uses_all_variables_deeply() { - expect_passes_rule!( - factory, - r#" - query Foo($a: String, $b: String, $c: String) { - field(a: $a) { - field(b: $b) { - field(c: $c) - } - } - } - "#, - ); - } - - #[test] - fn uses_all_variables_deeply_in_inline_fragments() { - expect_passes_rule!( - factory, - r#" - query Foo($a: String, $b: String, $c: String) { - ... on Type { - field(a: $a) { - field(b: $b) { - ... on Type { - field(c: $c) - } - } - } - } - } - "#, - ); - } - - #[test] - fn uses_all_variables_in_fragments() { - expect_passes_rule!( - factory, - r#" - query Foo($a: String, $b: String, $c: String) { - ...FragA - } - fragment FragA on Type { - field(a: $a) { - ...FragB - } - } - fragment FragB on Type { - field(b: $b) { - ...FragC - } - } - fragment FragC on Type { - field(c: $c) - } - "#, - ); - } - - #[test] - fn variable_used_by_fragment_in_multiple_operations() { - expect_passes_rule!( - factory, - r#" - query Foo($a: String) { - ...FragA - } - query Bar($b: String) { - ...FragB - } - fragment FragA on Type { - field(a: $a) - } - fragment FragB on Type { - field(b: $b) - } - "#, - ); - } - - #[test] - fn variable_used_by_recursive_fragment() { - expect_passes_rule!( - factory, - r#" - query Foo($a: String) { - ...FragA - } - fragment FragA on Type { - field(a: $a) { - ...FragA - } - } - "#, - ); - } - - #[test] - fn variable_used_by_inline_fragment() { - expect_passes_rule!( - factory, - r#" - query Foo($a: String) { - ... { - field(a: $a) { - ...FragA - } - } - } - "#, - ); - } - - #[test] - fn variable_not_used() { - expect_fails_rule!( - factory, - r#" - query ($a: String, $b: String, $c: String) { - field(a: $a, b: $b) - } - "#, - ); - } - - #[test] - fn multiple_variables_not_used_1() { - expect_fails_rule!( - factory, - r#" - query Foo($a: String, $b: String, $c: String) { - field(b: $b) - } - "#, - ); - } - - #[test] - fn variable_not_used_in_fragment() { - expect_fails_rule!( - factory, - r#" - query Foo($a: String, $b: String, $c: String) { - ...FragA - } - fragment FragA on Type { - field(a: $a) { - ...FragB - } - } - fragment FragB on Type { - field(b: $b) { - ...FragC - } - } - fragment FragC on Type { - field - } - "#, - ); - } - - #[test] - fn multiple_variables_not_used_2() { - expect_fails_rule!( - factory, - r#" - query Foo($a: String, $b: String, $c: String) { - ...FragA - } - fragment FragA on Type { - field { - ...FragB - } - } - fragment FragB on Type { - field(b: $b) { - ...FragC - } - } - fragment FragC on Type { - field - } - "#, - ); - } - - #[test] - fn variable_not_used_by_unreferenced_fragment() { - expect_fails_rule!( - factory, - r#" - query Foo($b: String) { - ...FragA - } - fragment FragA on Type { - field(a: $a) - } - fragment FragB on Type { - field(b: $b) - } - "#, - ); - } - - #[test] - fn variable_not_used_by_fragment_used_by_other_operation() { - expect_fails_rule!( - factory, - r#" - query Foo($b: String) { - ...FragA - } - query Bar($a: String) { - ...FragB - } - fragment FragA on Type { - field(a: $a) - } - fragment FragB on Type { - field(b: $b) - } - "#, - ); - } -} diff --git a/src/validation/rules/overlapping_fields_can_be_merged.rs b/src/validation/rules/overlapping_fields_can_be_merged.rs deleted file mode 100644 index 885d8c957..000000000 --- a/src/validation/rules/overlapping_fields_can_be_merged.rs +++ /dev/null @@ -1,175 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use crate::{ - Positioned, - parser::types::{Field, Selection, SelectionSet}, - validation::visitor::{Visitor, VisitorContext}, -}; - -#[derive(Default)] -pub struct OverlappingFieldsCanBeMerged; - -impl<'a> Visitor<'a> for OverlappingFieldsCanBeMerged { - fn enter_selection_set( - &mut self, - ctx: &mut VisitorContext<'a>, - selection_set: &'a Positioned, - ) { - let mut find_conflicts = FindConflicts { - outputs: Default::default(), - visited: Default::default(), - ctx, - }; - find_conflicts.find(None, selection_set); - } -} - -struct FindConflicts<'a, 'ctx> { - outputs: HashMap<(Option<&'a str>, &'a str), &'a Positioned>, - visited: HashSet<&'a str>, - ctx: &'a mut VisitorContext<'ctx>, -} - -impl<'a> FindConflicts<'a, '_> { - pub fn find(&mut self, on_type: Option<&'a str>, selection_set: &'a Positioned) { - for selection in &selection_set.node.items { - match &selection.node { - Selection::Field(field) => { - let output_name = field - .node - .alias - .as_ref() - .map(|name| &name.node) - .unwrap_or_else(|| &field.node.name.node); - self.add_output(on_type, &output_name, field); - } - Selection::InlineFragment(inline_fragment) => { - let on_type = inline_fragment - .node - .type_condition - .as_ref() - .map(|cond| cond.node.on.node.as_str()); - self.find(on_type, &inline_fragment.node.selection_set); - } - Selection::FragmentSpread(fragment_spread) => { - if let Some(fragment) = - self.ctx.fragment(&fragment_spread.node.fragment_name.node) - { - let on_type = Some(fragment.node.type_condition.node.on.node.as_str()); - - if !self - .visited - .insert(fragment_spread.node.fragment_name.node.as_str()) - { - // To avoid recursing itself, this error is detected by the - // `NoFragmentCycles` validator. - continue; - } - - self.find(on_type, &fragment.node.selection_set); - } - } - } - } - } - - fn add_output( - &mut self, - on_type: Option<&'a str>, - name: &'a str, - field: &'a Positioned, - ) { - if let Some(prev_field) = self.outputs.get(&(on_type, name)) { - if prev_field.node.name.node != field.node.name.node { - self.ctx.report_error( - vec![prev_field.pos, field.pos], - format!("Fields \"{}\" conflict because \"{}\" and \"{}\" are different fields. Use different aliases on the fields to fetch both if this was intentional.", - name, prev_field.node.name.node, field.node.name.node)); - } - - // check arguments - if prev_field.node.arguments.len() != field.node.arguments.len() { - self.ctx.report_error( - vec![prev_field.pos, field.pos], - format!("Fields \"{}\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.", name)); - } - - for (name, value) in &prev_field.node.arguments { - match field.node.get_argument(&name.node) { - Some(other_value) if value == other_value => {} - _=> self.ctx.report_error( - vec![prev_field.pos, field.pos], - format!("Fields \"{}\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.", name)), - } - } - } else { - self.outputs.insert((on_type, name), field); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory() -> OverlappingFieldsCanBeMerged { - OverlappingFieldsCanBeMerged - } - - #[test] - fn same_field_on_different_type() { - expect_passes_rule!( - factory, - r#" - { - pet { - ... on Dog { - doesKnowCommand(dogCommand: SIT) - } - ... on Cat { - doesKnowCommand(catCommand: JUMP) - } - } - } - "#, - ); - } - - #[test] - fn same_field_on_same_type() { - expect_fails_rule!( - factory, - r#" - { - pet { - ... on Dog { - doesKnowCommand(dogCommand: SIT) - } - ... on Dog { - doesKnowCommand(dogCommand: Heel) - } - } - } - "#, - ); - } - - #[test] - fn same_alias_on_different_type() { - expect_passes_rule!( - factory, - r#" - { - pet { - ... on Dog { - volume: barkVolume - } - ... on Cat { - volume: meowVolume - } - } - } - "#, - ); - } -} diff --git a/src/validation/rules/possible_fragment_spreads.rs b/src/validation/rules/possible_fragment_spreads.rs deleted file mode 100644 index 28f7ab593..000000000 --- a/src/validation/rules/possible_fragment_spreads.rs +++ /dev/null @@ -1,350 +0,0 @@ -use std::collections::HashMap; - -use crate::{ - Positioned, - parser::types::{ExecutableDocument, FragmentSpread, InlineFragment, TypeCondition}, - validation::visitor::{Visitor, VisitorContext}, -}; - -#[derive(Default)] -pub struct PossibleFragmentSpreads<'a> { - fragment_types: HashMap<&'a str, &'a str>, -} - -impl<'a> Visitor<'a> for PossibleFragmentSpreads<'a> { - fn enter_document(&mut self, _ctx: &mut VisitorContext<'a>, doc: &'a ExecutableDocument) { - for (name, fragment) in doc.fragments.iter() { - self.fragment_types - .insert(name.as_str(), &fragment.node.type_condition.node.on.node); - } - } - - fn enter_fragment_spread( - &mut self, - ctx: &mut VisitorContext<'a>, - fragment_spread: &'a Positioned, - ) { - if let Some(fragment_type) = self - .fragment_types - .get(&*fragment_spread.node.fragment_name.node) - { - if let Some(current_type) = ctx.current_type() { - if let Some(on_type) = ctx.registry.types.get(*fragment_type) { - if !current_type.type_overlap(on_type) { - ctx.report_error( - vec![fragment_spread.pos], - format!( - "Fragment \"{}\" cannot be spread here as objects of type \"{}\" can never be of type \"{}\"", - fragment_spread.node.fragment_name.node, current_type.name(), fragment_type - ), - ); - } - } - } - } - } - - fn enter_inline_fragment( - &mut self, - ctx: &mut VisitorContext<'a>, - inline_fragment: &'a Positioned, - ) { - if let Some(parent_type) = ctx.parent_type() { - if let Some(TypeCondition { on: fragment_type }) = &inline_fragment - .node - .type_condition - .as_ref() - .map(|c| &c.node) - { - if let Some(on_type) = ctx.registry.types.get(fragment_type.node.as_str()) { - if !parent_type.type_overlap(&on_type) { - ctx.report_error( - vec![inline_fragment.pos], - format!( - "Fragment cannot be spread here as objects of type \"{}\" \ - can never be of type \"{}\"", - parent_type.name(), - fragment_type - ), - ) - } - } - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory<'a>() -> PossibleFragmentSpreads<'a> { - PossibleFragmentSpreads::default() - } - - #[test] - fn of_the_same_object() { - expect_passes_rule!( - factory, - r#" - fragment objectWithinObject on Dog { ...dogFragment } - fragment dogFragment on Dog { barkVolume } - { __typename } - "#, - ); - } - - #[test] - fn of_the_same_object_with_inline_fragment() { - expect_passes_rule!( - factory, - r#" - fragment objectWithinObjectAnon on Dog { ... on Dog { barkVolume } } - { __typename } - "#, - ); - } - - #[test] - fn object_into_an_implemented_interface() { - expect_passes_rule!( - factory, - r#" - fragment objectWithinInterface on Pet { ...dogFragment } - fragment dogFragment on Dog { barkVolume } - { __typename } - "#, - ); - } - - #[test] - fn object_into_containing_union() { - expect_passes_rule!( - factory, - r#" - fragment objectWithinUnion on CatOrDog { ...dogFragment } - fragment dogFragment on Dog { barkVolume } - { __typename } - "#, - ); - } - - #[test] - fn union_into_contained_object() { - expect_passes_rule!( - factory, - r#" - fragment unionWithinObject on Dog { ...catOrDogFragment } - fragment catOrDogFragment on CatOrDog { __typename } - { __typename } - "#, - ); - } - - #[test] - fn union_into_overlapping_interface() { - expect_passes_rule!( - factory, - r#" - fragment unionWithinInterface on Pet { ...catOrDogFragment } - fragment catOrDogFragment on CatOrDog { __typename } - { __typename } - "#, - ); - } - - #[test] - fn union_into_overlapping_union() { - expect_passes_rule!( - factory, - r#" - fragment unionWithinUnion on DogOrHuman { ...catOrDogFragment } - fragment catOrDogFragment on CatOrDog { __typename } - { __typename } - "#, - ); - } - - #[test] - fn interface_into_implemented_object() { - expect_passes_rule!( - factory, - r#" - fragment interfaceWithinObject on Dog { ...petFragment } - fragment petFragment on Pet { name } - { __typename } - "#, - ); - } - - #[test] - fn interface_into_overlapping_interface() { - expect_passes_rule!( - factory, - r#" - fragment interfaceWithinInterface on Pet { ...beingFragment } - fragment beingFragment on Being { name } - { __typename } - "#, - ); - } - - #[test] - fn interface_into_overlapping_interface_in_inline_fragment() { - expect_passes_rule!( - factory, - r#" - fragment interfaceWithinInterface on Pet { ... on Being { name } } - { __typename } - "#, - ); - } - - #[test] - fn interface_into_overlapping_union() { - expect_passes_rule!( - factory, - r#" - fragment interfaceWithinUnion on CatOrDog { ...petFragment } - fragment petFragment on Pet { name } - { __typename } - "#, - ); - } - - #[test] - fn different_object_into_object() { - expect_fails_rule!( - factory, - r#" - fragment invalidObjectWithinObject on Cat { ...dogFragment } - fragment dogFragment on Dog { barkVolume } - { __typename } - "#, - ); - } - - #[test] - fn different_object_into_object_in_inline_fragment() { - expect_fails_rule!( - factory, - r#" - fragment invalidObjectWithinObjectAnon on Cat { - ... on Dog { barkVolume } - } - { __typename } - "#, - ); - } - - #[test] - fn object_into_not_implementing_interface() { - expect_fails_rule!( - factory, - r#" - fragment invalidObjectWithinInterface on Pet { ...humanFragment } - fragment humanFragment on Human { pets { name } } - { __typename } - "#, - ); - } - - #[test] - fn object_into_not_containing_union() { - expect_fails_rule!( - factory, - r#" - fragment invalidObjectWithinUnion on CatOrDog { ...humanFragment } - fragment humanFragment on Human { pets { name } } - { __typename } - "#, - ); - } - - #[test] - fn union_into_not_contained_object() { - expect_fails_rule!( - factory, - r#" - fragment invalidUnionWithinObject on Human { ...catOrDogFragment } - fragment catOrDogFragment on CatOrDog { __typename } - { __typename } - "#, - ); - } - - #[test] - fn union_into_non_overlapping_interface() { - expect_fails_rule!( - factory, - r#" - fragment invalidUnionWithinInterface on Pet { ...humanOrAlienFragment } - fragment humanOrAlienFragment on HumanOrAlien { __typename } - { __typename } - "#, - ); - } - - #[test] - fn union_into_non_overlapping_union() { - expect_fails_rule!( - factory, - r#" - fragment invalidUnionWithinUnion on CatOrDog { ...humanOrAlienFragment } - fragment humanOrAlienFragment on HumanOrAlien { __typename } - { __typename } - "#, - ); - } - - #[test] - fn interface_into_non_implementing_object() { - expect_fails_rule!( - factory, - r#" - fragment invalidInterfaceWithinObject on Cat { ...intelligentFragment } - fragment intelligentFragment on Intelligent { iq } - { __typename } - "#, - ); - } - - #[test] - fn interface_into_non_overlapping_interface() { - expect_fails_rule!( - factory, - r#" - fragment invalidInterfaceWithinInterface on Pet { - ...intelligentFragment - } - fragment intelligentFragment on Intelligent { iq } - { __typename } - "#, - ); - } - - #[test] - fn interface_into_non_overlapping_interface_in_inline_fragment() { - expect_fails_rule!( - factory, - r#" - fragment invalidInterfaceWithinInterfaceAnon on Pet { - ...on Intelligent { iq } - } - { __typename } - "#, - ); - } - - #[test] - fn interface_into_non_overlapping_union() { - expect_fails_rule!( - factory, - r#" - fragment invalidInterfaceWithinUnion on HumanOrAlien { ...petFragment } - fragment petFragment on Pet { name } - { __typename } - "#, - ); - } -} diff --git a/src/validation/rules/provided_non_null_arguments.rs b/src/validation/rules/provided_non_null_arguments.rs deleted file mode 100644 index 7c6c1263a..000000000 --- a/src/validation/rules/provided_non_null_arguments.rs +++ /dev/null @@ -1,311 +0,0 @@ -use crate::{ - Positioned, - parser::types::{Directive, Field}, - registry::MetaTypeName, - validation::visitor::{Visitor, VisitorContext}, -}; - -#[derive(Default)] -pub struct ProvidedNonNullArguments; - -impl<'a> Visitor<'a> for ProvidedNonNullArguments { - fn enter_directive( - &mut self, - ctx: &mut VisitorContext<'a>, - directive: &'a Positioned, - ) { - if let Some(schema_directive) = ctx - .registry - .directives - .get(directive.node.name.node.as_str()) - { - for arg in schema_directive.args.values() { - if MetaTypeName::create(&arg.ty).is_non_null() - && arg.default_value.is_none() - && !directive - .node - .arguments - .iter() - .any(|(name, _)| name.node == arg.name) - { - ctx.report_error(vec![directive.pos], - format!( - "Directive \"@{}\" argument \"{}\" of type \"{}\" is required but not provided", - directive.node.name, arg.name, arg.ty - )); - } - } - } - } - - fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Positioned) { - if let Some(parent_type) = ctx.parent_type() { - if let Some(schema_field) = parent_type.field_by_name(&field.node.name.node) { - for arg in schema_field.args.values() { - if MetaTypeName::create(&arg.ty).is_non_null() - && arg.default_value.is_none() - && !field - .node - .arguments - .iter() - .any(|(name, _)| name.node == arg.name) - { - ctx.report_error(vec![field.pos], - format!( - r#"Field "{}" argument "{}" of type "{}" is required but not provided"#, - field.node.name, arg.name, parent_type.name() - )); - } - } - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory() -> ProvidedNonNullArguments { - ProvidedNonNullArguments - } - - #[test] - fn ignores_unknown_arguments() { - expect_passes_rule!( - factory, - r#" - { - dog { - isHousetrained(unknownArgument: true) - } - } - "#, - ); - } - - #[test] - fn arg_on_optional_arg() { - expect_passes_rule!( - factory, - r#" - { - dog { - isHousetrained(atOtherHomes: true) - } - } - "#, - ); - } - - #[test] - fn no_arg_on_optional_arg() { - expect_passes_rule!( - factory, - r#" - { - dog { - isHousetrained - } - } - "#, - ); - } - - #[test] - fn multiple_args() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - multipleReqs(req1: 1, req2: 2) - } - } - "#, - ); - } - - #[test] - fn multiple_args_reverse_order() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - multipleReqs(req2: 2, req1: 1) - } - } - "#, - ); - } - - #[test] - fn no_args_on_multiple_optional() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - multipleOpts - } - } - "#, - ); - } - - #[test] - fn one_arg_on_multiple_optional() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - multipleOpts(opt1: 1) - } - } - "#, - ); - } - - #[test] - fn second_arg_on_multiple_optional() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - multipleOpts(opt2: 1) - } - } - "#, - ); - } - - #[test] - fn muliple_reqs_on_mixed_list() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - multipleOptAndReq(req1: 3, req2: 4) - } - } - "#, - ); - } - - #[test] - fn multiple_reqs_and_one_opt_on_mixed_list() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - multipleOptAndReq(req1: 3, req2: 4, opt1: 5) - } - } - "#, - ); - } - - #[test] - fn all_reqs_on_opts_on_mixed_list() { - expect_passes_rule!( - factory, - r#" - { - complicatedArgs { - multipleOptAndReq(req1: 3, req2: 4, opt1: 5, opt2: 6) - } - } - "#, - ); - } - - #[test] - fn missing_one_non_nullable_argument() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - multipleReqs(req2: 2) - } - } - "#, - ); - } - - #[test] - fn missing_multiple_non_nullable_arguments() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - multipleReqs - } - } - "#, - ); - } - - #[test] - fn incorrect_value_and_missing_argument() { - expect_fails_rule!( - factory, - r#" - { - complicatedArgs { - multipleReqs(req1: "one") - } - } - "#, - ); - } - - #[test] - fn ignores_unknown_directives() { - expect_passes_rule!( - factory, - r#" - { - dog @unknown - } - "#, - ); - } - - #[test] - fn with_directives_of_valid_types() { - expect_passes_rule!( - factory, - r#" - { - dog @include(if: true) { - name - } - human @skip(if: false) { - name - } - } - "#, - ); - } - - #[test] - fn with_directive_with_missing_types() { - expect_fails_rule!( - factory, - r#" - { - dog @include { - name @skip - } - } - "#, - ); - } -} diff --git a/src/validation/rules/scalar_leafs.rs b/src/validation/rules/scalar_leafs.rs deleted file mode 100644 index ea05b44b5..000000000 --- a/src/validation/rules/scalar_leafs.rs +++ /dev/null @@ -1,158 +0,0 @@ -use crate::{ - Positioned, - parser::types::Field, - validation::visitor::{Visitor, VisitorContext}, -}; - -#[derive(Default)] -pub struct ScalarLeafs; - -impl<'a> Visitor<'a> for ScalarLeafs { - fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Positioned) { - if let Some(ty) = ctx.parent_type() { - if let Some(schema_field) = ty.field_by_name(&field.node.name.node) { - if let Some(ty) = ctx.registry.concrete_type_by_name(&schema_field.ty) { - if ty.is_leaf() && !field.node.selection_set.node.items.is_empty() { - ctx.report_error(vec![field.pos], format!( - "Field \"{}\" must not have a selection since type \"{}\" has no subfields", - field.node.name, ty.name() - )) - } else if !ty.is_leaf() && field.node.selection_set.node.items.is_empty() { - ctx.report_error( - vec![field.pos], - format!( - "Field \"{}\" of type \"{}\" must have a selection of subfields", - field.node.name, - ty.name() - ), - ) - } - } - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory() -> ScalarLeafs { - ScalarLeafs - } - - #[test] - fn valid_scalar_selection() { - expect_passes_rule!( - factory, - r#" - fragment scalarSelection on Dog { - barks - } - { __typename } - "#, - ); - } - - #[test] - fn object_type_missing_selection() { - expect_fails_rule!( - factory, - r#" - query directQueryOnObjectWithoutSubFields { - human - } - "#, - ); - } - - #[test] - fn interface_type_missing_selection() { - expect_fails_rule!( - factory, - r#" - { - human { pets } - } - "#, - ); - } - - #[test] - fn valid_scalar_selection_with_args() { - expect_passes_rule!( - factory, - r#" - fragment scalarSelectionWithArgs on Dog { - doesKnowCommand(dogCommand: SIT) - } - { __typename } - "#, - ); - } - - #[test] - fn scalar_selection_not_allowed_on_boolean() { - expect_fails_rule!( - factory, - r#" - fragment scalarSelectionsNotAllowedOnBoolean on Dog { - barks { sinceWhen } - } - { __typename } - "#, - ); - } - - #[test] - fn scalar_selection_not_allowed_on_enum() { - expect_fails_rule!( - factory, - r#" - fragment scalarSelectionsNotAllowedOnEnum on Cat { - furColor { inHexdec } - } - { __typename } - "#, - ); - } - - #[test] - fn scalar_selection_not_allowed_with_args() { - expect_fails_rule!( - factory, - r#" - fragment scalarSelectionsNotAllowedWithArgs on Dog { - doesKnowCommand(dogCommand: SIT) { sinceWhen } - } - { __typename } - "#, - ); - } - - #[test] - fn scalar_selection_not_allowed_with_directives() { - expect_fails_rule!( - factory, - r#" - fragment scalarSelectionsNotAllowedWithDirectives on Dog { - name @include(if: true) { isAlsoHumanName } - } - { __typename } - "#, - ); - } - - #[test] - fn scalar_selection_not_allowed_with_directives_and_args() { - expect_fails_rule!( - factory, - r#" - fragment scalarSelectionsNotAllowedWithDirectivesAndArgs on Dog { - doesKnowCommand(dogCommand: SIT) @include(if: true) { sinceWhen } - } - { __typename } - "#, - ); - } -} diff --git a/src/validation/rules/unique_argument_names.rs b/src/validation/rules/unique_argument_names.rs deleted file mode 100644 index 2b53a5bb1..000000000 --- a/src/validation/rules/unique_argument_names.rs +++ /dev/null @@ -1,208 +0,0 @@ -use std::collections::HashSet; - -use async_graphql_value::Value; - -use crate::{ - Name, Positioned, - parser::types::{Directive, Field}, - validation::visitor::{Visitor, VisitorContext}, -}; - -#[derive(Default)] -pub struct UniqueArgumentNames<'a> { - names: HashSet<&'a str>, -} - -impl<'a> Visitor<'a> for UniqueArgumentNames<'a> { - fn enter_directive( - &mut self, - _ctx: &mut VisitorContext<'a>, - _directive: &'a Positioned, - ) { - self.names.clear(); - } - - fn enter_argument( - &mut self, - ctx: &mut VisitorContext<'a>, - name: &'a Positioned, - _value: &'a Positioned, - ) { - if !self.names.insert(name.node.as_str()) { - ctx.report_error( - vec![name.pos], - format!("There can only be one argument named \"{}\"", name), - ) - } - } - - fn enter_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Positioned) { - self.names.clear(); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory<'a>() -> UniqueArgumentNames<'a> { - UniqueArgumentNames::default() - } - - #[test] - fn no_arguments_on_field() { - expect_passes_rule!( - factory, - r#" - { - field - } - "#, - ); - } - - #[test] - fn no_arguments_on_directive() { - expect_passes_rule!( - factory, - r#" - { - dog @directive - } - "#, - ); - } - - #[test] - fn argument_on_field() { - expect_passes_rule!( - factory, - r#" - { - field(arg: "value") - } - "#, - ); - } - - #[test] - fn argument_on_directive() { - expect_passes_rule!( - factory, - r#" - { - dog @directive(arg: "value") - } - "#, - ); - } - - #[test] - fn same_argument_on_two_fields() { - expect_passes_rule!( - factory, - r#" - { - one: field(arg: "value") - two: field(arg: "value") - } - "#, - ); - } - - #[test] - fn same_argument_on_field_and_directive() { - expect_passes_rule!( - factory, - r#" - { - field(arg: "value") @directive(arg: "value") - } - "#, - ); - } - - #[test] - fn same_argument_on_two_directives() { - expect_passes_rule!( - factory, - r#" - { - field @directive1(arg: "value") @directive2(arg: "value") - } - "#, - ); - } - - #[test] - fn multiple_field_arguments() { - expect_passes_rule!( - factory, - r#" - { - field(arg1: "value", arg2: "value", arg3: "value") - } - "#, - ); - } - - #[test] - fn multiple_directive_arguments() { - expect_passes_rule!( - factory, - r#" - { - field @directive(arg1: "value", arg2: "value", arg3: "value") - } - "#, - ); - } - - #[test] - fn duplicate_field_arguments() { - expect_fails_rule!( - factory, - r#" - { - field(arg1: "value", arg1: "value") - } - "#, - ); - } - - #[test] - fn many_duplicate_field_arguments() { - expect_fails_rule!( - factory, - r#" - { - field(arg1: "value", arg1: "value", arg1: "value") - } - "#, - ); - } - - #[test] - fn duplicate_directive_arguments() { - expect_fails_rule!( - factory, - r#" - { - field @directive(arg1: "value", arg1: "value") - } - "#, - ); - } - - #[test] - fn many_duplicate_directive_arguments() { - expect_fails_rule!( - factory, - r#" - { - field @directive(arg1: "value", arg1: "value", arg1: "value") - } - "#, - ); - } -} diff --git a/src/validation/rules/unique_variable_names.rs b/src/validation/rules/unique_variable_names.rs deleted file mode 100644 index 38e700bb6..000000000 --- a/src/validation/rules/unique_variable_names.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::collections::HashSet; - -use crate::{ - Name, Positioned, - parser::types::{OperationDefinition, VariableDefinition}, - validation::visitor::{Visitor, VisitorContext}, -}; - -#[derive(Default)] -pub struct UniqueVariableNames<'a> { - names: HashSet<&'a str>, -} - -impl<'a> Visitor<'a> for UniqueVariableNames<'a> { - fn enter_operation_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - _name: Option<&'a Name>, - _operation_definition: &'a Positioned, - ) { - self.names.clear(); - } - - fn enter_variable_definition( - &mut self, - ctx: &mut VisitorContext<'a>, - variable_definition: &'a Positioned, - ) { - if !self.names.insert(&variable_definition.node.name.node) { - ctx.report_error( - vec![variable_definition.pos], - format!( - "There can only be one variable named \"${}\"", - variable_definition.node.name.node - ), - ); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory<'a>() -> UniqueVariableNames<'a> { - UniqueVariableNames::default() - } - - #[test] - fn unique_variable_names() { - expect_passes_rule!( - factory, - r#" - query A($x: Int, $y: String) { __typename } - query B($x: String, $y: Int) { __typename } - "#, - ); - } - - #[test] - fn duplicate_variable_names() { - expect_fails_rule!( - factory, - r#" - query A($x: Int, $x: Int, $x: String) { __typename } - query B($x: String, $x: Int) { __typename } - query C($x: Int, $x: Int) { __typename } - "#, - ); - } -} diff --git a/src/validation/rules/upload_file.rs b/src/validation/rules/upload_file.rs deleted file mode 100644 index 4844af4d0..000000000 --- a/src/validation/rules/upload_file.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::{ - Name, Positioned, - parser::types::{OperationDefinition, OperationType}, - validation::visitor::{Visitor, VisitorContext}, -}; - -#[derive(Default)] -pub struct UploadFile; - -impl<'a> Visitor<'a> for UploadFile { - fn enter_operation_definition( - &mut self, - ctx: &mut VisitorContext<'a>, - _name: Option<&'a Name>, - operation_definition: &'a Positioned, - ) { - for var in &operation_definition.node.variable_definitions { - if let Some(ty) = ctx - .registry - .concrete_type_by_parsed_type(&var.node.var_type.node) - { - if operation_definition.node.ty != OperationType::Mutation && ty.name() == "Upload" - { - ctx.report_error( - vec![var.pos], - "The Upload type is only allowed to be defined on a mutation", - ); - } - } - } - } -} diff --git a/src/validation/rules/variables_are_input_types.rs b/src/validation/rules/variables_are_input_types.rs deleted file mode 100644 index 510480b40..000000000 --- a/src/validation/rules/variables_are_input_types.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::{ - Positioned, - parser::types::VariableDefinition, - validation::visitor::{Visitor, VisitorContext}, -}; - -#[derive(Default)] -pub struct VariablesAreInputTypes; - -impl<'a> Visitor<'a> for VariablesAreInputTypes { - fn enter_variable_definition( - &mut self, - ctx: &mut VisitorContext<'a>, - variable_definition: &'a Positioned, - ) { - if let Some(ty) = ctx - .registry - .concrete_type_by_parsed_type(&variable_definition.node.var_type.node) - { - if !ty.is_input() { - ctx.report_error( - vec![variable_definition.pos], - format!( - "Variable \"{}\" cannot be of non-input type \"{}\"", - variable_definition.node.name.node, - ty.name() - ), - ); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory() -> VariablesAreInputTypes { - VariablesAreInputTypes - } - - #[test] - fn input_types_are_valid() { - expect_passes_rule!( - factory, - r#" - query Foo($a: String, $b: [Boolean!]!, $c: ComplexInput) { - field(a: $a, b: $b, c: $c) - } - "#, - ); - } - - #[test] - fn output_types_are_invalid() { - expect_fails_rule!( - factory, - r#" - query Foo($a: Dog, $b: [[CatOrDog!]]!, $c: Pet) { - field(a: $a, b: $b, c: $c) - } - "#, - ); - } -} diff --git a/src/validation/rules/variables_in_allowed_position.rs b/src/validation/rules/variables_in_allowed_position.rs deleted file mode 100644 index 204351694..000000000 --- a/src/validation/rules/variables_in_allowed_position.rs +++ /dev/null @@ -1,466 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use async_graphql_value::Value; - -use crate::{ - Name, Pos, Positioned, - parser::types::{ - ExecutableDocument, FragmentDefinition, FragmentSpread, OperationDefinition, - VariableDefinition, - }, - registry::MetaTypeName, - validation::{ - utils::Scope, - visitor::{Visitor, VisitorContext}, - }, -}; - -#[derive(Default)] -pub struct VariableInAllowedPosition<'a> { - spreads: HashMap, HashSet<&'a str>>, - variable_usages: HashMap, Vec<(&'a str, Pos, MetaTypeName<'a>)>>, - variable_defs: HashMap, Vec<&'a Positioned>>, - current_scope: Option>, -} - -impl<'a> VariableInAllowedPosition<'a> { - fn collect_incorrect_usages( - &self, - from: &Scope<'a>, - var_defs: &[&'a Positioned], - ctx: &mut VisitorContext<'a>, - visited: &mut HashSet>, - ) { - if visited.contains(from) { - return; - } - - visited.insert(*from); - - if let Some(usages) = self.variable_usages.get(from) { - for (var_name, usage_pos, var_type) in usages { - if let Some(def) = var_defs.iter().find(|def| def.node.name.node == *var_name) { - let expected_type = - if def.node.var_type.node.nullable && def.node.default_value.is_some() { - // A nullable type with a default value functions as a non-nullable - format!("{}!", def.node.var_type.node) - } else { - def.node.var_type.node.to_string() - }; - - if !var_type.is_subtype(&MetaTypeName::create(&expected_type)) { - ctx.report_error( - vec![def.pos, *usage_pos], - format!( - "Variable \"{}\" of type \"{}\" used in position expecting type \"{}\"", - var_name, var_type, expected_type - ), - ); - } - } - } - } - - if let Some(spreads) = self.spreads.get(from) { - for spread in spreads { - self.collect_incorrect_usages(&Scope::Fragment(spread), var_defs, ctx, visited); - } - } - } -} - -impl<'a> Visitor<'a> for VariableInAllowedPosition<'a> { - fn exit_document(&mut self, ctx: &mut VisitorContext<'a>, _doc: &'a ExecutableDocument) { - for (op_scope, var_defs) in &self.variable_defs { - self.collect_incorrect_usages(op_scope, var_defs, ctx, &mut HashSet::new()); - } - } - - fn enter_operation_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - name: Option<&'a Name>, - _operation_definition: &'a Positioned, - ) { - self.current_scope = Some(Scope::Operation(name.map(Name::as_str))); - } - - fn enter_fragment_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - name: &'a Name, - _fragment_definition: &'a Positioned, - ) { - self.current_scope = Some(Scope::Fragment(name)); - } - - fn enter_variable_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - variable_definition: &'a Positioned, - ) { - if let Some(ref scope) = self.current_scope { - self.variable_defs - .entry(*scope) - .or_default() - .push(variable_definition); - } - } - - fn enter_fragment_spread( - &mut self, - _ctx: &mut VisitorContext<'a>, - fragment_spread: &'a Positioned, - ) { - if let Some(ref scope) = self.current_scope { - self.spreads - .entry(*scope) - .or_default() - .insert(&fragment_spread.node.fragment_name.node); - } - } - - fn enter_input_value( - &mut self, - _ctx: &mut VisitorContext<'a>, - pos: Pos, - expected_type: &Option>, - value: &'a Value, - ) { - if let Value::Variable(name) = value { - if let Some(expected_type) = expected_type { - if let Some(scope) = &self.current_scope { - self.variable_usages.entry(*scope).or_default().push(( - name, - pos, - *expected_type, - )); - } - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub fn factory<'a>() -> VariableInAllowedPosition<'a> { - VariableInAllowedPosition::default() - } - - #[test] - fn boolean_into_boolean() { - expect_passes_rule!( - factory, - r#" - query Query($booleanArg: Boolean) - { - complicatedArgs { - booleanArgField(booleanArg: $booleanArg) - } - } - "#, - ); - } - - #[test] - fn boolean_into_boolean_within_fragment() { - expect_passes_rule!( - factory, - r#" - fragment booleanArgFrag on ComplicatedArgs { - booleanArgField(booleanArg: $booleanArg) - } - query Query($booleanArg: Boolean) - { - complicatedArgs { - ...booleanArgFrag - } - } - "#, - ); - - expect_passes_rule!( - factory, - r#" - query Query($booleanArg: Boolean) - { - complicatedArgs { - ...booleanArgFrag - } - } - fragment booleanArgFrag on ComplicatedArgs { - booleanArgField(booleanArg: $booleanArg) - } - "#, - ); - } - - #[test] - fn non_null_boolean_into_boolean() { - expect_passes_rule!( - factory, - r#" - query Query($nonNullBooleanArg: Boolean!) - { - complicatedArgs { - booleanArgField(booleanArg: $nonNullBooleanArg) - } - } - "#, - ); - } - - #[test] - fn non_null_boolean_into_boolean_within_fragment() { - expect_passes_rule!( - factory, - r#" - fragment booleanArgFrag on ComplicatedArgs { - booleanArgField(booleanArg: $nonNullBooleanArg) - } - query Query($nonNullBooleanArg: Boolean!) - { - complicatedArgs { - ...booleanArgFrag - } - } - "#, - ); - } - - #[test] - fn int_into_non_null_int_with_default() { - expect_passes_rule!( - factory, - r#" - query Query($intArg: Int = 1) - { - complicatedArgs { - nonNullIntArgField(nonNullIntArg: $intArg) - } - } - "#, - ); - } - - #[test] - fn string_list_into_string_list() { - expect_passes_rule!( - factory, - r#" - query Query($stringListVar: [String]) - { - complicatedArgs { - stringListArgField(stringListArg: $stringListVar) - } - } - "#, - ); - } - - #[test] - fn non_null_string_list_into_string_list() { - expect_passes_rule!( - factory, - r#" - query Query($stringListVar: [String!]) - { - complicatedArgs { - stringListArgField(stringListArg: $stringListVar) - } - } - "#, - ); - } - - #[test] - fn string_into_string_list_in_item_position() { - expect_passes_rule!( - factory, - r#" - query Query($stringVar: String) - { - complicatedArgs { - stringListArgField(stringListArg: [$stringVar]) - } - } - "#, - ); - } - - #[test] - fn non_null_string_into_string_list_in_item_position() { - expect_passes_rule!( - factory, - r#" - query Query($stringVar: String!) - { - complicatedArgs { - stringListArgField(stringListArg: [$stringVar]) - } - } - "#, - ); - } - - #[test] - fn complex_input_into_complex_input() { - expect_passes_rule!( - factory, - r#" - query Query($complexVar: ComplexInput) - { - complicatedArgs { - complexArgField(complexArg: $complexVar) - } - } - "#, - ); - } - - #[test] - fn complex_input_into_complex_input_in_field_position() { - expect_passes_rule!( - factory, - r#" - query Query($boolVar: Boolean = false) - { - complicatedArgs { - complexArgField(complexArg: {requiredArg: $boolVar}) - } - } - "#, - ); - } - - #[test] - fn non_null_boolean_into_non_null_boolean_in_directive() { - expect_passes_rule!( - factory, - r#" - query Query($boolVar: Boolean!) - { - dog @include(if: $boolVar) - } - "#, - ); - } - - #[test] - fn boolean_in_non_null_in_directive_with_default() { - expect_passes_rule!( - factory, - r#" - query Query($boolVar: Boolean = false) - { - dog @include(if: $boolVar) - } - "#, - ); - } - - #[test] - fn int_into_non_null_int() { - expect_fails_rule!( - factory, - r#" - query Query($intArg: Int) { - complicatedArgs { - nonNullIntArgField(nonNullIntArg: $intArg) - } - } - "#, - ); - } - - #[test] - fn int_into_non_null_int_within_fragment() { - expect_fails_rule!( - factory, - r#" - fragment nonNullIntArgFieldFrag on ComplicatedArgs { - nonNullIntArgField(nonNullIntArg: $intArg) - } - query Query($intArg: Int) { - complicatedArgs { - ...nonNullIntArgFieldFrag - } - } - "#, - ); - } - - #[test] - fn int_into_non_null_int_within_nested_fragment() { - expect_fails_rule!( - factory, - r#" - fragment outerFrag on ComplicatedArgs { - ...nonNullIntArgFieldFrag - } - fragment nonNullIntArgFieldFrag on ComplicatedArgs { - nonNullIntArgField(nonNullIntArg: $intArg) - } - query Query($intArg: Int) { - complicatedArgs { - ...outerFrag - } - } - "#, - ); - } - - #[test] - fn string_over_boolean() { - expect_fails_rule!( - factory, - r#" - query Query($stringVar: String) { - complicatedArgs { - booleanArgField(booleanArg: $stringVar) - } - } - "#, - ); - } - - #[test] - fn string_into_string_list() { - expect_fails_rule!( - factory, - r#" - query Query($stringVar: String) { - complicatedArgs { - stringListArgField(stringListArg: $stringVar) - } - } - "#, - ); - } - - #[test] - fn boolean_into_non_null_boolean_in_directive() { - expect_fails_rule!( - factory, - r#" - query Query($boolVar: Boolean) { - dog @include(if: $boolVar) - } - "#, - ); - } - - #[test] - fn string_into_non_null_boolean_in_directive() { - expect_fails_rule!( - factory, - r#" - query Query($stringVar: String) { - dog @include(if: $stringVar) - } - "#, - ); - } -} diff --git a/src/validation/suggestion.rs b/src/validation/suggestion.rs deleted file mode 100644 index c695f56a1..000000000 --- a/src/validation/suggestion.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::{collections::HashMap, fmt::Write}; - -fn levenshtein_distance(s1: &str, s2: &str) -> usize { - let mut column: Vec<_> = (0..=s1.len()).collect(); - for (x, rx) in s2.bytes().enumerate() { - column[0] = x + 1; - let mut lastdiag = x; - for (y, ry) in s1.bytes().enumerate() { - let olddiag = column[y + 1]; - if rx != ry { - lastdiag += 1; - } - column[y + 1] = (column[y + 1] + 1).min((column[y] + 1).min(lastdiag)); - lastdiag = olddiag; - } - } - column[s1.len()] -} - -pub fn make_suggestion(prefix: &str, options: I, input: &str) -> Option -where - I: IntoIterator, - A: AsRef, -{ - let mut selected = Vec::new(); - let mut distances = HashMap::new(); - - for opt in options { - let opt = opt.as_ref().to_string(); - let distance = levenshtein_distance(input, &opt); - let threshold = (input.len() / 2).max((opt.len() / 2).max(1)); - if distance < threshold { - selected.push(opt.clone()); - distances.insert(opt, distance); - } - } - - if selected.is_empty() { - return None; - } - selected.sort_by(|a, b| distances[a].cmp(&distances[b])); - - let mut suggestion = - String::with_capacity(prefix.len() + selected.iter().map(|s| s.len() + 5).sum::()); - suggestion.push_str(prefix); - suggestion.push(' '); - - for (i, s) in selected.iter().enumerate() { - if i != 0 { - suggestion.push_str(", "); - } - write!(suggestion, "\"{}\"", s).unwrap(); - } - - suggestion.push('?'); - - Some(suggestion) -} diff --git a/src/validation/test_harness.rs b/src/validation/test_harness.rs deleted file mode 100644 index 2f5a2484b..000000000 --- a/src/validation/test_harness.rs +++ /dev/null @@ -1,436 +0,0 @@ -#![allow(unused_variables)] -#![allow(clippy::diverging_sub_expression)] -#![allow(dead_code)] -#![allow(unreachable_code)] - -use std::sync::OnceLock; - -use crate::{ - futures_util::stream::Stream, - parser::types::ExecutableDocument, - validation::visitor::{RuleError, Visitor, visit}, - *, -}; - -#[derive(InputObject)] -#[graphql(internal)] -struct TestInput { - id: i32, - name: String, -} - -impl Default for TestInput { - fn default() -> Self { - Self { - id: 423, - name: "foo".to_string(), - } - } -} - -#[derive(Enum, Eq, PartialEq, Copy, Clone)] -#[graphql(internal)] -enum DogCommand { - Sit, - Heel, - Down, -} - -struct Dog; - -#[Object(internal)] -impl Dog { - async fn name(&self, surname: Option) -> Option { - unimplemented!() - } - - async fn nickname(&self) -> Option { - unimplemented!() - } - - async fn bark_volume(&self) -> Option { - unimplemented!() - } - - async fn barks(&self) -> Option { - unimplemented!() - } - - async fn does_know_command(&self, dog_command: Option) -> Option { - unimplemented!() - } - - async fn is_housetrained( - &self, - #[graphql(default = true)] at_other_homes: bool, - ) -> Option { - unimplemented!() - } - - async fn is_at_location(&self, x: Option, y: Option) -> Option { - unimplemented!() - } -} - -#[derive(Enum, Copy, Clone, Eq, PartialEq)] -#[graphql(internal)] -enum FurColor { - Brown, - Black, - Tan, - Spotted, -} - -struct Cat; - -#[Object(internal)] -impl Cat { - async fn name(&self, surname: Option) -> Option { - unimplemented!() - } - - async fn nickname(&self) -> Option { - unimplemented!() - } - - async fn meows(&self) -> Option { - unimplemented!() - } - - async fn meow_volume(&self) -> Option { - unimplemented!() - } - - async fn fur_color(&self) -> Option { - unimplemented!() - } -} - -#[derive(Union)] -#[graphql(internal)] -enum CatOrDog { - Cat(Cat), - Dog(Dog), -} - -struct Human; - -#[Object(internal)] -impl Human { - async fn name(&self, surname: Option) -> Option { - unimplemented!() - } - - async fn pets(&self) -> Option>> { - unimplemented!() - } - - async fn relatives(&self) -> Option> { - unimplemented!() - } - - async fn iq(&self) -> Option { - unimplemented!() - } -} - -struct Alien; - -#[Object(internal)] -impl Alien { - async fn name(&self, surname: Option) -> Option { - unimplemented!() - } - - async fn iq(&self) -> Option { - unimplemented!() - } - - async fn num_eyes(&self) -> Option { - unimplemented!() - } -} - -#[derive(Union)] -#[graphql(internal)] -enum DogOrHuman { - Dog(Dog), - Human(Human), -} - -#[derive(Union)] -#[graphql(internal)] -enum HumanOrAlien { - Human(Human), - Alien(Alien), -} - -#[derive(Interface)] -#[graphql( - internal, - field( - name = "name", - ty = "Option", - arg(name = "surname", ty = "Option") - ) -)] -enum Being { - Dog(Dog), - Cat(Cat), - Human(Human), - Alien(Alien), -} - -#[derive(Interface)] -#[graphql( - internal, - field( - name = "name", - ty = "Option", - arg(name = "surname", ty = "Option") - ) -)] -enum Pet { - Dog(Dog), - Cat(Cat), -} - -#[derive(Interface)] -#[graphql( - internal, - field( - name = "name", - ty = "Option", - arg(name = "surname", ty = "Option") - ) -)] -enum Canine { - Dog(Dog), -} - -#[derive(Interface)] -#[graphql(internal, field(name = "iq", ty = "Option"))] -enum Intelligent { - Human(Human), - Alien(Alien), -} - -#[derive(InputObject)] -#[graphql(internal)] -struct ComplexInput { - required_field: bool, - int_field: Option, - string_field: Option, - boolean_field: Option, - string_list_field: Option>>, -} - -struct ComplicatedArgs; - -#[Object(internal)] -impl ComplicatedArgs { - async fn int_arg_field(&self, int_arg: Option) -> Option { - unimplemented!() - } - - async fn non_null_int_arg_field(&self, non_null_int_arg: i32) -> Option { - unimplemented!() - } - - async fn string_arg_field(&self, string_arg: Option) -> Option { - unimplemented!() - } - - async fn boolean_arg_field(&self, boolean_arg: Option) -> Option { - unimplemented!() - } - - async fn enum_arg_field(&self, enum_arg: Option) -> Option { - unimplemented!() - } - - async fn float_arg_field(&self, float_arg: Option) -> Option { - unimplemented!() - } - - async fn id_arg_field(&self, id_arg: Option) -> Option { - unimplemented!() - } - - async fn string_list_arg_field( - &self, - string_list_arg: Option>>, - ) -> Option { - unimplemented!() - } - - async fn complex_arg_field(&self, complex_arg: Option) -> Option { - unimplemented!() - } - - async fn multiple_reqs(&self, req1: i32, req2: i32) -> Option { - unimplemented!() - } - - async fn multiple_opts( - &self, - #[graphql(default)] opt1: i32, - #[graphql(default)] opt2: i32, - ) -> Option { - unimplemented!() - } - - async fn multiple_opt_and_req( - &self, - req1: i32, - req2: i32, - #[graphql(default)] opt1: i32, - #[graphql(default)] opt2: i32, - ) -> Option { - unimplemented!() - } -} - -#[derive(OneofObject)] -#[graphql(internal)] -enum OneofArg { - A(i32), - B(String), -} - -pub struct Query; - -#[Object(internal)] -impl Query { - async fn human(&self, id: Option) -> Option { - unimplemented!() - } - - async fn alien(&self) -> Option { - unimplemented!() - } - - async fn dog(&self) -> Option { - unimplemented!() - } - - async fn cat(&self) -> Option { - unimplemented!() - } - - async fn pet(&self) -> Option { - unimplemented!() - } - - async fn being(&self) -> Option { - unimplemented!() - } - - async fn intelligent(&self) -> Option { - unimplemented!() - } - - async fn cat_or_dog(&self) -> Option { - unimplemented!() - } - - async fn dog_or_human(&self) -> Option { - unimplemented!() - } - - async fn human_or_alien(&self) -> Option { - unimplemented!() - } - - async fn complicated_args(&self) -> Option { - unimplemented!() - } - - async fn oneof_arg(&self, arg: OneofArg) -> String { - unimplemented!() - } - - async fn oneof_opt(&self, arg: Option) -> String { - unimplemented!() - } -} - -pub struct Mutation; - -#[Object(internal)] -impl Mutation { - async fn test_input(&self, #[graphql(default)] input: TestInput) -> i32 { - unimplemented!() - } -} - -pub struct Subscription; - -#[Subscription(internal)] -impl Subscription { - async fn values(&self) -> impl Stream { - futures_util::stream::once(async move { 10 }) - } -} - -static TEST_HARNESS: OnceLock> = OnceLock::new(); - -pub(crate) fn validate<'a, V, F>( - doc: &'a ExecutableDocument, - factory: F, -) -> Result<(), Vec> -where - V: Visitor<'a> + 'a, - F: Fn() -> V, -{ - let schema = TEST_HARNESS.get_or_init(|| Schema::new(Query, Mutation, Subscription)); - let registry = &schema.0.env.registry; - let mut ctx = VisitorContext::new(registry, doc, None); - let mut visitor = factory(); - visit(&mut visitor, &mut ctx, doc); - if ctx.errors.is_empty() { - Ok(()) - } else { - Err(ctx.errors) - } -} - -pub(crate) fn expect_passes_rule_<'a, V, F>(doc: &'a ExecutableDocument, factory: F) -where - V: Visitor<'a> + 'a, - F: Fn() -> V, -{ - if let Err(errors) = validate(doc, factory) { - for err in errors { - if let Some(position) = err.locations.first() { - print!("[{}:{}] ", position.line, position.column); - } - println!("{}", err.message); - } - panic!("Expected rule to pass, but errors found"); - } -} - -macro_rules! expect_passes_rule { - ($factory:expr, $query_source:literal $(,)?) => { - let doc = crate::parser::parse_query($query_source).expect("Parse error"); - crate::validation::test_harness::expect_passes_rule_(&doc, $factory); - }; -} - -pub(crate) fn expect_fails_rule_<'a, V, F>(doc: &'a ExecutableDocument, factory: F) -where - V: Visitor<'a> + 'a, - F: Fn() -> V, -{ - if validate(doc, factory).is_ok() { - panic!("Expected rule to fail, but no errors were found"); - } -} - -macro_rules! expect_fails_rule { - ($factory:expr, $query_source:literal $(,)?) => { - let doc = crate::parser::parse_query($query_source).expect("Parse error"); - crate::validation::test_harness::expect_fails_rule_(&doc, $factory); - }; -} diff --git a/src/validation/utils.rs b/src/validation/utils.rs deleted file mode 100644 index ec3b60422..000000000 --- a/src/validation/utils.rs +++ /dev/null @@ -1,197 +0,0 @@ -use std::collections::HashSet; - -use async_graphql_value::{ConstValue, Value}; - -use crate::{QueryPathSegment, context::QueryPathNode, registry}; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum Scope<'a> { - Operation(Option<&'a str>), - Fragment(&'a str), -} - -fn valid_error(path_node: &QueryPathNode, msg: String) -> String { - format!("\"{}\", {}", path_node, msg) -} - -pub fn referenced_variables(value: &Value) -> Vec<&str> { - let mut vars = Vec::new(); - referenced_variables_to_vec(value, &mut vars); - vars -} - -fn referenced_variables_to_vec<'a>(value: &'a Value, vars: &mut Vec<&'a str>) { - match value { - Value::Variable(name) => { - vars.push(name); - } - Value::List(values) => values - .iter() - .for_each(|value| referenced_variables_to_vec(value, vars)), - Value::Object(obj) => obj - .values() - .for_each(|value| referenced_variables_to_vec(value, vars)), - _ => {} - } -} - -pub fn is_valid_input_value( - registry: ®istry::Registry, - type_name: &str, - value: &ConstValue, - path_node: QueryPathNode, -) -> Option { - match registry::MetaTypeName::create(type_name) { - registry::MetaTypeName::NonNull(type_name) => match value { - ConstValue::Null => Some(valid_error( - &path_node, - format!("expected type \"{}\"", type_name), - )), - _ => is_valid_input_value(registry, type_name, value, path_node), - }, - registry::MetaTypeName::List(type_name) => match value { - ConstValue::List(elems) => elems.iter().enumerate().find_map(|(idx, elem)| { - is_valid_input_value( - registry, - type_name, - elem, - QueryPathNode { - parent: Some(&path_node), - segment: QueryPathSegment::Index(idx), - }, - ) - }), - ConstValue::Null => None, - _ => is_valid_input_value(registry, type_name, value, path_node), - }, - registry::MetaTypeName::Named(type_name) => { - if let ConstValue::Null = value { - return None; - } - - match registry - .types - .get(type_name) - .unwrap_or_else(|| panic!("Type `{}` not defined", type_name)) - { - registry::MetaType::Scalar { - is_valid: Some(is_valid_fn), - .. - } => { - if (is_valid_fn)(&value) { - None - } else { - Some(valid_error( - &path_node, - format!("expected type \"{}\"", type_name), - )) - } - } - registry::MetaType::Scalar { is_valid: None, .. } => None, - registry::MetaType::Enum { - enum_values, - name: enum_name, - .. - } => match value { - ConstValue::Enum(name) => { - if !enum_values.contains_key(name.as_str()) { - Some(valid_error( - &path_node, - format!( - "enumeration type \"{}\" does not contain the value \"{}\"", - enum_name, name - ), - )) - } else { - None - } - } - ConstValue::String(name) => { - if !enum_values.contains_key(name.as_str()) { - Some(valid_error( - &path_node, - format!( - "enumeration type \"{}\" does not contain the value \"{}\"", - enum_name, name - ), - )) - } else { - None - } - } - _ => Some(valid_error( - &path_node, - format!("expected type \"{}\"", type_name), - )), - }, - registry::MetaType::InputObject { - input_fields, - name: object_name, - oneof, - .. - } => match value { - ConstValue::Object(values) => { - if *oneof { - if values.len() != 1 { - return Some(valid_error( - &path_node, - "Oneof input objects requires have exactly one field" - .to_string(), - )); - } - - if let ConstValue::Null = values[0] { - return Some(valid_error( - &path_node, - "Oneof Input Objects require that exactly one field must be supplied and that field must not be null" - .to_string(), - )); - } - } - - let mut input_names = - values.keys().map(AsRef::as_ref).collect::>(); - - for field in input_fields.values() { - input_names.remove(&*field.name); - if let Some(value) = values.get(&*field.name) { - if let Some(reason) = is_valid_input_value( - registry, - &field.ty, - value, - QueryPathNode { - parent: Some(&path_node), - segment: QueryPathSegment::Name(&field.name), - }, - ) { - return Some(reason); - } - } else if registry::MetaTypeName::create(&field.ty).is_non_null() - && field.default_value.is_none() - { - return Some(valid_error( - &path_node, - format!( - r#"field "{}" of type "{}" is required but not provided"#, - field.name, field.ty, - ), - )); - } - } - - if let Some(name) = input_names.iter().next() { - return Some(valid_error( - &path_node, - format!("unknown field \"{}\" of type \"{}\"", name, object_name), - )); - } - - None - } - _ => None, - }, - _ => None, - } - } - } -} diff --git a/src/validation/visitor.rs b/src/validation/visitor.rs deleted file mode 100644 index 50dd4f3be..000000000 --- a/src/validation/visitor.rs +++ /dev/null @@ -1,852 +0,0 @@ -use std::{ - collections::HashMap, - fmt::{self, Display, Formatter}, -}; - -use async_graphql_value::Value; - -use crate::{ - InputType, Name, Pos, Positioned, ServerError, ServerResult, Variables, - parser::types::{ - Directive, ExecutableDocument, Field, FragmentDefinition, FragmentSpread, InlineFragment, - OperationDefinition, OperationType, Selection, SelectionSet, TypeCondition, - VariableDefinition, - }, - registry::{self, MetaType, MetaTypeName}, -}; - -#[doc(hidden)] -pub struct VisitorContext<'a> { - pub(crate) registry: &'a registry::Registry, - pub(crate) variables: Option<&'a Variables>, - pub(crate) errors: Vec, - type_stack: Vec>, - input_type: Vec>>, - fragments: &'a HashMap>, -} - -impl<'a> VisitorContext<'a> { - pub(crate) fn new( - registry: &'a registry::Registry, - doc: &'a ExecutableDocument, - variables: Option<&'a Variables>, - ) -> Self { - Self { - registry, - variables, - errors: Default::default(), - type_stack: Default::default(), - input_type: Default::default(), - fragments: &doc.fragments, - } - } - - pub(crate) fn report_error>(&mut self, locations: Vec, msg: T) { - self.errors.push(RuleError::new(locations, msg)); - } - - pub(crate) fn append_errors(&mut self, errors: Vec) { - self.errors.extend(errors); - } - - pub(crate) fn with_type)>( - &mut self, - ty: Option<&'a registry::MetaType>, - mut f: F, - ) { - self.type_stack.push(ty); - f(self); - self.type_stack.pop(); - } - - pub(crate) fn with_input_type)>( - &mut self, - ty: Option>, - mut f: F, - ) { - self.input_type.push(ty); - f(self); - self.input_type.pop(); - } - - pub(crate) fn parent_type(&self) -> Option<&'a registry::MetaType> { - if self.type_stack.len() >= 2 { - self.type_stack - .get(self.type_stack.len() - 2) - .copied() - .flatten() - } else { - None - } - } - - pub(crate) fn current_type(&self) -> Option<&'a registry::MetaType> { - self.type_stack.last().copied().flatten() - } - - pub(crate) fn is_known_fragment(&self, name: &str) -> bool { - self.fragments.contains_key(name) - } - - pub(crate) fn fragment(&self, name: &str) -> Option<&'a Positioned> { - self.fragments.get(name) - } - - #[doc(hidden)] - pub fn param_value( - &self, - variable_definitions: &[Positioned], - field: &Field, - name: &str, - default: Option T>, - ) -> ServerResult { - let value = field.get_argument(name).cloned(); - - if value.is_none() { - if let Some(default) = default { - return Ok(default()); - } - } - - let (pos, value) = match value { - Some(value) => { - let pos = value.pos; - ( - pos, - Some(value.node.into_const_with(|name| { - variable_definitions - .iter() - .find(|def| def.node.name.node == name) - .and_then(|def| { - if let Some(variables) = self.variables { - variables - .get(&def.node.name.node) - .or_else(|| def.node.default_value()) - } else { - None - } - }) - .cloned() - .ok_or_else(|| { - ServerError::new( - format!("Variable {} is not defined.", name), - Some(pos), - ) - }) - })?), - ) - } - None => (Pos::default(), None), - }; - - T::parse(value).map_err(|e| e.into_server_error(pos)) - } -} - -#[derive(Copy, Clone, Eq, PartialEq)] -pub(crate) enum VisitMode { - Normal, - Inline, -} - -pub(crate) trait Visitor<'a> { - fn mode(&self) -> VisitMode { - VisitMode::Normal - } - - fn enter_document(&mut self, _ctx: &mut VisitorContext<'a>, _doc: &'a ExecutableDocument) {} - fn exit_document(&mut self, _ctx: &mut VisitorContext<'a>, _doc: &'a ExecutableDocument) {} - - fn enter_operation_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - _name: Option<&'a Name>, - _operation_definition: &'a Positioned, - ) { - } - fn exit_operation_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - _name: Option<&'a Name>, - _operation_definition: &'a Positioned, - ) { - } - - fn enter_fragment_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - _name: &'a Name, - _fragment_definition: &'a Positioned, - ) { - } - fn exit_fragment_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - _name: &'a Name, - _fragment_definition: &'a Positioned, - ) { - } - - fn enter_variable_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - _variable_definition: &'a Positioned, - ) { - } - fn exit_variable_definition( - &mut self, - _ctx: &mut VisitorContext<'a>, - _variable_definition: &'a Positioned, - ) { - } - - fn enter_directive( - &mut self, - _ctx: &mut VisitorContext<'a>, - _directive: &'a Positioned, - ) { - } - fn exit_directive( - &mut self, - _ctx: &mut VisitorContext<'a>, - _directive: &'a Positioned, - ) { - } - - fn enter_argument( - &mut self, - _ctx: &mut VisitorContext<'a>, - _name: &'a Positioned, - _value: &'a Positioned, - ) { - } - fn exit_argument( - &mut self, - _ctx: &mut VisitorContext<'a>, - _name: &'a Positioned, - _value: &'a Positioned, - ) { - } - - fn enter_selection_set( - &mut self, - _ctx: &mut VisitorContext<'a>, - _selection_set: &'a Positioned, - ) { - } - fn exit_selection_set( - &mut self, - _ctx: &mut VisitorContext<'a>, - _selection_set: &'a Positioned, - ) { - } - - fn enter_selection( - &mut self, - _ctx: &mut VisitorContext<'a>, - _selection: &'a Positioned, - ) { - } - fn exit_selection( - &mut self, - _ctx: &mut VisitorContext<'a>, - _selection: &'a Positioned, - ) { - } - - fn enter_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Positioned) {} - fn exit_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Positioned) {} - - fn enter_fragment_spread( - &mut self, - _ctx: &mut VisitorContext<'a>, - _fragment_spread: &'a Positioned, - ) { - } - fn exit_fragment_spread( - &mut self, - _ctx: &mut VisitorContext<'a>, - _fragment_spread: &'a Positioned, - ) { - } - - fn enter_inline_fragment( - &mut self, - _ctx: &mut VisitorContext<'a>, - _inline_fragment: &'a Positioned, - ) { - } - fn exit_inline_fragment( - &mut self, - _ctx: &mut VisitorContext<'a>, - _inline_fragment: &'a Positioned, - ) { - } - - fn enter_input_value( - &mut self, - _ctx: &mut VisitorContext<'a>, - _pos: Pos, - _expected_type: &Option>, - _value: &'a Value, - ) { - } - fn exit_input_value( - &mut self, - _ctx: &mut VisitorContext<'a>, - _pos: Pos, - _expected_type: &Option>, - _value: &Value, - ) { - } -} - -pub(crate) struct VisitorNil; - -impl VisitorNil { - pub(crate) fn with(self, visitor: V) -> VisitorCons { - VisitorCons(visitor, self) - } -} - -pub(crate) struct VisitorCons(A, B); - -impl VisitorCons { - pub(crate) const fn with(self, visitor: V) -> VisitorCons { - VisitorCons(visitor, self) - } -} - -impl Visitor<'_> for VisitorNil {} - -impl<'a, A, B> Visitor<'a> for VisitorCons -where - A: Visitor<'a> + 'a, - B: Visitor<'a> + 'a, -{ - fn mode(&self) -> VisitMode { - self.0.mode() - } - - fn enter_document(&mut self, ctx: &mut VisitorContext<'a>, doc: &'a ExecutableDocument) { - self.0.enter_document(ctx, doc); - self.1.enter_document(ctx, doc); - } - - fn exit_document(&mut self, ctx: &mut VisitorContext<'a>, doc: &'a ExecutableDocument) { - self.0.exit_document(ctx, doc); - self.1.exit_document(ctx, doc); - } - - fn enter_operation_definition( - &mut self, - ctx: &mut VisitorContext<'a>, - name: Option<&'a Name>, - operation_definition: &'a Positioned, - ) { - self.0 - .enter_operation_definition(ctx, name, operation_definition); - self.1 - .enter_operation_definition(ctx, name, operation_definition); - } - - fn exit_operation_definition( - &mut self, - ctx: &mut VisitorContext<'a>, - name: Option<&'a Name>, - operation_definition: &'a Positioned, - ) { - self.0 - .exit_operation_definition(ctx, name, operation_definition); - self.1 - .exit_operation_definition(ctx, name, operation_definition); - } - - fn enter_fragment_definition( - &mut self, - ctx: &mut VisitorContext<'a>, - name: &'a Name, - fragment_definition: &'a Positioned, - ) { - self.0 - .enter_fragment_definition(ctx, name, fragment_definition); - self.1 - .enter_fragment_definition(ctx, name, fragment_definition); - } - - fn exit_fragment_definition( - &mut self, - ctx: &mut VisitorContext<'a>, - name: &'a Name, - fragment_definition: &'a Positioned, - ) { - self.0 - .exit_fragment_definition(ctx, name, fragment_definition); - self.1 - .exit_fragment_definition(ctx, name, fragment_definition); - } - - fn enter_variable_definition( - &mut self, - ctx: &mut VisitorContext<'a>, - variable_definition: &'a Positioned, - ) { - self.0.enter_variable_definition(ctx, variable_definition); - self.1.enter_variable_definition(ctx, variable_definition); - } - - fn exit_variable_definition( - &mut self, - ctx: &mut VisitorContext<'a>, - variable_definition: &'a Positioned, - ) { - self.0.exit_variable_definition(ctx, variable_definition); - self.1.exit_variable_definition(ctx, variable_definition); - } - - fn enter_directive( - &mut self, - ctx: &mut VisitorContext<'a>, - directive: &'a Positioned, - ) { - self.0.enter_directive(ctx, directive); - self.1.enter_directive(ctx, directive); - } - - fn exit_directive( - &mut self, - ctx: &mut VisitorContext<'a>, - directive: &'a Positioned, - ) { - self.0.exit_directive(ctx, directive); - self.1.exit_directive(ctx, directive); - } - - fn enter_argument( - &mut self, - ctx: &mut VisitorContext<'a>, - name: &'a Positioned, - value: &'a Positioned, - ) { - self.0.enter_argument(ctx, name, value); - self.1.enter_argument(ctx, name, value); - } - - fn exit_argument( - &mut self, - ctx: &mut VisitorContext<'a>, - name: &'a Positioned, - value: &'a Positioned, - ) { - self.0.exit_argument(ctx, name, value); - self.1.exit_argument(ctx, name, value); - } - - fn enter_selection_set( - &mut self, - ctx: &mut VisitorContext<'a>, - selection_set: &'a Positioned, - ) { - self.0.enter_selection_set(ctx, selection_set); - self.1.enter_selection_set(ctx, selection_set); - } - - fn exit_selection_set( - &mut self, - ctx: &mut VisitorContext<'a>, - selection_set: &'a Positioned, - ) { - self.0.exit_selection_set(ctx, selection_set); - self.1.exit_selection_set(ctx, selection_set); - } - - fn enter_selection( - &mut self, - ctx: &mut VisitorContext<'a>, - selection: &'a Positioned, - ) { - self.0.enter_selection(ctx, selection); - self.1.enter_selection(ctx, selection); - } - - fn exit_selection( - &mut self, - ctx: &mut VisitorContext<'a>, - selection: &'a Positioned, - ) { - self.0.exit_selection(ctx, selection); - self.1.exit_selection(ctx, selection); - } - - fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Positioned) { - self.0.enter_field(ctx, field); - self.1.enter_field(ctx, field); - } - - fn exit_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Positioned) { - self.0.exit_field(ctx, field); - self.1.exit_field(ctx, field); - } - - fn enter_fragment_spread( - &mut self, - ctx: &mut VisitorContext<'a>, - fragment_spread: &'a Positioned, - ) { - self.0.enter_fragment_spread(ctx, fragment_spread); - self.1.enter_fragment_spread(ctx, fragment_spread); - } - - fn exit_fragment_spread( - &mut self, - ctx: &mut VisitorContext<'a>, - fragment_spread: &'a Positioned, - ) { - self.0.exit_fragment_spread(ctx, fragment_spread); - self.1.exit_fragment_spread(ctx, fragment_spread); - } - - fn enter_inline_fragment( - &mut self, - ctx: &mut VisitorContext<'a>, - inline_fragment: &'a Positioned, - ) { - self.0.enter_inline_fragment(ctx, inline_fragment); - self.1.enter_inline_fragment(ctx, inline_fragment); - } - - fn exit_inline_fragment( - &mut self, - ctx: &mut VisitorContext<'a>, - inline_fragment: &'a Positioned, - ) { - self.0.exit_inline_fragment(ctx, inline_fragment); - self.1.exit_inline_fragment(ctx, inline_fragment); - } -} - -pub(crate) fn visit<'a, V: Visitor<'a>>( - v: &mut V, - ctx: &mut VisitorContext<'a>, - doc: &'a ExecutableDocument, -) { - v.enter_document(ctx, doc); - - for (name, fragment) in &doc.fragments { - ctx.with_type( - ctx.registry - .types - .get(fragment.node.type_condition.node.on.node.as_str()), - |ctx| visit_fragment_definition(v, ctx, name, fragment), - ) - } - - for (name, operation) in doc.operations.iter() { - visit_operation_definition(v, ctx, name, operation); - } - - v.exit_document(ctx, doc); -} - -fn visit_operation_definition<'a, V: Visitor<'a>>( - v: &mut V, - ctx: &mut VisitorContext<'a>, - name: Option<&'a Name>, - operation: &'a Positioned, -) { - v.enter_operation_definition(ctx, name, operation); - let root_name = match &operation.node.ty { - OperationType::Query => Some(&*ctx.registry.query_type), - OperationType::Mutation => ctx.registry.mutation_type.as_deref(), - OperationType::Subscription => ctx.registry.subscription_type.as_deref(), - }; - if let Some(root_name) = root_name { - ctx.with_type(Some(&ctx.registry.types[root_name]), |ctx| { - visit_variable_definitions(v, ctx, &operation.node.variable_definitions); - visit_directives(v, ctx, &operation.node.directives); - visit_selection_set(v, ctx, &operation.node.selection_set); - }); - } else { - ctx.report_error( - vec![operation.pos], - // The only one with an irregular plural, "query", is always present - format!("Schema is not configured for {}s.", operation.node.ty), - ); - } - v.exit_operation_definition(ctx, name, operation); -} - -fn visit_selection_set<'a, V: Visitor<'a>>( - v: &mut V, - ctx: &mut VisitorContext<'a>, - selection_set: &'a Positioned, -) { - if !selection_set.node.items.is_empty() { - v.enter_selection_set(ctx, selection_set); - for selection in &selection_set.node.items { - visit_selection(v, ctx, selection); - } - v.exit_selection_set(ctx, selection_set); - } -} - -fn visit_selection<'a, V: Visitor<'a>>( - v: &mut V, - ctx: &mut VisitorContext<'a>, - selection: &'a Positioned, -) { - v.enter_selection(ctx, selection); - match &selection.node { - Selection::Field(field) => { - if field.node.name.node != "__typename" { - ctx.with_type( - ctx.current_type() - .and_then(|ty| ty.field_by_name(&field.node.name.node)) - .and_then(|schema_field| { - ctx.registry.concrete_type_by_name(&schema_field.ty) - }), - |ctx| { - visit_field(v, ctx, field); - }, - ); - } else if ctx.current_type().map(|ty| match ty { - MetaType::Object { - is_subscription, .. - } => *is_subscription, - _ => false, - }) == Some(true) - { - ctx.report_error( - vec![field.pos], - "Unknown field \"__typename\" on type \"Subscription\".", - ); - } - } - Selection::FragmentSpread(fragment_spread) => { - visit_fragment_spread(v, ctx, fragment_spread) - } - Selection::InlineFragment(inline_fragment) => { - if let Some(TypeCondition { on: name }) = &inline_fragment - .node - .type_condition - .as_ref() - .map(|c| &c.node) - { - ctx.with_type(ctx.registry.types.get(name.node.as_str()), |ctx| { - visit_inline_fragment(v, ctx, inline_fragment) - }); - } else { - visit_inline_fragment(v, ctx, inline_fragment) - } - } - } - v.exit_selection(ctx, selection); -} - -fn visit_field<'a, V: Visitor<'a>>( - v: &mut V, - ctx: &mut VisitorContext<'a>, - field: &'a Positioned, -) { - v.enter_field(ctx, field); - - for (name, value) in &field.node.arguments { - v.enter_argument(ctx, name, value); - let expected_ty = ctx - .parent_type() - .and_then(|ty| ty.field_by_name(&field.node.name.node)) - .and_then(|schema_field| schema_field.args.get(&*name.node)) - .map(|input_ty| MetaTypeName::create(&input_ty.ty)); - ctx.with_input_type(expected_ty, |ctx| { - visit_input_value(v, ctx, field.pos, expected_ty, &value.node) - }); - v.exit_argument(ctx, name, value); - } - - visit_directives(v, ctx, &field.node.directives); - visit_selection_set(v, ctx, &field.node.selection_set); - v.exit_field(ctx, field); -} - -fn visit_input_value<'a, V: Visitor<'a>>( - v: &mut V, - ctx: &mut VisitorContext<'a>, - pos: Pos, - expected_ty: Option>, - value: &'a Value, -) { - v.enter_input_value(ctx, pos, &expected_ty, value); - - match value { - Value::List(values) => { - if let Some(expected_ty) = expected_ty { - let elem_ty = expected_ty.unwrap_non_null(); - if let MetaTypeName::List(expected_ty) = elem_ty { - values.iter().for_each(|value| { - visit_input_value( - v, - ctx, - pos, - Some(MetaTypeName::create(expected_ty)), - value, - ) - }); - } - } - } - Value::Object(values) => { - if let Some(expected_ty) = expected_ty { - let expected_ty = expected_ty.unwrap_non_null(); - if let MetaTypeName::Named(expected_ty) = expected_ty { - if let Some(MetaType::InputObject { input_fields, .. }) = ctx - .registry - .types - .get(MetaTypeName::concrete_typename(expected_ty)) - { - for (item_key, item_value) in values { - if let Some(input_value) = input_fields.get(item_key.as_str()) { - visit_input_value( - v, - ctx, - pos, - Some(MetaTypeName::create(&input_value.ty)), - item_value, - ); - } - } - } - } - } - } - _ => {} - } - - v.exit_input_value(ctx, pos, &expected_ty, value); -} - -fn visit_variable_definitions<'a, V: Visitor<'a>>( - v: &mut V, - ctx: &mut VisitorContext<'a>, - variable_definitions: &'a [Positioned], -) { - for d in variable_definitions { - v.enter_variable_definition(ctx, d); - v.exit_variable_definition(ctx, d); - } -} - -fn visit_directives<'a, V: Visitor<'a>>( - v: &mut V, - ctx: &mut VisitorContext<'a>, - directives: &'a [Positioned], -) { - for d in directives { - v.enter_directive(ctx, d); - - let schema_directive = ctx.registry.directives.get(d.node.name.node.as_str()); - - for (name, value) in &d.node.arguments { - v.enter_argument(ctx, name, value); - let expected_ty = schema_directive - .and_then(|schema_directive| schema_directive.args.get(&*name.node)) - .map(|input_ty| MetaTypeName::create(&input_ty.ty)); - ctx.with_input_type(expected_ty, |ctx| { - visit_input_value(v, ctx, d.pos, expected_ty, &value.node) - }); - v.exit_argument(ctx, name, value); - } - - v.exit_directive(ctx, d); - } -} - -fn visit_fragment_definition<'a, V: Visitor<'a>>( - v: &mut V, - ctx: &mut VisitorContext<'a>, - name: &'a Name, - fragment: &'a Positioned, -) { - if v.mode() == VisitMode::Normal { - v.enter_fragment_definition(ctx, name, fragment); - visit_directives(v, ctx, &fragment.node.directives); - visit_selection_set(v, ctx, &fragment.node.selection_set); - v.exit_fragment_definition(ctx, name, fragment); - } -} - -fn visit_fragment_spread<'a, V: Visitor<'a>>( - v: &mut V, - ctx: &mut VisitorContext<'a>, - fragment_spread: &'a Positioned, -) { - v.enter_fragment_spread(ctx, fragment_spread); - visit_directives(v, ctx, &fragment_spread.node.directives); - if v.mode() == VisitMode::Inline { - if let Some(fragment) = ctx - .fragments - .get(fragment_spread.node.fragment_name.node.as_str()) - { - visit_selection_set(v, ctx, &fragment.node.selection_set); - } - } - v.exit_fragment_spread(ctx, fragment_spread); -} - -fn visit_inline_fragment<'a, V: Visitor<'a>>( - v: &mut V, - ctx: &mut VisitorContext<'a>, - inline_fragment: &'a Positioned, -) { - v.enter_inline_fragment(ctx, inline_fragment); - visit_directives(v, ctx, &inline_fragment.node.directives); - visit_selection_set(v, ctx, &inline_fragment.node.selection_set); - v.exit_inline_fragment(ctx, inline_fragment); -} - -#[derive(Debug, PartialEq)] -pub(crate) struct RuleError { - pub(crate) locations: Vec, - pub(crate) message: String, -} - -impl RuleError { - pub(crate) fn new(locations: Vec, msg: impl Into) -> Self { - Self { - locations, - message: msg.into(), - } - } -} - -impl Display for RuleError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - for (idx, loc) in self.locations.iter().enumerate() { - if idx == 0 { - write!(f, "[")?; - } else { - write!(f, ", ")?; - } - - write!(f, "{}:{}", loc.line, loc.column)?; - - if idx == self.locations.len() - 1 { - write!(f, "] ")?; - } - } - - write!(f, "{}", self.message)?; - Ok(()) - } -} - -impl From for ServerError { - fn from(e: RuleError) -> Self { - Self { - message: e.message, - source: None, - locations: e.locations, - path: Vec::new(), - extensions: None, - } - } -} diff --git a/src/validation/visitors/cache_control.rs b/src/validation/visitors/cache_control.rs deleted file mode 100644 index 84ffe519f..000000000 --- a/src/validation/visitors/cache_control.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::{ - CacheControl, Positioned, - parser::types::{Field, SelectionSet}, - registry::MetaType, - validation::visitor::{VisitMode, Visitor, VisitorContext}, -}; - -pub struct CacheControlCalculate<'a> { - pub cache_control: &'a mut CacheControl, -} - -impl Visitor<'_> for CacheControlCalculate<'_> { - fn mode(&self) -> VisitMode { - VisitMode::Inline - } - - fn enter_selection_set( - &mut self, - ctx: &mut VisitorContext<'_>, - _selection_set: &Positioned, - ) { - if let Some(MetaType::Object { cache_control, .. }) = ctx.current_type() { - *self.cache_control = self.cache_control.merge(cache_control); - } - } - - fn enter_field(&mut self, ctx: &mut VisitorContext<'_>, field: &Positioned) { - if let Some(registry_field) = ctx - .parent_type() - .and_then(|parent| parent.field_by_name(&field.node.name.node)) - { - *self.cache_control = self.cache_control.merge(®istry_field.cache_control); - } - } -} diff --git a/src/validation/visitors/complexity.rs b/src/validation/visitors/complexity.rs deleted file mode 100644 index 808794614..000000000 --- a/src/validation/visitors/complexity.rs +++ /dev/null @@ -1,465 +0,0 @@ -use async_graphql_parser::types::{ExecutableDocument, OperationDefinition, VariableDefinition}; -use async_graphql_value::Name; - -use crate::{ - Positioned, - parser::types::Field, - registry::{MetaType, MetaTypeName}, - validation::visitor::{VisitMode, Visitor, VisitorContext}, -}; - -pub struct ComplexityCalculate<'ctx, 'a> { - pub complexity: &'a mut usize, - pub complexity_stack: Vec, - pub variable_definition: Option<&'ctx [Positioned]>, -} - -impl<'a> ComplexityCalculate<'_, 'a> { - pub fn new(complexity: &'a mut usize) -> Self { - Self { - complexity, - complexity_stack: Default::default(), - variable_definition: None, - } - } -} - -impl<'ctx> Visitor<'ctx> for ComplexityCalculate<'ctx, '_> { - fn mode(&self) -> VisitMode { - VisitMode::Inline - } - - fn enter_document(&mut self, _ctx: &mut VisitorContext<'ctx>, _doc: &'ctx ExecutableDocument) { - self.complexity_stack.push(0); - } - - fn exit_document(&mut self, _ctx: &mut VisitorContext<'ctx>, _doc: &'ctx ExecutableDocument) { - *self.complexity = self.complexity_stack.pop().unwrap(); - } - - fn enter_operation_definition( - &mut self, - _ctx: &mut VisitorContext<'ctx>, - _name: Option<&'ctx Name>, - operation_definition: &'ctx Positioned, - ) { - self.variable_definition = Some(&operation_definition.node.variable_definitions); - } - - fn enter_field(&mut self, _ctx: &mut VisitorContext<'_>, _field: &Positioned) { - self.complexity_stack.push(0); - } - - fn exit_field(&mut self, ctx: &mut VisitorContext<'ctx>, field: &'ctx Positioned) { - let children_complex = self.complexity_stack.pop().unwrap(); - - if let Some(MetaType::Object { fields, .. }) = ctx.parent_type() { - if let Some(meta_field) = fields.get(MetaTypeName::concrete_typename( - field.node.name.node.as_str(), - )) { - if let Some(f) = &meta_field.compute_complexity { - match f( - ctx, - self.variable_definition.unwrap_or(&[]), - &field.node, - children_complex, - ) { - Ok(n) => { - *self.complexity_stack.last_mut().unwrap() += n; - } - Err(err) => ctx.report_error(vec![field.pos], err.to_string()), - } - return; - } - } - } - - *self.complexity_stack.last_mut().unwrap() += 1 + children_complex; - } -} - -#[cfg(test)] -#[allow(clippy::diverging_sub_expression)] -mod tests { - use async_graphql_derive::SimpleObject; - use futures_util::stream::BoxStream; - - use super::*; - use crate::{ - EmptyMutation, Object, Schema, Subscription, parser::parse_query, validation::visit, - }; - - struct Query; - - #[derive(SimpleObject)] - #[graphql(internal)] - struct MySimpleObj { - #[graphql(complexity = 0)] - a: i32, - #[graphql(complexity = 0)] - b: String, - #[graphql(complexity = 5)] - c: i32, - } - - #[derive(Copy, Clone)] - struct MyObj; - - #[Object(internal)] - #[allow(unreachable_code)] - impl MyObj { - async fn a(&self) -> i32 { - todo!() - } - - async fn b(&self) -> i32 { - todo!() - } - - async fn c(&self) -> MyObj { - todo!() - } - } - - #[Object(internal)] - #[allow(unreachable_code)] - impl Query { - async fn value(&self) -> i32 { - todo!() - } - - async fn simple_obj(&self) -> MySimpleObj { - todo!() - } - - #[graphql(complexity = "count * child_complexity + 2")] - #[allow(unused_variables)] - async fn simple_objs( - &self, - #[graphql(default_with = "5")] count: usize, - ) -> Vec { - todo!() - } - - async fn obj(&self) -> MyObj { - todo!() - } - - #[graphql(complexity = "5 * child_complexity")] - async fn obj2(&self) -> MyObj { - todo!() - } - - #[graphql(complexity = "count * child_complexity")] - #[allow(unused_variables)] - async fn objs(&self, #[graphql(default_with = "5")] count: usize) -> Vec { - todo!() - } - - #[graphql(complexity = 3)] - async fn d(&self) -> MyObj { - todo!() - } - } - - struct Subscription; - - #[Subscription(internal)] - impl Subscription { - async fn value(&self) -> BoxStream<'static, i32> { - todo!() - } - - async fn obj(&self) -> BoxStream<'static, MyObj> { - todo!() - } - - #[graphql(complexity = "count * child_complexity")] - #[allow(unused_variables)] - async fn objs( - &self, - #[graphql(default_with = "5")] count: usize, - ) -> BoxStream<'static, Vec> { - todo!() - } - - #[graphql(complexity = 3)] - async fn d(&self) -> BoxStream<'static, MyObj> { - todo!() - } - } - - #[track_caller] - fn check_complexity(query: &str, expect_complexity: usize) { - let registry = - Schema::::create_registry(Default::default()); - let doc = parse_query(query).unwrap(); - let mut ctx = VisitorContext::new(®istry, &doc, None); - let mut complexity = 0; - let mut complexity_calculate = ComplexityCalculate::new(&mut complexity); - visit(&mut complexity_calculate, &mut ctx, &doc); - assert_eq!(complexity, expect_complexity); - } - - #[test] - fn simple_object() { - check_complexity( - r#"{ - simpleObj { a b } - }"#, - 1, - ); - - check_complexity( - r#"{ - simpleObj { a b c } - }"#, - 6, - ); - - check_complexity( - r#"{ - simpleObjs(count: 7) { a b c } - }"#, - 7 * 5 + 2, - ); - } - - #[test] - fn complex_object() { - check_complexity( - r#" - { - value #1 - }"#, - 1, - ); - - check_complexity( - r#" - { - value #1 - d #3 - }"#, - 4, - ); - - check_complexity( - r#" - { - value obj { #2 - a b #2 - } - }"#, - 4, - ); - - check_complexity( - r#" - { - value obj { #2 - a b obj { #3 - a b obj { #3 - a #1 - } - } - } - }"#, - 9, - ); - - check_complexity( - r#" - fragment A on MyObj { - a b ... A2 #2 - } - - fragment A2 on MyObj { - obj { # 1 - a # 1 - } - } - - query { - obj { # 1 - ... A - } - }"#, - 5, - ); - - check_complexity( - r#" - { - obj { # 1 - ... on MyObj { - a b #2 - ... on MyObj { - obj { #1 - a #1 - } - } - } - } - }"#, - 5, - ); - - check_complexity( - r#" - { - objs(count: 10) { - a b - } - }"#, - 20, - ); - - check_complexity( - r#" - { - objs { - a b - } - }"#, - 10, - ); - - check_complexity( - r#" - fragment A on MyObj { - a b - } - - query { - objs(count: 10) { - ... A - } - }"#, - 20, - ); - } - - #[test] - fn complex_subscription() { - check_complexity( - r#" - subscription { - value #1 - }"#, - 1, - ); - - check_complexity( - r#" - subscription { - value #1 - d #3 - }"#, - 4, - ); - - check_complexity( - r#" - subscription { - value obj { #2 - a b #2 - } - }"#, - 4, - ); - - check_complexity( - r#" - subscription { - value obj { #2 - a b obj { #3 - a b obj { #3 - a #1 - } - } - } - }"#, - 9, - ); - - check_complexity( - r#" - fragment A on MyObj { - a b ... A2 #2 - } - - fragment A2 on MyObj { - obj { # 1 - a # 1 - } - } - - subscription query { - obj { # 1 - ... A - } - }"#, - 5, - ); - - check_complexity( - r#" - subscription { - obj { # 1 - ... on MyObj { - a b #2 - ... on MyObj { - obj { #1 - a #1 - } - } - } - } - }"#, - 5, - ); - - check_complexity( - r#" - subscription { - objs(count: 10) { - a b - } - }"#, - 20, - ); - - check_complexity( - r#" - subscription { - objs { - a b - } - }"#, - 10, - ); - - check_complexity( - r#" - fragment A on MyObj { - a b - } - - subscription query { - objs(count: 10) { - ... A - } - }"#, - 20, - ); - - check_complexity( - r#" - query { - obj2 { a b } - }"#, - 10, - ); - } -} diff --git a/src/validation/visitors/depth.rs b/src/validation/visitors/depth.rs deleted file mode 100644 index a698538a1..000000000 --- a/src/validation/visitors/depth.rs +++ /dev/null @@ -1,158 +0,0 @@ -use async_graphql_parser::types::Field; - -use crate::{ - Positioned, - validation::visitor::{VisitMode, Visitor, VisitorContext}, -}; - -pub struct DepthCalculate<'a> { - max_depth: &'a mut usize, - current_depth: usize, -} - -impl<'a> DepthCalculate<'a> { - pub fn new(max_depth: &'a mut usize) -> Self { - Self { - max_depth, - current_depth: 0, - } - } -} - -impl<'ctx> Visitor<'ctx> for DepthCalculate<'_> { - fn mode(&self) -> VisitMode { - VisitMode::Inline - } - - fn enter_field(&mut self, _ctx: &mut VisitorContext<'ctx>, _field: &'ctx Positioned) { - self.current_depth += 1; - *self.max_depth = (*self.max_depth).max(self.current_depth); - } - - fn exit_field(&mut self, _ctx: &mut VisitorContext<'ctx>, _field: &'ctx Positioned) { - self.current_depth -= 1; - } -} - -#[cfg(test)] -#[allow(clippy::diverging_sub_expression)] -mod tests { - use super::*; - use crate::{ - EmptyMutation, EmptySubscription, Object, Schema, parser::parse_query, validation::visit, - }; - - struct Query; - - struct MyObj; - - #[Object(internal)] - #[allow(unreachable_code)] - impl MyObj { - async fn a(&self) -> i32 { - todo!() - } - - async fn b(&self) -> i32 { - todo!() - } - - async fn c(&self) -> MyObj { - todo!() - } - } - - #[Object(internal)] - #[allow(unreachable_code)] - impl Query { - async fn value(&self) -> i32 { - todo!() - } - - async fn obj(&self) -> MyObj { - todo!() - } - } - - fn check_depth(query: &str, expect_depth: usize) { - let registry = - Schema::::create_registry(Default::default()); - let doc = parse_query(query).unwrap(); - let mut ctx = VisitorContext::new(®istry, &doc, None); - let mut depth = 0; - let mut depth_calculate = DepthCalculate::new(&mut depth); - visit(&mut depth_calculate, &mut ctx, &doc); - assert_eq!(depth, expect_depth); - } - - #[test] - fn depth() { - check_depth( - r#"{ - value #1 - }"#, - 1, - ); - - check_depth( - r#" - { - obj { #1 - a b #2 - } - }"#, - 2, - ); - - check_depth( - r#" - { - obj { # 1 - a b c { # 2 - a b c { # 3 - a b # 4 - } - } - } - }"#, - 4, - ); - - check_depth( - r#" - fragment A on MyObj { - a b ... A2 #2 - } - - fragment A2 on MyObj { - obj { - a #3 - } - } - - query { - obj { # 1 - ... A - } - }"#, - 3, - ); - - check_depth( - r#" - { - obj { # 1 - ... on MyObj { - a b #2 - ... on MyObj { - obj { - a #3 - } - } - } - } - }"#, - 3, - ); - } -} diff --git a/src/validation/visitors/mod.rs b/src/validation/visitors/mod.rs deleted file mode 100644 index 03765d98b..000000000 --- a/src/validation/visitors/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod cache_control; -mod complexity; -mod depth; - -pub use cache_control::CacheControlCalculate; -pub use complexity::ComplexityCalculate; -pub use depth::DepthCalculate; diff --git a/src/validators/chars_max_length.rs b/src/validators/chars_max_length.rs deleted file mode 100644 index c4504cbfb..000000000 --- a/src/validators/chars_max_length.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::{InputType, InputValueError}; - -pub fn chars_max_length + InputType>( - value: &T, - len: usize, -) -> Result<(), InputValueError> { - if value.as_ref().chars().count() <= len { - Ok(()) - } else { - Err(format!( - "the chars length is {}, must be less than or equal to {}", - value.as_ref().chars().count(), - len - ) - .into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_chars_max_length() { - assert!(chars_max_length(&"你好".to_string(), 3).is_ok()); - assert!(chars_max_length(&"你好啊".to_string(), 3).is_ok()); - assert!(chars_max_length(&"嗨你好啊".to_string(), 3).is_err()); - } -} diff --git a/src/validators/chars_min_length.rs b/src/validators/chars_min_length.rs deleted file mode 100644 index 5355786e9..000000000 --- a/src/validators/chars_min_length.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::{InputType, InputValueError}; - -pub fn chars_min_length + InputType>( - value: &T, - len: usize, -) -> Result<(), InputValueError> { - if value.as_ref().chars().count() >= len { - Ok(()) - } else { - Err(format!( - "the chars length is {}, must be greater than or equal to {}", - value.as_ref().chars().count(), - len - ) - .into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_chars_min_length() { - assert!(chars_min_length(&"你好".to_string(), 3).is_err()); - assert!(chars_min_length(&"你好啊".to_string(), 3).is_ok()); - assert!(chars_min_length(&"嗨你好啊".to_string(), 3).is_ok()); - } -} diff --git a/src/validators/email.rs b/src/validators/email.rs deleted file mode 100644 index 5d0768c61..000000000 --- a/src/validators/email.rs +++ /dev/null @@ -1,28 +0,0 @@ -use fast_chemail::is_valid_email; - -use crate::{InputType, InputValueError}; - -pub fn email + InputType>(value: &T) -> Result<(), InputValueError> { - if is_valid_email(value.as_ref()) { - Ok(()) - } else { - Err("invalid email".into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_email() { - assert!(email(&"joe@example.com".to_string()).is_ok()); - assert!(email(&"joe.test@example.com".to_string()).is_ok()); - assert!(email(&"email@example-one.com".to_string()).is_ok()); - assert!(email(&"1234567890@example.com".to_string()).is_ok()); - - assert!(email(&"plainaddress".to_string()).is_err()); - assert!(email(&"@example.com".to_string()).is_err()); - assert!(email(&"email.example.com".to_string()).is_err()); - } -} diff --git a/src/validators/ip.rs b/src/validators/ip.rs deleted file mode 100644 index f553561c5..000000000 --- a/src/validators/ip.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::{net::IpAddr, str::FromStr}; - -use crate::{InputType, InputValueError}; - -pub fn ip + InputType>(value: &T) -> Result<(), InputValueError> { - if IpAddr::from_str(value.as_ref()).is_ok() { - Ok(()) - } else { - Err("invalid ip".into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_ip() { - assert!(ip(&"1.1.1.1".to_string()).is_ok()); - assert!(ip(&"255.0.0.0".to_string()).is_ok()); - assert!(ip(&"256.1.1.1".to_string()).is_err()); - assert!(ip(&"fe80::223:6cff:fe8a:2e8a".to_string()).is_ok()); - assert!(ip(&"::ffff:254.42.16.14".to_string()).is_ok()); - assert!(ip(&"2a02::223:6cff :fe8a:2e8a".to_string()).is_err()); - } -} diff --git a/src/validators/max_items.rs b/src/validators/max_items.rs deleted file mode 100644 index aafc718b5..000000000 --- a/src/validators/max_items.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::ops::Deref; - -use crate::{InputType, InputValueError}; - -pub fn max_items + InputType, E>( - value: &T, - len: usize, -) -> Result<(), InputValueError> { - if value.deref().len() <= len { - Ok(()) - } else { - Err(format!( - "the value length is {}, must be less than or equal to {}", - value.deref().len(), - len - ) - .into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_max_items() { - assert!(max_items(&vec![1, 2], 3).is_ok()); - assert!(max_items(&vec![1, 2, 3], 3).is_ok()); - assert!(max_items(&vec![1, 2, 3, 4], 3).is_err()); - } -} diff --git a/src/validators/max_length.rs b/src/validators/max_length.rs deleted file mode 100644 index 94e080efb..000000000 --- a/src/validators/max_length.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::{InputType, InputValueError}; - -pub fn max_length + InputType>( - value: &T, - len: usize, -) -> Result<(), InputValueError> { - if value.as_ref().len() <= len { - Ok(()) - } else { - Err(format!( - "the string length is {}, must be less than or equal to {}", - value.as_ref().len(), - len - ) - .into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_max_length() { - assert!(max_length(&"ab".to_string(), 3).is_ok()); - assert!(max_length(&"abc".to_string(), 3).is_ok()); - assert!(max_length(&"abcd".to_string(), 3).is_err()); - } -} diff --git a/src/validators/maximum.rs b/src/validators/maximum.rs deleted file mode 100644 index 29de15d8a..000000000 --- a/src/validators/maximum.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::fmt::Display; - -use num_traits::AsPrimitive; - -use crate::{InputType, InputValueError}; - -pub fn maximum(value: &T, n: N) -> Result<(), InputValueError> -where - T: AsPrimitive + InputType, - N: PartialOrd + Display + Copy + 'static, -{ - if value.as_() <= n { - Ok(()) - } else { - Err(format!( - "the value is {}, must be less than or equal to {}", - value.as_(), - n - ) - .into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_maximum() { - assert!(maximum(&99, 100).is_ok()); - assert!(maximum(&100, 100).is_ok()); - assert!(maximum(&101, 100).is_err()); - } -} diff --git a/src/validators/min_items.rs b/src/validators/min_items.rs deleted file mode 100644 index bcd1fc39a..000000000 --- a/src/validators/min_items.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::ops::Deref; - -use crate::{InputType, InputValueError}; - -pub fn min_items + InputType, E>( - value: &T, - len: usize, -) -> Result<(), InputValueError> { - if value.deref().len() >= len { - Ok(()) - } else { - Err(format!( - "the value length is {}, must be greater than or equal to {}", - value.deref().len(), - len - ) - .into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_min_items() { - assert!(min_items(&vec![1, 2], 3).is_err()); - assert!(min_items(&vec![1, 2, 3], 3).is_ok()); - assert!(min_items(&vec![1, 2, 3, 4], 3).is_ok()); - } -} diff --git a/src/validators/min_length.rs b/src/validators/min_length.rs deleted file mode 100644 index 7e5d5f301..000000000 --- a/src/validators/min_length.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::{InputType, InputValueError}; - -pub fn min_length + InputType>( - value: &T, - len: usize, -) -> Result<(), InputValueError> { - if value.as_ref().len() >= len { - Ok(()) - } else { - Err(format!( - "the string length is {}, must be greater than or equal to {}", - value.as_ref().len(), - len - ) - .into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_min_length() { - assert!(min_length(&"ab".to_string(), 3).is_err()); - assert!(min_length(&"abc".to_string(), 3).is_ok()); - assert!(min_length(&"abcd".to_string(), 3).is_ok()); - } -} diff --git a/src/validators/min_password_strength.rs b/src/validators/min_password_strength.rs deleted file mode 100644 index 889553559..000000000 --- a/src/validators/min_password_strength.rs +++ /dev/null @@ -1,35 +0,0 @@ -use zxcvbn::{ZxcvbnError, zxcvbn}; - -use crate::{InputType, InputValueError}; - -pub fn min_password_strength + InputType>( - value: &T, - min_score: u8, -) -> Result<(), InputValueError> { - match zxcvbn(value.as_ref(), &[]) { - Ok(password_strength) => { - if password_strength.score() < min_score { - Err("password is too weak".into()) - } else { - Ok(()) - } - } - Err(ZxcvbnError::BlankPassword) => Err("password is too weak".into()), - _ => Err("error processing password strength".into()), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_min_password_strength() { - assert!(min_password_strength(&"password".to_string(), 3).is_err()); - assert!(min_password_strength(&"query".to_string(), 3).is_err()); - assert!(min_password_strength(&"P@ssword1".to_string(), 3).is_err()); - assert!(min_password_strength(&"".to_string(), 3).is_err()); - - assert!(min_password_strength(&"Some!Secure!Password".to_string(), 3).is_ok()); - } -} diff --git a/src/validators/minimum.rs b/src/validators/minimum.rs deleted file mode 100644 index b410c2c54..000000000 --- a/src/validators/minimum.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::fmt::Display; - -use num_traits::AsPrimitive; - -use crate::{InputType, InputValueError}; - -pub fn minimum(value: &T, n: N) -> Result<(), InputValueError> -where - T: AsPrimitive + InputType, - N: PartialOrd + Display + Copy + 'static, -{ - if value.as_() >= n { - Ok(()) - } else { - Err(format!( - "the value is {}, must be greater than or equal to {}", - value.as_(), - n - ) - .into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_minimum() { - assert!(minimum(&99, 100).is_err()); - assert!(minimum(&100, 100).is_ok()); - assert!(minimum(&101, 100).is_ok()); - } -} diff --git a/src/validators/mod.rs b/src/validators/mod.rs deleted file mode 100644 index ce115f1d8..000000000 --- a/src/validators/mod.rs +++ /dev/null @@ -1,56 +0,0 @@ -mod chars_max_length; -mod chars_min_length; -#[cfg(feature = "email-validator")] -mod email; -mod ip; -mod max_items; -mod max_length; -mod maximum; -mod min_items; -mod min_length; -#[cfg(feature = "password-strength-validator")] -mod min_password_strength; -mod minimum; -mod multiple_of; -mod regex; -mod url; -#[cfg(feature = "uuid-validator")] -mod uuid; - -pub use chars_max_length::chars_max_length; -pub use chars_min_length::chars_min_length; -#[cfg(feature = "email-validator")] -pub use email::email; -pub use ip::ip; -pub use max_items::max_items; -pub use max_length::max_length; -pub use maximum::maximum; -pub use min_items::min_items; -pub use min_length::min_length; -#[cfg(feature = "password-strength-validator")] -pub use min_password_strength::min_password_strength; -pub use minimum::minimum; -pub use multiple_of::multiple_of; -#[cfg(feature = "uuid-validator")] -pub use uuid::uuid; - -pub use self::{regex::regex, url::url}; -use crate::{InputType, InputValueError}; - -/// Represents a custom input value validator. -pub trait CustomValidator { - /// Check the value is valid. - fn check(&self, value: &T) -> Result<(), InputValueError>; -} - -impl CustomValidator for F -where - T: InputType, - E: Into>, - F: Fn(&T) -> Result<(), E>, -{ - #[inline] - fn check(&self, value: &T) -> Result<(), InputValueError> { - (self)(value).map_err(Into::into) - } -} diff --git a/src/validators/multiple_of.rs b/src/validators/multiple_of.rs deleted file mode 100644 index 29fd6f966..000000000 --- a/src/validators/multiple_of.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::{fmt::Display, ops::Rem}; - -use num_traits::{AsPrimitive, Zero}; - -use crate::{InputType, InputValueError}; - -pub fn multiple_of(value: &T, n: N) -> Result<(), InputValueError> -where - T: AsPrimitive + InputType, - N: Rem + Zero + Display + Copy + PartialEq + 'static, -{ - let value = value.as_(); - if !value.is_zero() && value % n == N::zero() { - Ok(()) - } else { - Err(format!("the value must be a multiple of {}.", n).into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_multiple_of() { - assert!(multiple_of(&5, 3).is_err()); - assert!(multiple_of(&6, 3).is_ok()); - assert!(multiple_of(&0, 3).is_err()); - } -} diff --git a/src/validators/regex.rs b/src/validators/regex.rs deleted file mode 100644 index bf1d6022c..000000000 --- a/src/validators/regex.rs +++ /dev/null @@ -1,25 +0,0 @@ -use regex::Regex; - -use crate::{InputType, InputValueError}; - -pub fn regex + InputType>( - value: &T, - regex: &'static str, -) -> Result<(), InputValueError> { - if let Ok(true) = Regex::new(regex).map(|re| re.is_match(value.as_ref())) { - Ok(()) - } else { - Err(format_args!("value doesn't match expected format '{}'", regex).into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_url() { - assert!(regex(&"123".to_string(), "^[0-9]+$").is_ok()); - assert!(regex(&"12a3".to_string(), "^[0-9]+$").is_err()); - } -} diff --git a/src/validators/url.rs b/src/validators/url.rs deleted file mode 100644 index 64e09e3c8..000000000 --- a/src/validators/url.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::str::FromStr; - -use crate::{InputType, InputValueError}; - -pub fn url + InputType>(value: &T) -> Result<(), InputValueError> { - if let Ok(true) = http::uri::Uri::from_str(value.as_ref()) - .map(|uri| uri.scheme().is_some() && uri.authority().is_some()) - { - Ok(()) - } else { - Err("invalid url".into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_url() { - assert!(url(&"http".to_string()).is_err()); - assert!(url(&"https://google.com".to_string()).is_ok()); - assert!(url(&"http://localhost:80".to_string()).is_ok()); - assert!(url(&"ftp://localhost:80".to_string()).is_ok()); - } -} diff --git a/src/validators/uuid.rs b/src/validators/uuid.rs deleted file mode 100644 index cf99ce9b4..000000000 --- a/src/validators/uuid.rs +++ /dev/null @@ -1,56 +0,0 @@ -use uuid::Uuid; - -use crate::{InputType, InputValueError}; - -pub fn uuid + InputType>( - value: &T, - version_option: Option, -) -> Result<(), InputValueError> { - match Uuid::try_parse(value.as_ref()) { - Ok(uuid) => { - if let Some(version) = version_option { - if uuid.get_version_num() != version { - return Err(InputValueError::custom("UUID version mismatch")); - } - } - Ok(()) - } - Err(_) => Err(InputValueError::custom("Invalid UUID")), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_uuid() { - assert!(uuid(&"94c59486-c302-4f43-abd7-a9c980ddab36".to_string(), None).is_ok()); - assert!( - uuid(&"94c59486-c302-4f43-abd7-a9c980ddab3".to_string(), None).is_err_and(|e| { - let message = format!("{:?}", e); - println!("{}", message); - message.contains("Invalid UUID") - }) - ); - } - - #[test] - fn test_uuid_version() { - assert!(uuid(&"94c59486-c302-4f43-abd7-a9c980ddab36".to_string(), Some(4)).is_ok()); - assert!( - uuid(&"94c59486-c302-4f43-abd7-a9c980ddab3".to_string(), Some(4)).is_err_and(|e| { - let message = format!("{:?}", e); - println!("{}", message); - message.contains("Invalid UUID") - }) - ); - assert!( - uuid(&"94c59486-c302-5f43-abd7-a9c980ddab36".to_string(), Some(4)).is_err_and(|e| { - let message = format!("{:?}", e); - println!("{}", message); - message.contains("UUID version mismatch") - }) - ); - } -} diff --git a/tests/batch_request.rs b/tests/batch_request.rs deleted file mode 100644 index 9dcf351cc..000000000 --- a/tests/batch_request.rs +++ /dev/null @@ -1,33 +0,0 @@ -use async_graphql::*; - -#[tokio::test] -pub async fn test_batch_request() { - struct Query; - - #[Object] - impl Query { - async fn value(&self, a: i32, b: i32) -> i32 { - a + b - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let batch: BatchRequest = vec![ - Request::new("{ value(a: 10, b: 20) }"), - Request::new("{ value(a: 30, b: 40) }"), - Request::new("{ value1 }"), - ] - .into(); - let resp = schema.execute_batch(batch).await; - assert_eq!( - serde_json::to_value(&resp).unwrap(), - serde_json::json!([ - {"data": { "value": 30 }}, - {"data": { "value": 70 }}, - {"data": null, "errors": [{ - "message": r#"Unknown field "value1" on type "Query". Did you mean "value"?"#, - "locations": [{"line": 1, "column": 3}] - }]}, - ]) - ); -} diff --git a/tests/binary.rs b/tests/binary.rs deleted file mode 100644 index 2f46d78f2..000000000 --- a/tests/binary.rs +++ /dev/null @@ -1,22 +0,0 @@ -use async_graphql::*; -use bytes::Bytes; - -#[tokio::test] -pub async fn test_batch_request() { - struct Query; - - #[Object] - impl Query { - async fn data(&self) -> Bytes { - Bytes::from_static(b"abcdef") - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute("{ data }").await.into_result().unwrap().data, - value!({ - "data": Bytes::from_static(b"abcdef"), - }) - ); -} diff --git a/tests/complex_object.rs b/tests/complex_object.rs deleted file mode 100644 index 203ac474b..000000000 --- a/tests/complex_object.rs +++ /dev/null @@ -1,538 +0,0 @@ -use core::marker::PhantomData; - -use async_graphql::*; - -#[tokio::test] -async fn test_complex_object_process_with_method_field() { - #[derive(SimpleObject)] - #[graphql(complex)] - struct MyObj { - a: i32, - } - - #[ComplexObject] - impl MyObj { - async fn test( - &self, - #[graphql(process_with = "str::make_ascii_uppercase")] processed_complex_arg: String, - ) -> String { - processed_complex_arg - } - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self) -> MyObj { - MyObj { a: 10 } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = "{ obj { test(processedComplexArg: \"smol\") } }"; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "obj": { - "test": "SMOL" - } - }) - ); -} - -#[tokio::test] -pub async fn test_complex_object() { - /// A complex object. - #[derive(SimpleObject)] - #[graphql(complex)] - struct MyObj { - a: i32, - b: i32, - } - - #[ComplexObject] - impl MyObj { - /// A field named `c`. - async fn c(&self) -> i32 { - self.a + self.b - } - - /// A field named `d`. - async fn d(&self, #[graphql(desc = "An argument named `v`.")] v: i32) -> i32 { - self.a + self.b + v - } - } - - #[allow(clippy::duplicated_attributes)] - #[derive(Interface)] - #[graphql( - field(name = "a", ty = "&i32"), - field(name = "b", ty = "&i32"), - field(name = "c", ty = "i32"), - field(name = "d", ty = "i32", arg(name = "v", ty = "i32")) - )] - enum ObjInterface { - MyObj(MyObj), - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self) -> MyObj { - MyObj { a: 10, b: 20 } - } - - async fn obj2(&self) -> ObjInterface { - MyObj { a: 10, b: 20 }.into() - } - } - - let query = "{ obj { a b c d(v:100) } obj2 { a b c d(v:200) } }"; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.data, - value!({ - "obj": { - "a": 10, - "b": 20, - "c": 30, - "d": 130, - }, - "obj2": { - "a": 10, - "b": 20, - "c": 30, - "d": 230, - } - }) - ); -} - -#[tokio::test] -pub async fn test_complex_object_with_generic_context_data() { - trait MyData: Send + Sync { - fn answer(&self) -> i64; - } - - struct DefaultMyData {} - - impl MyData for DefaultMyData { - fn answer(&self) -> i64 { - 42 - } - } - - struct MyQuery { - marker: PhantomData, - } - - #[Object] - impl MyQuery - where - D: 'static + MyData, - { - #[graphql(skip)] - pub fn new() -> Self { - Self { - marker: PhantomData, - } - } - - async fn obj(&self, ctx: &Context<'_>) -> MyObject { - MyObject::new(ctx.data_unchecked::().answer()) - } - } - - #[derive(SimpleObject, Debug, Clone, Hash, Eq, PartialEq)] - #[graphql(complex)] - struct MyObject { - my_val: i64, - #[graphql(skip)] - marker: PhantomData, - } - - #[ComplexObject] - impl MyObject { - #[graphql(skip)] - pub fn new(my_val: i64) -> Self { - Self { - my_val, - marker: PhantomData, - } - } - } - - let schema = Schema::build( - MyQuery::::new(), - EmptyMutation, - EmptySubscription, - ) - .data(DefaultMyData {}) - .finish(); - - assert_eq!( - schema.execute("{ obj { myVal } }").await.data, - value!({ - "obj": { - "myVal": 42, - } - }) - ); -} - -#[tokio::test] -pub async fn test_complex_object_with_generic_concrete_type() { - #[derive(SimpleObject)] - #[graphql(concrete(name = "MyObjIntString", params(i32, String)))] - #[graphql(concrete(name = "MyObji64f32", params(i64, u8)))] - #[graphql(complex)] - struct MyObj { - a: A, - b: B, - } - - #[ComplexObject] - impl MyObj { - async fn value_a(&self) -> String { - format!("i32,String {},{}", self.a, self.b) - } - } - - #[ComplexObject] - impl MyObj { - async fn value_b(&self) -> String { - format!("i64,u8 {},{}", self.a, self.b) - } - } - - struct Query; - - #[Object] - impl Query { - async fn q1(&self) -> MyObj { - MyObj { - a: 100, - b: "abc".to_string(), - } - } - - async fn q2(&self) -> MyObj { - MyObj { a: 100, b: 28 } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = "{ q1 { a b valueA } q2 { a b valueB } }"; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "q1": { - "a": 100, - "b": "abc", - "valueA": "i32,String 100,abc", - }, - "q2": { - "a": 100, - "b": 28, - "valueB": "i64,u8 100,28", - } - }) - ); - - assert_eq!( - schema - .execute(r#"{ __type(name: "MyObjIntString") { fields { name type { kind ofType { name } } } } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ - "__type": { - "fields": [ - { - "name": "a", - "type": { - "kind": "NON_NULL", - "ofType": { "name": "Int" }, - }, - }, - { - "name": "b", - "type": { - "kind": "NON_NULL", - "ofType": { "name": "String" }, - }, - }, - { - "name": "valueA", - "type": { - "kind": "NON_NULL", - "ofType": { "name": "String" }, - }, - }, - ] - } - }) - ); - - assert_eq!( - schema - .execute(r#"{ __type(name: "MyObji64f32") { fields { name type { kind ofType { name } } } } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ - "__type": { - "fields": [ - { - "name": "a", - "type": { - "kind": "NON_NULL", - "ofType": { "name": "Int" }, - }, - }, - { - "name": "b", - "type": { - "kind": "NON_NULL", - "ofType": { "name": "Int" }, - }, - }, - { - "name": "valueB", - "type": { - "kind": "NON_NULL", - "ofType": { "name": "String" }, - }, - }, - ] - } - }) - ); - - assert_eq!( - schema - .execute( - r#"{ __type(name: "Query") { fields { name type { kind ofType { name } } } } }"# - ) - .await - .into_result() - .unwrap() - .data, - value!({ - "__type": { - "fields": [ - { - "name": "q1", - "type": { - "kind": "NON_NULL", - "ofType": { "name": "MyObjIntString" }, - }, - }, - { - "name": "q2", - "type": { - "kind": "NON_NULL", - "ofType": { "name": "MyObji64f32" }, - }, - }, - ] - } - }) - ); -} - -#[tokio::test] -async fn test_flatten() { - #[derive(SimpleObject)] - struct A { - a: i32, - b: i32, - } - - #[derive(SimpleObject)] - #[graphql(complex)] - struct B { - #[graphql(skip)] - a: A, - c: i32, - } - - #[ComplexObject] - impl B { - #[graphql(flatten)] - async fn a(&self) -> &A { - &self.a - } - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self) -> B { - B { - a: A { a: 100, b: 200 }, - c: 300, - } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = "{ __type(name: \"B\") { fields { name } } }"; - assert_eq!( - schema.execute(query).await.data, - value!({ - "__type": { - "fields": [ - {"name": "c"}, - {"name": "a"}, - {"name": "b"} - ] - } - }) - ); - - let query = "{ obj { a b c } }"; - assert_eq!( - schema.execute(query).await.data, - value!({ - "obj": { - "a": 100, - "b": 200, - "c": 300, - } - }) - ); -} - -#[tokio::test] -async fn test_flatten_with_context() { - #[derive(SimpleObject)] - struct A { - a: i32, - b: i32, - } - - #[derive(SimpleObject)] - #[graphql(complex)] - struct B { - #[graphql(skip)] - a: A, - c: i32, - } - - #[ComplexObject] - impl B { - #[graphql(flatten)] - async fn a(&self, _ctx: &Context<'_>) -> &A { - &self.a - } - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self) -> B { - B { - a: A { a: 100, b: 200 }, - c: 300, - } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = "{ __type(name: \"B\") { fields { name } } }"; - assert_eq!( - schema.execute(query).await.data, - value!({ - "__type": { - "fields": [ - {"name": "c"}, - {"name": "a"}, - {"name": "b"} - ] - } - }) - ); - - let query = "{ obj { a b c } }"; - assert_eq!( - schema.execute(query).await.data, - value!({ - "obj": { - "a": 100, - "b": 200, - "c": 300, - } - }) - ); -} - -#[tokio::test] -async fn test_flatten_with_result() { - #[derive(SimpleObject)] - struct A { - a: i32, - b: i32, - } - - #[derive(SimpleObject)] - #[graphql(complex)] - struct B { - #[graphql(skip)] - a: A, - c: i32, - } - - #[ComplexObject] - impl B { - #[graphql(flatten)] - async fn a(&self) -> FieldResult<&A> { - Ok(&self.a) - } - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self) -> B { - B { - a: A { a: 100, b: 200 }, - c: 300, - } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = "{ __type(name: \"B\") { fields { name } } }"; - assert_eq!( - schema.execute(query).await.data, - value!({ - "__type": { - "fields": [ - {"name": "c"}, - {"name": "a"}, - {"name": "b"} - ] - } - }) - ); - - let query = "{ obj { a b c } }"; - assert_eq!( - schema.execute(query).await.data, - value!({ - "obj": { - "a": 100, - "b": 200, - "c": 300, - } - }) - ); -} diff --git a/tests/connection.rs b/tests/connection.rs deleted file mode 100644 index 06ca03c8b..000000000 --- a/tests/connection.rs +++ /dev/null @@ -1,227 +0,0 @@ -use async_graphql::{connection::*, *}; - -#[tokio::test] -pub async fn test_connection_additional_fields() { - struct Query; - - #[derive(SimpleObject)] - struct ConnectionFields { - total_count: i32, - } - - #[derive(SimpleObject)] - struct Diff { - diff: i32, - } - - #[Object] - impl Query { - async fn numbers( - &self, - after: Option, - before: Option, - first: Option, - last: Option, - ) -> Result> { - connection::query( - after, - before, - first, - last, - |after, before, first, last| async move { - let mut start = after.map(|after| after + 1).unwrap_or(0); - let mut end = before.unwrap_or(10000); - if let Some(first) = first { - end = (start + first).min(end); - } - if let Some(last) = last { - start = if last > end - start { end } else { end - last }; - } - let mut connection = Connection::with_additional_fields( - start > 0, - end < 10000, - ConnectionFields { total_count: 10000 }, - ); - connection.edges.extend((start..end).map(|n| { - Edge::with_additional_fields( - n, - n as i32, - Diff { - diff: (10000 - n) as i32, - }, - ) - })); - Ok::<_, Error>(connection) - }, - ) - .await - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - assert_eq!( - schema - .execute( - "{ numbers(first: 2) { __typename totalCount edges { __typename node diff } } }" - ) - .await - .data, - value!({ - "numbers": { - "__typename": "IntConnection", - "totalCount": 10000, - "edges": [ - {"__typename": "IntEdge", "node": 0, "diff": 10000}, - {"__typename": "IntEdge", "node": 1, "diff": 9999}, - ] - }, - }) - ); - - assert_eq!( - schema - .execute("{ numbers(last: 2) { edges { node diff } } }") - .await - .data, - value!({ - "numbers": { - "edges": [ - {"node": 9998, "diff": 2}, - {"node": 9999, "diff": 1}, - ] - }, - }) - ); -} - -#[tokio::test] -pub async fn test_connection_nodes() { - struct Query; - - #[Object] - impl Query { - async fn numbers( - &self, - after: Option, - before: Option, - first: Option, - last: Option, - ) -> Result> { - connection::query( - after, - before, - first, - last, - |after, before, first, last| async move { - let mut start = after.map(|after| after + 1).unwrap_or(0); - let mut end = before.unwrap_or(10000); - if let Some(first) = first { - end = (start + first).min(end); - } - if let Some(last) = last { - start = if last > end - start { end } else { end - last }; - } - let mut connection = Connection::new(start > 0, end < 10000); - connection - .edges - .extend((start..end).map(|n| Edge::new(n, n as i32))); - Ok::<_, Error>(connection) - }, - ) - .await - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - assert_eq!( - schema - .execute("{ numbers(first: 2) { __typename edges { __typename node } nodes } }") - .await - .data, - value!({ - "numbers": { - "__typename": "IntConnection", - "edges": [ - {"__typename": "IntEdge", "node": 0}, - {"__typename": "IntEdge", "node": 1}, - ], - "nodes": [ - 0, - 1, - ], - }, - }) - ); - - assert_eq!( - schema.execute("{ numbers(last: 2) { nodes } }").await.data, - value!({ - "numbers": { - "nodes": [ - 9998, - 9999, - ], - }, - }) - ); -} - -#[tokio::test] -pub async fn test_connection_nodes_disabled() { - struct Query; - - #[Object] - impl Query { - async fn numbers( - &self, - after: Option, - before: Option, - first: Option, - last: Option, - ) -> Result< - Connection< - usize, - i32, - EmptyFields, - EmptyFields, - DefaultConnectionName, - DefaultEdgeName, - DisableNodesField, - >, - > { - connection::query( - after, - before, - first, - last, - |after, before, first, last| async move { - let mut start = after.map(|after| after + 1).unwrap_or(0); - let mut end = before.unwrap_or(10000); - if let Some(first) = first { - end = (start + first).min(end); - } - if let Some(last) = last { - start = if last > end - start { end } else { end - last }; - } - let mut connection = Connection::new(start > 0, end < 10000); - connection - .edges - .extend((start..end).map(|n| Edge::new(n, n as i32))); - Ok::<_, Error>(connection) - }, - ) - .await - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - let r = schema.execute("{ numbers(last: 2) { nodes } }").await; - - assert_eq!( - r.errors[0].message, - "Unknown field \"nodes\" on type \"IntConnection\"." - ); -} diff --git a/tests/default_value.rs b/tests/default_value.rs deleted file mode 100644 index 02f037141..000000000 --- a/tests/default_value.rs +++ /dev/null @@ -1,116 +0,0 @@ -use async_graphql::*; - -#[tokio::test] -pub async fn test_default_value_arg() { - struct Query; - - #[Object] - impl Query { - async fn value1(&self, #[graphql(default = 100)] input: i32) -> i32 { - input - } - - async fn value2(&self, #[graphql(default)] input: i32) -> i32 { - input - } - - async fn value3(&self, #[graphql(default_with = "1 + 2 + 3")] input: i32) -> i32 { - input - } - - async fn value4(&self, #[graphql(default = 100)] input: u32) -> u32 { - input - } - } - - let query = "{ value1 value2 value3 value4 }"; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.data, - value!({ - "value1": 100, - "value2": 0, - "value3": 6, - "value4": 100, - }) - ); - - let query = "{ value1(input: 1) value2(input: 2) value3(input: 3) }"; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.data, - value!({ - "value1": 1, - "value2": 2, - "value3": 3, - }) - ); -} - -#[tokio::test] -pub async fn test_default_value_inputobject() { - #[derive(InputObject)] - struct MyInput { - #[graphql(default = 100)] - value1: i32, - - #[graphql(default)] - value2: i32, - - #[graphql(default_with = "1 + 2 + 3")] - value3: i32, - - #[graphql(default = 80.0)] - value4: f64, - } - - #[derive(SimpleObject)] - struct MyOutput { - value1: i32, - value2: i32, - value3: i32, - value4: f64, - } - - struct Query; - - #[Object] - impl Query { - async fn value(&self, input: MyInput) -> MyOutput { - MyOutput { - value1: input.value1, - value2: input.value2, - value3: input.value3, - value4: input.value4, - } - } - } - - let query = "{ value(input: {}) { value1 value2 value3 value4 } }"; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.data, - value!({ - "value": { - "value1": 100, - "value2": 0, - "value3": 6, - "value4": 80.0, - } - }) - ); - - let query = "{ value(input: { value1: 1, value2: 2, value3: 3, value4: 88.0 }) { value1 value2 value3 value4 } }"; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.data, - value!({ - "value": { - "value1": 1, - "value2": 2, - "value3": 3, - "value4": 88.0, - } - }) - ); -} diff --git a/tests/derived_field.rs b/tests/derived_field.rs deleted file mode 100644 index 9f2ce0352..000000000 --- a/tests/derived_field.rs +++ /dev/null @@ -1,438 +0,0 @@ -#![allow(clippy::uninlined_format_args)] - -use async_graphql::*; - -#[tokio::test] -pub async fn test_derived_field_object() { - use serde::{Deserialize, Serialize}; - - struct Query; - - #[derive(Serialize, Deserialize)] - struct ValueDerived(String); - - scalar!(ValueDerived); - - impl From for ValueDerived { - fn from(value: i32) -> Self { - ValueDerived(format!("{}", value)) - } - } - - #[Object] - impl Query { - #[graphql(derived(name = "value2", into = "ValueDerived"))] - async fn value1(&self, #[graphql(default = 100)] input: i32) -> i32 { - input - } - } - - let query = "{ value1 value2 }"; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.data, - value!({ - "value1": 100, - "value2": "100", - }) - ); - - let query = "{ value1(input: 1) value2(input: 2) }"; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.data, - value!({ - "value1": 1, - "value2": "2", - }) - ); -} - -#[tokio::test] -pub async fn test_derived_field_object_with() { - use serde::{Deserialize, Serialize}; - - struct Query; - - #[derive(Serialize, Deserialize)] - struct ValueDerived(String); - - scalar!(ValueDerived); - - impl From for ValueDerived { - fn from(value: i32) -> Self { - ValueDerived(format!("{}", value)) - } - } - - fn option_to_option>(value: Option) -> Option { - value.map(|x| x.into()) - } - - #[Object] - impl Query { - #[graphql(derived( - name = "value2", - into = "Option", - with = "option_to_option" - ))] - async fn value1(&self, #[graphql(default = 100)] input: i32) -> Option { - Some(input) - } - } - - let query = "{ value1 value2 }"; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.data, - value!({ - "value1": 100, - "value2": "100", - }) - ); - - let query = "{ value1(input: 1) value2(input: 2) }"; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.data, - value!({ - "value1": 1, - "value2": "2", - }) - ); -} - -#[tokio::test] -pub async fn test_derived_field_simple_object() { - use serde::{Deserialize, Serialize}; - - struct Query; - - #[derive(Serialize, Deserialize)] - struct ValueDerived(String); - - scalar!(ValueDerived); - - impl From for ValueDerived { - fn from(value: i32) -> Self { - ValueDerived(format!("{}", value)) - } - } - - #[derive(SimpleObject)] - struct TestObj { - #[graphql(owned, derived(name = "value2", into = "ValueDerived"))] - pub value1: i32, - } - - #[Object] - impl Query { - async fn test(&self, #[graphql(default = 100)] input: i32) -> TestObj { - TestObj { value1: input } - } - } - - let query = "{ test { value1 value2 } }"; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.data, - value!({ - "test": { - "value1": 100, - "value2": "100", - } - }) - ); - - let query = "{ test(input: 2) { value1 value2 }}"; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - dbg!(schema.execute(query).await); - assert_eq!( - schema.execute(query).await.data, - value!({ - "test": { - "value1": 2, - "value2": "2", - } - }) - ); -} - -#[tokio::test] -pub async fn test_derived_field_simple_object_option() { - use serde::{Deserialize, Serialize}; - - struct Query; - - #[derive(Serialize, Deserialize, Clone)] - struct ValueDerived(String); - - #[derive(Serialize, Deserialize, Clone)] - struct ValueDerived2(String); - - scalar!(ValueDerived); - scalar!(ValueDerived2); - - impl From for ValueDerived2 { - fn from(value: ValueDerived) -> Self { - ValueDerived2(value.0) - } - } - - fn option_to_option>(value: Option) -> Option { - value.map(|x| x.into()) - } - - fn vec_to_vec>(value: Vec) -> Vec { - value.into_iter().map(|x| x.into()).collect() - } - - fn vecopt_to_vecopt>(value: Vec>) -> Vec> { - value.into_iter().map(|x| x.map(|opt| opt.into())).collect() - } - - fn optvec_to_optvec>(value: Option>) -> Option> { - value.map(|x| x.into_iter().map(|y| y.into()).collect()) - } - - #[derive(SimpleObject)] - struct TestObj { - #[graphql(derived( - owned, - name = "value2", - into = "Option", - with = "option_to_option" - ))] - pub value1: Option, - #[graphql(derived( - owned, - name = "value_vec_2", - into = "Vec", - with = "vec_to_vec" - ))] - pub value_vec_1: Vec, - #[graphql(derived( - owned, - name = "value_opt_vec_2", - into = "Option>", - with = "optvec_to_optvec" - ))] - pub value_opt_vec_1: Option>, - #[graphql(derived( - owned, - name = "value_vec_opt_2", - into = "Vec>", - with = "vecopt_to_vecopt" - ))] - pub value_vec_opt_1: Vec>, - } - - #[Object] - impl Query { - async fn test(&self) -> TestObj { - TestObj { - value1: Some(ValueDerived("Test".to_string())), - value_vec_1: vec![ValueDerived("Test".to_string())], - value_opt_vec_1: Some(vec![ValueDerived("Test".to_string())]), - value_vec_opt_1: vec![Some(ValueDerived("Test".to_string()))], - } - } - } - - let query = "{ test { value1 value2 valueVec1 valueVec2 valueOptVec1 valueOptVec2 } }"; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.data, - value!({ - "test": { - "value1": "Test", - "value2": "Test", - "valueVec1": vec!["Test"], - "valueVec2": vec!["Test"], - "valueOptVec1": vec!["Test"], - "valueOptVec2": vec!["Test"], - } - }) - ); -} - -#[tokio::test] -pub async fn test_derived_field_complex_object() { - use serde::{Deserialize, Serialize}; - - #[derive(SimpleObject)] - #[graphql(complex)] - struct MyObj { - a: i32, - #[graphql(owned, derived(name = "f", into = "ValueDerived"))] - b: i32, - } - - #[derive(Serialize, Deserialize)] - struct ValueDerived(String); - - scalar!(ValueDerived); - - impl From for ValueDerived { - fn from(value: i32) -> Self { - ValueDerived(format!("{}", value)) - } - } - - #[ComplexObject] - impl MyObj { - async fn c(&self) -> i32 { - self.a + self.b - } - - #[graphql(derived(name = "e", into = "ValueDerived"))] - async fn d(&self, v: i32) -> i32 { - self.a + self.b + v - } - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self) -> MyObj { - MyObj { a: 10, b: 20 } - } - } - - let query = "{ obj { a b c d(v:100) e(v: 200) f } }"; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - dbg!(schema.execute(query).await); - assert_eq!( - schema.execute(query).await.data, - value!({ - "obj": { - "a": 10, - "b": 20, - "c": 30, - "d": 130, - "e": "230", - "f": "20", - }, - }) - ); -} - -#[tokio::test] -pub async fn test_derived_field_complex_object_derived() { - use serde::{Deserialize, Serialize}; - - #[derive(SimpleObject)] - #[graphql(complex)] - struct MyObj { - a: i32, - #[graphql(owned, derived(name = "f", into = "ValueDerived"))] - b: i32, - } - - #[derive(Serialize, Deserialize)] - struct ValueDerived(String); - - scalar!(ValueDerived); - - impl From for ValueDerived { - fn from(value: i32) -> Self { - ValueDerived(format!("{}", value)) - } - } - - fn option_to_option>(value: Option) -> Option { - value.map(|x| x.into()) - } - - #[ComplexObject] - impl MyObj { - async fn c(&self) -> i32 { - self.a + self.b - } - - #[graphql(derived(name = "e", into = "Option", with = "option_to_option"))] - async fn d(&self, v: i32) -> Option { - Some(self.a + self.b + v) - } - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self) -> MyObj { - MyObj { a: 10, b: 20 } - } - } - - let query = "{ obj { a b c d(v:100) e(v: 200) f } }"; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.data, - value!({ - "obj": { - "a": 10, - "b": 20, - "c": 30, - "d": 130, - "e": "230", - "f": "20", - }, - }) - ); -} - -#[tokio::test] -pub async fn test_derived_field_with_skip_simple_object() { - use serde::{Deserialize, Serialize}; - - struct Query; - - #[derive(Serialize, Deserialize)] - struct ValueDerived(String); - - scalar!(ValueDerived); - - impl From for ValueDerived { - fn from(value: i32) -> Self { - ValueDerived(format!("{}", value)) - } - } - - #[derive(SimpleObject)] - struct TestObj { - #[graphql(derived(owned, name = "value2", into = "ValueDerived"), skip)] - pub value1: i32, - } - - #[Object] - impl Query { - async fn test(&self, #[graphql(default = 100)] input: i32) -> TestObj { - TestObj { value1: input } - } - } - - let query = "{ test { value2 } }"; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.data, - value!({ - "test": { - "value2": "100", - } - }) - ); - - let query = "{ test(input: 2) { value2 }}"; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - dbg!(schema.execute(query).await); - assert_eq!( - schema.execute(query).await.data, - value!({ - "test": { - "value2": "2", - } - }) - ); -} diff --git a/tests/description.rs b/tests/description.rs deleted file mode 100644 index b9c3e056f..000000000 --- a/tests/description.rs +++ /dev/null @@ -1,157 +0,0 @@ -#![allow(clippy::diverging_sub_expression)] - -use async_graphql::*; - -#[tokio::test] -pub async fn test_use_type_description() { - /// Haha - #[doc = "next line"] - #[derive(Description, Default)] - struct MyObj; - - #[Object(use_type_description)] - impl MyObj { - async fn value(&self) -> i32 { - 100 - } - } - - #[derive(SimpleObject, Default)] - struct Query { - obj: MyObj, - } - - let schema = Schema::new(Query::default(), EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute(r#"{ __type(name: "MyObj") { description } }"#) - .await - .data, - value!({ - "__type": { "description": "Haha\nnext line" } - }) - ); -} - -#[tokio::test] -pub async fn test_use_type_external() { - /// Wow - #[doc = include_str!("external_descriptions/desc1.md")] - /// More - #[derive(Description, Default)] - struct MyObj<'a>(&'a str); - - #[Object(use_type_description)] - impl MyObj<'_> { - async fn value(&self) -> &str { - self.0 - } - } - - struct Query; - - #[Object] - #[allow(unreachable_code)] - impl Query { - async fn obj(&self) -> MyObj<'_> { - todo!() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute(r#"{ __type(name: "MyObj") { description } }"#) - .await - .data, - value!({ - "__type": { "description": "Wow\nextern 1\n\nextern 2\nMore" } - }) - ); -} - -#[tokio::test] -pub async fn test_use_type_external_macro() { - macro_rules! external_doc { - ($ident:ident) => { - include_str!(concat!("external_descriptions/", stringify!($ident), ".md")) - }; - } - - /// Wow - // Simple declarative macros also work - #[doc = external_doc!(desc1)] - /// More - #[derive(Description, Default)] - struct MyObj<'a>(&'a str); - - #[Object(use_type_description)] - impl MyObj<'_> { - async fn value(&self) -> &str { - self.0 - } - } - - struct Query; - - #[Object] - #[allow(unreachable_code)] - impl Query { - async fn obj(&self) -> MyObj<'_> { - todo!() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute(r#"{ __type(name: "MyObj") { description } }"#) - .await - .data, - value!({ - "__type": { "description": "Wow\nextern 1\n\nextern 2\nMore" } - }) - ); -} - -#[tokio::test] -pub async fn test_fields() { - #[derive(SimpleObject, Default)] - #[graphql(complex)] - struct Obj { - #[doc = include_str!("external_descriptions/desc1.md")] - obj: String, - } - - #[ComplexObject] - impl Obj { - #[doc = "line 1"] - #[doc = ""] - /// - #[doc = "line 2"] - /// - #[doc = include_str!("external_descriptions/desc2.md")] - // Make sure trailing whitespace is removed - /// - #[doc = ""] - async fn obj2(&self) -> i32 { - 0 - } - } - - let schema = Schema::new(Obj::default(), EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute(r#"{ __type(name: "Obj") { fields { name, description } } }"#) - .await - .data, - value!({ - "__type": { - "fields": [ - {"name": "obj", "description": "extern 1\n\nextern 2"}, - {"name": "obj2", "description": "line 1\n\n\nline 2\n\nexternal"} - ] - } - }) - ); -} diff --git a/tests/directive.rs b/tests/directive.rs deleted file mode 100644 index 55c564424..000000000 --- a/tests/directive.rs +++ /dev/null @@ -1,230 +0,0 @@ -use async_graphql::*; -use serde::{Deserialize, Serialize}; - -#[tokio::test] -pub async fn test_directive_skip() { - struct Query; - - #[Object] - impl Query { - pub async fn value(&self) -> i32 { - 10 - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let data = schema - .execute( - r#" - fragment A on Query { - value5: value @skip(if: true) - value6: value @skip(if: false) - } - - query { - value1: value @skip(if: true) - value2: value @skip(if: false) - ... @skip(if: true) { - value3: value - } - ... @skip(if: false) { - value4: value - } - ... A - } - "#, - ) - .await - .into_result() - .unwrap() - .data; - assert_eq!( - data, - value!({ - "value2": 10, - "value4": 10, - "value6": 10, - }) - ); -} - -#[tokio::test] -pub async fn test_directive_include() { - struct Query; - - #[Object] - impl Query { - pub async fn value(&self) -> i32 { - 10 - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let resp = schema - .execute( - r#" - { - value1: value @include(if: true) - value2: value @include(if: false) - } - "#, - ) - .await; - assert_eq!( - resp.data, - value!({ - "value1": 10, - }) - ); -} - -#[tokio::test] -pub async fn test_custom_directive() { - struct Concat { - prefix: String, - suffix: String, - } - - #[async_trait::async_trait] - impl CustomDirective for Concat { - async fn resolve_field( - &self, - _ctx: &Context<'_>, - resolve: ResolveFut<'_>, - ) -> ServerResult> { - resolve.await.map(|value| { - value.map(|value| match value { - Value::String(str) => Value::String(self.prefix.clone() + &str + &self.suffix), - _ => value, - }) - }) - } - } - - #[Directive(location = "Field")] - fn concat(prefix: String, suffix: String) -> impl CustomDirective { - Concat { prefix, suffix } - } - - struct Query; - - #[Object] - impl Query { - pub async fn value(&self) -> &'static str { - "abc" - } - } - - let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .directive(concat) - .finish(); - assert_eq!( - schema - .execute(r#"{ value @concat(prefix: "&", suffix: "*") }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "value": "&abc*" }) - ); -} - -#[tokio::test] -pub async fn test_no_unused_directives() { - struct Query; - - #[Object] - impl Query { - pub async fn a(&self) -> String { - "a".into() - } - } - - let sdl = Schema::new(Query, EmptyMutation, EmptySubscription).sdl(); - - assert!(!sdl.contains("directive @deprecated")); - assert!(!sdl.contains("directive @specifiedBy")); - assert!(!sdl.contains("directive @oneOf")); -} - -#[tokio::test] -pub async fn test_includes_deprecated_directive() { - #[derive(SimpleObject)] - struct A { - #[graphql(deprecation = "Use `Foo` instead")] - a: String, - } - - struct Query; - - #[Object] - impl Query { - pub async fn a(&self) -> A { - A { a: "a".into() } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - assert!(schema.sdl().contains( - r#" -""" -Marks an element of a GraphQL schema as no longer supported. -""" -directive @deprecated(reason: String = "No longer supported") on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION | ENUM_VALUE"#, - )) -} - -#[tokio::test] -pub async fn test_includes_specified_by_directive() { - #[derive(Serialize, Deserialize)] - struct A { - a: String, - } - - scalar!( - A, - "A", - "This is A", - "https://www.youtube.com/watch?v=dQw4w9WgXcQ" - ); - - struct Query; - - #[Object] - impl Query { - pub async fn a(&self) -> A { - A { a: "a".into() } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - assert!( - schema - .sdl() - .contains(r#"directive @specifiedBy(url: String!) on SCALAR"#) - ) -} - -#[tokio::test] -pub async fn test_includes_one_of_directive() { - #[derive(OneofObject)] - enum AB { - A(String), - B(i64), - } - - struct Query; - - #[Object] - impl Query { - pub async fn ab(&self, _input: AB) -> bool { - true - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - assert!(schema.sdl().contains(r#"directive @oneOf on INPUT_OBJECT"#)) -} diff --git a/tests/dynamic_schema.rs b/tests/dynamic_schema.rs deleted file mode 100644 index 60ef15d30..000000000 --- a/tests/dynamic_schema.rs +++ /dev/null @@ -1,144 +0,0 @@ -#[cfg(feature = "dynamic-schema")] -mod tests { - use async_graphql::{ - Value, - dynamic::{ - Directive, Enum, EnumItem, Field, FieldFuture, InputObject, InputValue, Interface, - InterfaceField, Object, ResolverContext, Scalar, Schema, SchemaError, TypeRef, Union, - }, - }; - - fn mock_resolver_fn(_ctx: ResolverContext) -> FieldFuture { - FieldFuture::Value(None) - } - - pub fn schema() -> Result { - let test_enum = Enum::new("TestEnum") - .item(EnumItem::new("A")) - .item(EnumItem::new("B").directive(Directive::new("default"))) - .item(EnumItem::new("C")) - .directive(Directive::new("oneOf")); - - let interface = Interface::new("TestInterface") - .field( - InterfaceField::new("id", TypeRef::named_nn(TypeRef::STRING)) - .directive(Directive::new("id")), - ) - .field(InterfaceField::new( - "name", - TypeRef::named_nn(TypeRef::STRING), - )) - .directive( - Directive::new("test") - .argument("a", Value::from(5)) - .argument("b", Value::from(true)) - .argument("c", Value::from("str")), - ); - - let output_type = Object::new("OutputType") - .implement(interface.type_name()) - .field( - Field::new("id", TypeRef::named_nn(TypeRef::STRING), mock_resolver_fn) - .directive(Directive::new("test")), - ) - .field(Field::new( - "name", - TypeRef::named_nn(TypeRef::STRING), - mock_resolver_fn, - )) - .field(Field::new( - "body", - TypeRef::named(TypeRef::STRING), - mock_resolver_fn, - )) - .directive(Directive::new("type")); - - let output_type_2 = Object::new("OutputType2").field(Field::new( - "a", - TypeRef::named_nn_list_nn(TypeRef::INT), - mock_resolver_fn, - )); - - let union_type = Union::new("TestUnion") - .possible_type(output_type.type_name()) - .possible_type(output_type_2.type_name()) - .directive(Directive::new("wrap")); - - let input_type = InputObject::new("InputType") - .field( - InputValue::new("a", TypeRef::named_nn(TypeRef::STRING)) - .directive(Directive::new("input_a").argument("test", Value::from(5))), - ) - .directive(Directive::new("a")) - .directive(Directive::new("b")); - - let scalar = Scalar::new("TestScalar").directive(Directive::new("json")); - - let query = Object::new("Query") - .field( - Field::new( - "interface", - TypeRef::named_nn(interface.type_name()), - mock_resolver_fn, - ) - .argument( - InputValue::new("x", TypeRef::named(test_enum.type_name())) - .directive(Directive::new("validate")), - ), - ) - .field( - Field::new( - "output_type", - TypeRef::named(output_type.type_name()), - mock_resolver_fn, - ) - .argument(InputValue::new( - "input", - TypeRef::named_nn(input_type.type_name()), - )), - ) - .field( - Field::new( - "enum", - TypeRef::named(test_enum.type_name()), - mock_resolver_fn, - ) - .argument(InputValue::new( - "input", - TypeRef::named_list_nn(test_enum.type_name()), - )) - .directive(Directive::new("pin")), - ) - .field(Field::new( - "union", - TypeRef::named_nn(union_type.type_name()), - mock_resolver_fn, - )) - .field(Field::new( - "scalar", - TypeRef::named(scalar.type_name()), - mock_resolver_fn, - )); - - Schema::build(query.type_name(), None, None) - .register(test_enum) - .register(interface) - .register(input_type) - .register(output_type) - .register(output_type_2) - .register(union_type) - .register(scalar) - .register(query) - .finish() - } - - #[test] - fn test_schema_sdl() { - let schema = schema().unwrap(); - let sdl = schema.sdl(); - - let expected = include_str!("schemas/test_dynamic_schema.graphql"); - - assert_eq!(sdl, expected); - } -} diff --git a/tests/enum.rs b/tests/enum.rs deleted file mode 100644 index ef330e7fe..000000000 --- a/tests/enum.rs +++ /dev/null @@ -1,110 +0,0 @@ -use async_graphql::*; - -#[tokio::test] -pub async fn test_enum_type() { - #[derive(Enum, Copy, Clone, Eq, PartialEq)] - enum MyEnum { - A, - B, - } - - #[derive(InputObject)] - struct MyInput { - value: MyEnum, - } - - struct Root { - value: MyEnum, - } - - #[Object] - impl Root { - async fn value(&self) -> MyEnum { - self.value - } - - async fn test_arg(&self, input: MyEnum) -> MyEnum { - input - } - - async fn test_input<'a>(&self, input: MyInput) -> MyEnum { - input.value - } - } - - let schema = Schema::new(Root { value: MyEnum::A }, EmptyMutation, EmptySubscription); - let query = r#"{ - value - testArg(input: A) - testInput(input: {value: B}) - }"# - .to_owned(); - assert_eq!( - schema.execute(&query).await.data, - value!({ - "value": "A", - "testArg": "A", - "testInput": "B", - }) - ); -} - -#[tokio::test] -pub async fn test_enum_derive_and_item_attributes() { - use serde::Deserialize; - - #[derive(Deserialize, Debug, Enum, Copy, Clone, Eq, PartialEq)] - enum Test { - #[serde(alias = "Other")] - Real, - } - - #[derive(Deserialize, PartialEq, Debug)] - #[allow(dead_code)] - struct TestStruct { - value: Test, - } - - assert_eq!( - from_value::(value!({"value": "Other"})).unwrap(), - TestStruct { value: Test::Real } - ); -} - -#[tokio::test] -pub async fn test_remote_enum() { - #[derive(Enum, Copy, Clone, Eq, PartialEq)] - #[graphql(remote = "remote::RemoteEnum")] - enum LocalEnum { - A, - B, - C, - } - - mod remote { - pub enum RemoteEnum { - A, - B, - C, - } - } - - let _: remote::RemoteEnum = LocalEnum::A.into(); - let _: LocalEnum = remote::RemoteEnum::A.into(); -} - -#[tokio::test] -pub async fn test_display() { - #[derive(Enum, Copy, Clone, Eq, PartialEq)] - #[graphql(display)] - enum MyEnum { - A, - #[graphql(name = "bbb")] - B, - C, - } - - assert_eq!(MyEnum::A.to_string(), "A"); - assert_eq!(MyEnum::B.to_string(), "bbb"); - assert_eq!(MyEnum::C.to_string(), "C"); -} diff --git a/tests/error_ext.rs b/tests/error_ext.rs deleted file mode 100644 index 51d886c19..000000000 --- a/tests/error_ext.rs +++ /dev/null @@ -1,191 +0,0 @@ -use async_graphql::*; - -#[tokio::test] -pub async fn test_error_extensions() { - #[derive(Enum, Eq, PartialEq, Copy, Clone)] - enum MyEnum { - Create, - Delete, - Update, - } - - struct Query; - - #[Object] - impl Query { - async fn extend_err(&self) -> Result { - Err("my error".extend_with(|err, e| { - e.set("msg", err.to_string()); - e.set("code", 100); - e.set("action", MyEnum::Create) - })) - } - - async fn extend_result(&self) -> Result { - Err(Error::from("my error")) - .extend_err(|_, e| { - e.set("msg", "my error"); - e.set("code", 100); - }) - .extend_err(|_, e| { - e.set("code2", 20); - }) - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - assert_eq!( - serde_json::to_value(&schema.execute("{ extendErr }").await).unwrap(), - serde_json::json!({ - "data": null, - "errors": [{ - "message": "my error", - "locations": [{ - "column": 3, - "line": 1, - }], - "path": ["extendErr"], - "extensions": { - "msg": "my error", - "code": 100, - "action": "CREATE", - } - }] - }) - ); - - assert_eq!( - serde_json::to_value(&schema.execute("{ extendResult }").await).unwrap(), - serde_json::json!({ - "data": null, - "errors": [{ - "message": "my error", - "locations": [{ - "column": 3, - "line": 1, - }], - "path": ["extendResult"], - "extensions": { - "msg": "my error", - "code": 100, - "code2": 20 - } - }] - }) - ); -} - -#[tokio::test] -pub async fn test_failure() { - #[derive(thiserror::Error, Debug, PartialEq)] - enum MyError { - #[error("error1")] - Error1, - - #[error("error2")] - Error2, - } - - struct Query; - - #[Object] - impl Query { - async fn failure(&self) -> Result { - Err(Error::new_with_source(MyError::Error1)) - } - - async fn failure2(&self) -> Result { - Err(Error::new_with_source(MyError::Error2)) - } - - async fn failure3(&self) -> Result { - Err(Error::new_with_source(MyError::Error1) - .extend_with(|_, values| values.set("a", 1)) - .extend_with(|_, values| values.set("b", 2))) - } - - async fn failure4(&self) -> Result { - Err(Error::new_with_source(MyError::Error2)) - .extend_err(|_, values| values.set("a", 1)) - .extend_err(|_, values| values.set("b", 2))?; - Ok(1) - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let err = schema - .execute("{ failure }") - .await - .into_result() - .unwrap_err() - .remove(0); - assert_eq!(err.source::().unwrap(), &MyError::Error1); - - let err = schema - .execute("{ failure2 }") - .await - .into_result() - .unwrap_err() - .remove(0); - assert_eq!(err.source::().unwrap(), &MyError::Error2); - - let err = schema - .execute("{ failure3 }") - .await - .into_result() - .unwrap_err() - .remove(0); - assert_eq!(err.source::().unwrap(), &MyError::Error1); - assert_eq!( - err.extensions, - Some({ - let mut values = ErrorExtensionValues::default(); - values.set("a", 1); - values.set("b", 2); - values - }) - ); - - let err = schema - .execute("{ failure4 }") - .await - .into_result() - .unwrap_err() - .remove(0); - assert_eq!(err.source::().unwrap(), &MyError::Error2); - assert_eq!( - err.extensions, - Some({ - let mut values = ErrorExtensionValues::default(); - values.set("a", 1); - values.set("b", 2); - values - }) - ); -} - -#[tokio::test] -pub async fn test_failure2() { - #[derive(thiserror::Error, Debug, PartialEq)] - enum MyError { - #[error("error1")] - Error1, - } - - #[cfg(feature = "custom-error-conversion")] - impl From for Error { - fn from(e: MyError) -> Self { - Self::new_with_source(e) - } - } - - struct Query; - - #[Object] - impl Query { - async fn failure(&self) -> Result { - Err(MyError::Error1) - } - } -} diff --git a/tests/export_sdl.rs b/tests/export_sdl.rs deleted file mode 100644 index ef19386bb..000000000 --- a/tests/export_sdl.rs +++ /dev/null @@ -1,44 +0,0 @@ -use async_graphql::*; - -#[tokio::test] -async fn test_spaces() { - #[derive(SimpleObject)] - struct A { - a: i32, - b: i32, - } - - #[derive(SimpleObject)] - struct B { - a: A, - b: i32, - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self) -> B { - B { - a: A { a: 100, b: 200 }, - b: 300, - } - } - - async fn a(&self) -> A { - A { a: 100, b: 200 } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let sdl = schema.sdl_with_options( - SDLExportOptions::new() - .use_space_ident() - .indent_width(2) - .sorted_fields() - .sorted_enum_items(), - ); - - let expected = include_str!("schemas/test_space_schema.graphql"); - assert_eq!(sdl, expected); -} diff --git a/tests/extension.rs b/tests/extension.rs deleted file mode 100644 index a50eff80d..000000000 --- a/tests/extension.rs +++ /dev/null @@ -1,535 +0,0 @@ -use std::sync::{ - Arc, - atomic::{AtomicI32, Ordering}, -}; - -use async_graphql::{ - extensions::{ - Extension, ExtensionContext, ExtensionFactory, NextExecute, NextParseQuery, - NextPrepareRequest, NextRequest, NextResolve, NextSubscribe, NextValidation, ResolveInfo, - }, - futures_util::stream::BoxStream, - parser::types::ExecutableDocument, - *, -}; -use async_graphql_value::ConstValue; -use futures_util::{StreamExt, lock::Mutex, stream::Stream}; - -#[tokio::test] -pub async fn test_extension_ctx() { - #[derive(Default, Clone)] - struct MyData(Arc>); - - struct Query; - - #[Object] - impl Query { - async fn value(&self, ctx: &Context<'_>) -> i32 { - *ctx.data_unchecked::().0.lock().await - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn value(&self, ctx: &Context<'_>) -> impl Stream { - let data = *ctx.data_unchecked::().0.lock().await; - futures_util::stream::once(async move { data }) - } - } - - struct MyExtensionImpl; - - #[async_trait::async_trait] - impl Extension for MyExtensionImpl { - async fn parse_query( - &self, - ctx: &ExtensionContext<'_>, - query: &str, - variables: &Variables, - next: NextParseQuery<'_>, - ) -> ServerResult { - if let Ok(data) = ctx.data::() { - *data.0.lock().await = 100; - } - next.run(ctx, query, variables).await - } - } - - struct MyExtension; - - impl ExtensionFactory for MyExtension { - fn create(&self) -> Arc { - Arc::new(MyExtensionImpl) - } - } - - // data in schema - { - let data = MyData::default(); - let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .data(data.clone()) - .extension(MyExtension) - .finish(); - assert_eq!( - schema - .execute("{ value }") - .await - .into_result() - .unwrap() - .data, - value! ({ - "value": 100 - }) - ); - } - - // data in request - { - let data = MyData::default(); - let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .extension(MyExtension) - .finish(); - - assert_eq!( - schema - .execute(Request::new("{ value }").data(data.clone())) - .await - .into_result() - .unwrap() - .data, - value! ({ - "value": 100 - }) - ); - } - - // data in session - { - let schema = Schema::build(Query, EmptyMutation, Subscription) - .extension(MyExtension) - .finish(); - - let mut data = Data::default(); - data.insert(MyData::default()); - let mut stream = schema.execute_stream_with_session_data( - Request::new("subscription { value }"), - Arc::new(data), - ); - assert_eq!( - stream.next().await.unwrap().into_result().unwrap().data, - value! ({ - "value": 100 - }) - ); - } -} - -#[tokio::test] -pub async fn test_extension_call_order() { - struct MyExtensionImpl { - calls: Arc>>, - } - - #[async_trait::async_trait] - #[allow(unused_variables)] - impl Extension for MyExtensionImpl { - async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response { - self.calls.lock().await.push("request_start"); - let res = next.run(ctx).await; - self.calls.lock().await.push("request_end"); - res - } - - fn subscribe<'s>( - &self, - ctx: &ExtensionContext<'_>, - mut stream: BoxStream<'s, Response>, - next: NextSubscribe<'_>, - ) -> BoxStream<'s, Response> { - let calls = self.calls.clone(); - next.run( - ctx, - Box::pin(async_stream::stream! { - calls.lock().await.push("subscribe_start"); - while let Some(item) = stream.next().await { - yield item; - } - calls.lock().await.push("subscribe_end"); - }), - ) - } - - async fn prepare_request( - &self, - ctx: &ExtensionContext<'_>, - request: Request, - next: NextPrepareRequest<'_>, - ) -> ServerResult { - self.calls.lock().await.push("prepare_request_start"); - let res = next.run(ctx, request).await; - self.calls.lock().await.push("prepare_request_end"); - res - } - - async fn parse_query( - &self, - ctx: &ExtensionContext<'_>, - query: &str, - variables: &Variables, - next: NextParseQuery<'_>, - ) -> ServerResult { - self.calls.lock().await.push("parse_query_start"); - let res = next.run(ctx, query, variables).await; - self.calls.lock().await.push("parse_query_end"); - res - } - - async fn validation( - &self, - ctx: &ExtensionContext<'_>, - next: NextValidation<'_>, - ) -> Result> { - self.calls.lock().await.push("validation_start"); - let res = next.run(ctx).await; - self.calls.lock().await.push("validation_end"); - res - } - - async fn execute( - &self, - ctx: &ExtensionContext<'_>, - operation_name: Option<&str>, - next: NextExecute<'_>, - ) -> Response { - assert_eq!(operation_name, Some("Abc")); - self.calls.lock().await.push("execute_start"); - let res = next.run(ctx, operation_name).await; - self.calls.lock().await.push("execute_end"); - res - } - - async fn resolve( - &self, - ctx: &ExtensionContext<'_>, - info: ResolveInfo<'_>, - next: NextResolve<'_>, - ) -> ServerResult> { - self.calls.lock().await.push("resolve_start"); - let res = next.run(ctx, info).await; - self.calls.lock().await.push("resolve_end"); - res - } - } - - struct MyExtension { - calls: Arc>>, - } - - impl ExtensionFactory for MyExtension { - fn create(&self) -> Arc { - Arc::new(MyExtensionImpl { - calls: self.calls.clone(), - }) - } - } - - struct Query; - - #[Object] - impl Query { - async fn value1(&self) -> i32 { - 10 - } - - async fn value2(&self) -> i32 { - 10 - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn value(&self) -> impl Stream { - futures_util::stream::iter(vec![1, 2, 3]) - } - } - - { - let calls: Arc>> = Default::default(); - let schema = Schema::build(Query, EmptyMutation, Subscription) - .extension(MyExtension { - calls: calls.clone(), - }) - .finish(); - let _ = schema - .execute("query Abc { value1 value2 }") - .await - .into_result() - .unwrap(); - let calls = calls.lock().await; - assert_eq!( - &*calls, - &vec![ - "request_start", - "prepare_request_start", - "prepare_request_end", - "parse_query_start", - "parse_query_end", - "validation_start", - "validation_end", - "execute_start", - "resolve_start", - "resolve_end", - "resolve_start", - "resolve_end", - "execute_end", - "request_end", - ] - ); - } - - { - let calls: Arc>> = Default::default(); - let schema = Schema::build(Query, EmptyMutation, Subscription) - .extension(MyExtension { - calls: calls.clone(), - }) - .finish(); - let mut stream = schema.execute_stream("subscription Abc { value }"); - while stream.next().await.is_some() {} - let calls = calls.lock().await; - assert_eq!( - &*calls, - &vec![ - "subscribe_start", - "prepare_request_start", - "prepare_request_end", - "parse_query_start", - "parse_query_end", - "validation_start", - "validation_end", - // push 1 - "execute_start", - "resolve_start", - "resolve_end", - "execute_end", - // push 2 - "execute_start", - "resolve_start", - "resolve_end", - "execute_end", - // push 3 - "execute_start", - "resolve_start", - "resolve_end", - "execute_end", - // end - "subscribe_end", - ] - ); - } -} - -#[tokio::test] -pub async fn query_execute_with_data() { - struct MyExtensionImpl(T); - - #[async_trait::async_trait] - impl Extension for MyExtensionImpl - where - T: Copy + Sync + Send + 'static, - { - async fn execute( - &self, - ctx: &ExtensionContext<'_>, - operation_name: Option<&str>, - next: NextExecute<'_>, - ) -> Response { - let mut data = Data::default(); - data.insert(self.0); - next.run_with_data(ctx, operation_name, data).await - } - } - - struct Query; - - #[Object] - impl Query { - async fn value(&self, ctx: &Context<'_>) -> Result { - Ok(*ctx.data::()? as i64 + ctx.data::()?) - } - } - - struct MyExtension(T); - - impl ExtensionFactory for MyExtension - where - T: Copy + Sync + Send + 'static, - { - fn create(&self) -> Arc { - Arc::new(MyExtensionImpl(self.0)) - } - } - - let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .extension(MyExtension(100i32)) - .extension(MyExtension(200i64)) - .finish(); - let query = "{ value }"; - assert_eq!( - schema.execute(query).await.data, - value!({ - "value": 300 - }) - ); -} - -#[tokio::test] -pub async fn subscription_execute_with_data() { - type Logs = Arc>>; - - struct MyExtensionImpl { - counter: Arc, - } - - impl MyExtensionImpl { - async fn append_log(&self, ctx: &ExtensionContext<'_>, element: LogElement) { - ctx.data::().unwrap().lock().await.push(element); - } - } - - #[async_trait::async_trait] - impl Extension for MyExtensionImpl { - async fn execute( - &self, - ctx: &ExtensionContext<'_>, - operation_name: Option<&str>, - next: NextExecute<'_>, - ) -> Response { - let mut data = Data::default(); - - let current_counter = self.counter.fetch_add(1, Ordering::SeqCst); - data.insert(current_counter); - self.append_log(ctx, LogElement::PreHook(current_counter)) - .await; - let resp = next.run_with_data(ctx, operation_name, data).await; - self.append_log(ctx, LogElement::PostHook(current_counter)) - .await; - resp - } - } - - struct MyExtension { - counter: Arc, - } - - impl ExtensionFactory for MyExtension { - fn create(&self) -> Arc { - Arc::new(MyExtensionImpl { - counter: self.counter.clone(), - }) - } - } - - #[derive(Debug, Eq, PartialEq)] - enum LogElement { - PreHook(i32), - OuterAccess(i32), - InnerAccess(i32), - PostHook(i32), - } - - let logs = Logs::default(); - let message_counter = Arc::new(AtomicI32::new(0)); - - #[derive(Clone, Copy)] - struct Inner(i32); - - #[Object] - impl Inner { - async fn value(&self, ctx: &Context<'_>) -> i32 { - if let Some(logs) = ctx.data_opt::() { - logs.lock().await.push(LogElement::InnerAccess(self.0)); - } - self.0 - } - } - - #[derive(Clone, Copy)] - struct Outer(Inner); - - #[Object] - impl Outer { - async fn inner(&self, ctx: &Context<'_>) -> Inner { - if let Some(logs) = ctx.data_opt::() { - logs.lock().await.push(LogElement::OuterAccess(self.0.0)); - } - self.0 - } - } - - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i64 { - 0 - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn outers(&self) -> impl Stream { - futures_util::stream::iter(10..13).map(Inner).map(Outer) - } - } - - let schema: Schema = - Schema::build(Query, EmptyMutation, Subscription) - .data(logs.clone()) - .extension(MyExtension { - counter: message_counter.clone(), - }) - .finish(); - let mut stream = schema.execute_stream("subscription { outers { inner { value } } }"); - - for i in 10i32..13 { - assert_eq!( - Response::new(value!({ - "outers": { - "inner": { - "value": i - } - } - })), - stream.next().await.unwrap() - ); - } - - { - let logs = logs.lock().await; - assert_eq!( - *logs, - vec![ - LogElement::PreHook(0), - LogElement::OuterAccess(10), - LogElement::InnerAccess(10), - LogElement::PostHook(0), - LogElement::PreHook(1), - LogElement::OuterAccess(11), - LogElement::InnerAccess(11), - LogElement::PostHook(1), - LogElement::PreHook(2), - LogElement::OuterAccess(12), - LogElement::InnerAccess(12), - LogElement::PostHook(2), - ], - "Log mismatch" - ); - } -} diff --git a/tests/external_descriptions/desc1.md b/tests/external_descriptions/desc1.md deleted file mode 100644 index d7c01bd45..000000000 --- a/tests/external_descriptions/desc1.md +++ /dev/null @@ -1,3 +0,0 @@ -extern 1 - -extern 2 \ No newline at end of file diff --git a/tests/external_descriptions/desc2.md b/tests/external_descriptions/desc2.md deleted file mode 100644 index 5446323fa..000000000 --- a/tests/external_descriptions/desc2.md +++ /dev/null @@ -1 +0,0 @@ -external diff --git a/tests/federation.rs b/tests/federation.rs deleted file mode 100644 index a76d696a2..000000000 --- a/tests/federation.rs +++ /dev/null @@ -1,1184 +0,0 @@ -#![allow(unreachable_code)] -#![allow(dead_code)] -#![allow(clippy::diverging_sub_expression)] - -use std::{collections::HashMap, convert::Infallible}; - -use async_graphql::{ - dataloader::{DataLoader, Loader}, - *, -}; - -#[tokio::test] -pub async fn test_nested_key() { - #[derive(InputObject)] - struct MyInputA { - a: i32, - b: i32, - c: MyInputB, - } - - #[derive(InputObject)] - struct MyInputB { - v: i32, - } - - assert_eq!(MyInputB::federation_fields().as_deref(), Some("{ v }")); - assert_eq!( - MyInputA::federation_fields().as_deref(), - Some("{ a b c { v } }") - ); - - struct Query; - - #[derive(SimpleObject)] - struct MyObj { - a: i32, - b: i32, - c: i32, - } - - #[Object] - impl Query { - #[graphql(entity)] - async fn find_obj(&self, input: MyInputA) -> MyObj { - MyObj { - a: input.a, - b: input.b, - c: input.c.v, - } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = r#"{ - _entities(representations: [{__typename: "MyObj", input: {a: 1, b: 2, c: { v: 3 }}}]) { - __typename - ... on MyObj { - a b c - } - } - }"#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "_entities": [ - {"__typename": "MyObj", "a": 1, "b": 2, "c": 3}, - ] - }) - ); -} - -#[tokio::test] -pub async fn test_federation() { - struct User { - id: ID, - } - - #[Object(extends)] - impl User { - #[graphql(external)] - async fn id(&self) -> &ID { - &self.id - } - - async fn reviews(&self) -> Vec { - todo!() - } - } - - struct Review; - - #[Object] - impl Review { - async fn body(&self) -> String { - todo!() - } - - async fn author(&self) -> User { - todo!() - } - - async fn product(&self) -> Product { - todo!() - } - } - - struct Product { - upc: String, - } - - #[Object(extends)] - impl Product { - #[graphql(external)] - async fn upc(&self) -> &str { - &self.upc - } - - async fn reviews(&self) -> Vec { - todo!() - } - } - - struct Query; - - #[Object] - impl Query { - #[graphql(entity)] - async fn find_user_by_id(&self, id: ID) -> User { - User { id } - } - - #[graphql(entity)] - async fn find_product_by_upc(&self, upc: String) -> Product { - Product { upc } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = r#"{ - _entities(representations: [{__typename: "Product", upc: "B00005N5PF"}]) { - __typename - ... on Product { - upc - } - } - }"#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "_entities": [ - {"__typename": "Product", "upc": "B00005N5PF"}, - ] - }) - ); -} - -#[tokio::test] -pub async fn test_find_entity_with_context() { - struct MyLoader; - - #[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] - impl Loader for MyLoader { - type Value = MyObj; - type Error = Infallible; - - async fn load(&self, keys: &[ID]) -> Result, Self::Error> { - Ok(keys - .iter() - .filter(|id| id.as_str() != "999") - .map(|id| { - ( - id.clone(), - MyObj { - id: id.clone(), - value: 999, - }, - ) - }) - .collect()) - } - } - - #[derive(Clone, SimpleObject)] - struct MyObj { - id: ID, - value: i32, - } - - struct Query; - - #[Object] - impl Query { - #[graphql(entity)] - async fn find_user_by_id(&self, ctx: &Context<'_>, id: ID) -> FieldResult { - let loader = ctx.data_unchecked::>(); - loader - .load_one(id) - .await - .unwrap() - .ok_or_else(|| "Not found".into()) - } - } - - let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .data(DataLoader::new(MyLoader, tokio::spawn)) - .finish(); - let query = r#"{ - _entities(representations: [ - {__typename: "MyObj", id: "1"}, - {__typename: "MyObj", id: "2"}, - {__typename: "MyObj", id: "3"}, - {__typename: "MyObj", id: "4"} - ]) { - __typename - ... on MyObj { - id - value - } - } - }"#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "_entities": [ - {"__typename": "MyObj", "id": "1", "value": 999 }, - {"__typename": "MyObj", "id": "2", "value": 999 }, - {"__typename": "MyObj", "id": "3", "value": 999 }, - {"__typename": "MyObj", "id": "4", "value": 999 }, - ] - }) - ); - - let query = r#"{ - _entities(representations: [ - {__typename: "MyObj", id: "999"} - ]) { - __typename - ... on MyObj { - id - value - } - } - }"#; - assert_eq!( - schema.execute(query).await.into_result().unwrap_err(), - vec![ServerError { - message: "Not found".to_string(), - source: None, - locations: vec![Pos { - line: 2, - column: 13 - }], - path: vec![PathSegment::Field("_entities".to_owned())], - extensions: None, - }] - ); -} - -#[tokio::test] -pub async fn test_entity_union() { - #[derive(SimpleObject)] - struct MyObj { - a: i32, - } - - struct Query; - - #[Object] - impl Query { - #[graphql(entity)] - async fn find_obj(&self, _id: i32) -> MyObj { - todo!() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = r#"{ - __type(name: "_Entity") { possibleTypes { name } } - }"#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "__type": { - "possibleTypes": [ - {"name": "MyObj"}, - ] - } - }) - ); -} - -#[tokio::test] -pub async fn test_entity_shareable() { - #[derive(SimpleObject)] - struct MyObjFieldShareable { - #[graphql(shareable)] - field_shareable_a: i32, - } - - #[derive(SimpleObject)] - #[graphql(shareable)] - struct MyObjShareable { - a: i32, - } - - struct Query; - - #[Object(extends)] - impl Query { - #[graphql(entity)] - async fn find_obj_field_shareable(&self, _id: i32) -> MyObjFieldShareable { - todo!() - } - #[graphql(entity)] - async fn find_obj_shareable(&self, _id: i32) -> MyObjShareable { - todo!() - } - } - - let schema_sdl = Schema::new(Query, EmptyMutation, EmptySubscription) - .sdl_with_options(SDLExportOptions::new().federation()); - assert!(schema_sdl.contains("fieldShareableA: Int! @shareable"),); - - assert!(schema_sdl.contains(r#"MyObjShareable @key(fields: "id") @shareable"#),); -} - -#[tokio::test] -pub async fn test_field_override_directive() { - #[derive(SimpleObject)] - struct MyObjFieldOverride { - #[graphql(override_from = "AnotherSubgraph")] - field_override_a: i32, - } - - struct Query; - - #[Object(extends)] - impl Query { - #[graphql(entity)] - async fn find_obj_field_override(&self, _id: i32) -> MyObjFieldOverride { - todo!() - } - } - - let schema_sdl = Schema::new(Query, EmptyMutation, EmptySubscription) - .sdl_with_options(SDLExportOptions::new().federation()); - assert!(schema_sdl.contains("fieldOverrideA: Int! @override(from: \"AnotherSubgraph\")"),); -} - -#[tokio::test] -pub async fn test_entity_inaccessible() { - struct MyCustomObjInaccessible; - - #[Object(inaccessible)] - impl MyCustomObjInaccessible { - async fn a(&self) -> i32 { - todo!() - } - - #[graphql(inaccessible)] - async fn custom_object_inaccessible(&self) -> i32 { - todo!() - } - } - - #[derive(SimpleObject)] - struct MyObjFieldInaccessible { - #[graphql(inaccessible)] - obj_field_inaccessible_a: i32, - } - - #[derive(SimpleObject)] - #[graphql(inaccessible)] - struct MyObjInaccessible { - a: i32, - } - - #[derive(InputObject)] - struct MyInputObjFieldInaccessible { - #[graphql(inaccessible)] - input_field_inaccessible_a: i32, - } - - #[derive(InputObject)] - #[graphql(inaccessible)] - struct MyInputObjInaccessible { - a: i32, - } - - #[derive(Enum, PartialEq, Eq, Copy, Clone)] - enum MyEnumVariantInaccessible { - #[graphql(inaccessible)] - OptionAInaccessible, - OptionB, - OptionC, - } - - #[derive(Enum, PartialEq, Eq, Copy, Clone)] - #[graphql(inaccessible)] - enum MyEnumInaccessible { - OptionA, - OptionB, - OptionC, - } - - #[derive(SimpleObject)] - struct MyInterfaceObjA { - inaccessible_interface_value: String, - } - - #[derive(SimpleObject)] - #[graphql(inaccessible)] - struct MyInterfaceObjB { - inaccessible_interface_value: String, - } - - #[derive(Interface)] - #[graphql(field(name = "inaccessible_interface_value", ty = "String", inaccessible))] - #[graphql(inaccessible)] - enum MyInterfaceInaccessible { - MyInterfaceObjA(MyInterfaceObjA), - MyInterfaceObjB(MyInterfaceObjB), - } - - #[derive(Union)] - #[graphql(inaccessible)] - enum MyUnionInaccessible { - MyInterfaceObjA(MyInterfaceObjA), - MyInterfaceObjB(MyInterfaceObjB), - } - - struct MyNumberInaccessible(i32); - - #[Scalar(inaccessible)] - impl ScalarType for MyNumberInaccessible { - fn parse(_value: Value) -> InputValueResult { - todo!() - } - - fn to_value(&self) -> Value { - todo!() - } - } - - struct Query; - - #[Object(extends)] - impl Query { - #[graphql(entity)] - async fn find_obj_field_inaccessible(&self, _id: i32) -> MyObjFieldInaccessible { - todo!() - } - - #[graphql(entity)] - async fn find_obj_inaccessible(&self, _id: i32) -> MyObjInaccessible { - todo!() - } - - async fn enum_variant_inaccessible(&self, _id: i32) -> MyEnumVariantInaccessible { - todo!() - } - - async fn enum_inaccessible(&self, _id: i32) -> MyEnumInaccessible { - todo!() - } - - #[graphql(inaccessible)] - async fn inaccessible_field(&self, _id: i32) -> i32 { - todo!() - } - - async fn inaccessible_argument(&self, #[graphql(inaccessible)] _id: i32) -> i32 { - todo!() - } - - async fn inaccessible_interface(&self) -> MyInterfaceInaccessible { - todo!() - } - - async fn inaccessible_union(&self) -> MyUnionInaccessible { - todo!() - } - - async fn inaccessible_scalar(&self) -> MyNumberInaccessible { - todo!() - } - - async fn inaccessible_input_field(&self, _value: MyInputObjFieldInaccessible) -> i32 { - todo!() - } - - async fn inaccessible_input(&self, _value: MyInputObjInaccessible) -> i32 { - todo!() - } - - async fn inaccessible_custom_object(&self) -> MyCustomObjInaccessible { - todo!() - } - } - - let schema_sdl = Schema::new(Query, EmptyMutation, EmptySubscription) - .sdl_with_options(SDLExportOptions::new().federation()); - - // FIELD_DEFINITION - assert!(schema_sdl.contains("inaccessibleField(id: Int!): Int! @inaccessible")); - assert!(schema_sdl.contains("objFieldInaccessibleA: Int! @inaccessible")); - assert!(schema_sdl.contains("inaccessibleInterfaceValue: String! @inaccessible")); - assert!(schema_sdl.contains("customObjectInaccessible: Int! @inaccessible")); - // INTERFACE - assert!(schema_sdl.contains("interface MyInterfaceInaccessible @inaccessible")); - // OBJECT - assert!(schema_sdl.contains("type MyCustomObjInaccessible @inaccessible")); - assert!(schema_sdl.contains(r#"type MyObjInaccessible @key(fields: "id") @inaccessible"#)); - assert!( - schema_sdl - .contains("type MyInterfaceObjB implements MyInterfaceInaccessible @inaccessible") - ); - // UNION - assert!(schema_sdl.contains("union MyUnionInaccessible @inaccessible =")); - // ARGUMENT_DEFINITION - assert!(schema_sdl.contains("inaccessibleArgument(id: Int! @inaccessible): Int!")); - // SCALAR - assert!(schema_sdl.contains("scalar MyNumberInaccessible @inaccessible")); - // ENUM - assert!(schema_sdl.contains("enum MyEnumInaccessible @inaccessible")); - // ENUM_VALUE - assert!(schema_sdl.contains("OPTION_A_INACCESSIBLE @inaccessible")); - // INPUT_OBJECT - assert!(schema_sdl.contains("input MyInputObjInaccessible @inaccessible")); - // INPUT_FIELD_DEFINITION - assert!(schema_sdl.contains("inputFieldInaccessibleA: Int! @inaccessible")); - // no trailing spaces - assert!(!schema_sdl.contains(" \n")); - - // compare to expected schema - let path = std::path::Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()) - .join("tests/schemas/test_entity_inaccessible.schema.graphql"); - let expected_schema = std::fs::read_to_string(&path).unwrap(); - if schema_sdl != expected_schema { - std::fs::write(path, schema_sdl).unwrap(); - panic!("schema was not up-to-date. rerun") - } -} - -#[tokio::test] -pub async fn test_link_directive() { - struct User { - id: ID, - } - - #[Object(extends)] - impl User { - #[graphql(external)] - async fn id(&self) -> &ID { - &self.id - } - - async fn reviews(&self) -> Vec { - todo!() - } - } - - struct Review; - - #[Object] - impl Review { - async fn body(&self) -> String { - todo!() - } - - async fn author(&self) -> User { - todo!() - } - - async fn product(&self) -> Product { - todo!() - } - } - - struct Product { - upc: String, - } - - #[Object(extends)] - impl Product { - #[graphql(external)] - async fn upc(&self) -> &str { - &self.upc - } - - async fn reviews(&self) -> Vec { - todo!() - } - } - - struct Query; - - #[Object] - impl Query { - #[graphql(entity)] - async fn find_user_by_id(&self, id: ID) -> User { - User { id } - } - - #[graphql(entity)] - async fn find_product_by_upc(&self, upc: String) -> Product { - Product { upc } - } - } - - let schema_sdl = Schema::build(Query, EmptyMutation, EmptySubscription) - .finish() - .sdl_with_options(SDLExportOptions::new().federation()); - - let path = std::path::Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()) - .join("tests/schemas/test_fed2_link.schema.graphqls"); - let expected_schema = std::fs::read_to_string(&path).unwrap(); - if schema_sdl != expected_schema { - std::fs::write(path, schema_sdl).unwrap(); - panic!("schema was not up-to-date. verify changes and re-run if correct.") - } -} - -#[tokio::test] -pub async fn test_entity_tag() { - struct MyCustomObjTagged; - - #[Object( - tag = "tagged", - tag = "object", - tag = "with", - tag = "multiple", - tag = "tags" - )] - impl MyCustomObjTagged { - async fn a(&self) -> i32 { - todo!() - } - - #[graphql(tag = "tagged_custom_object_field")] - async fn custom_object_tagged(&self) -> i32 { - todo!() - } - } - - #[derive(SimpleObject)] - struct MyObjFieldTagged { - #[graphql(tag = "tagged_field")] - obj_field_tagged_a: i32, - } - - #[derive(SimpleObject)] - #[graphql(tag = "tagged_simple_object")] - struct MyObjTagged { - a: i32, - } - - #[derive(InputObject)] - struct MyInputObjFieldTagged { - #[graphql(tag = "tagged_input_object_field")] - input_field_tagged_a: i32, - } - - #[derive(InputObject)] - #[graphql(tag = "input_object_tag")] - struct MyInputObjTagged { - a: i32, - } - - #[derive(Enum, PartialEq, Eq, Copy, Clone)] - enum MyEnumVariantTagged { - #[graphql(tag = "tagged_enum_option")] - OptionATagged, - OptionB, - OptionC, - } - - #[derive(Enum, PartialEq, Eq, Copy, Clone)] - #[graphql(tag = "tagged_num")] - enum MyEnumTagged { - OptionA, - OptionB, - OptionC, - } - - #[derive(SimpleObject)] - struct MyInterfaceObjA { - tagged_interface_value: String, - } - - #[derive(SimpleObject)] - #[graphql(tag = "interface_object")] - struct MyInterfaceObjB { - tagged_interface_value: String, - } - - #[derive(Interface)] - #[graphql(field( - name = "tagged_interface_value", - ty = "String", - tag = "tagged_interface_field" - ))] - #[graphql(tag = "tagged_interface")] - enum MyInterfaceTagged { - MyInterfaceObjA(MyInterfaceObjA), - MyInterfaceObjB(MyInterfaceObjB), - } - - #[derive(Union)] - #[graphql(tag = "tagged_union")] - enum MyUnionTagged { - MyInterfaceObjA(MyInterfaceObjA), - MyInterfaceObjB(MyInterfaceObjB), - } - - struct MyNumberTagged(i32); - - #[Scalar(tag = "tagged_scalar")] - impl ScalarType for MyNumberTagged { - fn parse(_value: Value) -> InputValueResult { - todo!() - } - - fn to_value(&self) -> Value { - todo!() - } - } - - struct Query; - - #[Object(extends)] - impl Query { - #[graphql(entity)] - async fn find_obj_field_tagged(&self, _id: i32) -> MyObjFieldTagged { - todo!() - } - - #[graphql(entity)] - async fn find_obj_tagged(&self, _id: i32) -> MyObjTagged { - todo!() - } - - async fn enum_variant_tagged(&self, _id: i32) -> MyEnumVariantTagged { - todo!() - } - - async fn enum_tagged(&self, _id: i32) -> MyEnumTagged { - todo!() - } - - #[graphql(tag = "tagged_\"field\"")] - async fn tagged_field(&self, _id: i32) -> i32 { - todo!() - } - - async fn tagged_argument(&self, #[graphql(tag = "tagged_argument")] _id: i32) -> i32 { - todo!() - } - - async fn tagged_interface(&self) -> MyInterfaceTagged { - todo!() - } - - async fn tagged_union(&self) -> MyUnionTagged { - todo!() - } - - async fn tagged_scalar(&self) -> MyNumberTagged { - todo!() - } - - async fn tagged_input_field(&self, _value: MyInputObjFieldTagged) -> i32 { - todo!() - } - - async fn tagged_input(&self, _value: MyInputObjTagged) -> i32 { - todo!() - } - - async fn tagged_custom_object(&self) -> MyCustomObjTagged { - todo!() - } - } - - let schema_sdl = Schema::new(Query, EmptyMutation, EmptySubscription) - .sdl_with_options(SDLExportOptions::new().federation()); - - // FIELD_DEFINITION - assert!(schema_sdl.contains(r#"taggedField(id: Int!): Int! @tag(name: "tagged_\"field\"")"#)); - assert!(schema_sdl.contains(r#"objFieldTaggedA: Int! @tag(name: "tagged_field")"#)); - assert!( - schema_sdl - .contains(r#"taggedInterfaceValue: String! @tag(name: "tagged_interface_field")"#) - ); - assert!( - schema_sdl.contains(r#"customObjectTagged: Int! @tag(name: "tagged_custom_object_field")"#) - ); - // INTERFACE - assert!(schema_sdl.contains(r#"interface MyInterfaceTagged @tag(name: "tagged_interface")"#)); - // OBJECT - assert!(schema_sdl.contains(r#"type MyCustomObjTagged @tag(name: "tagged") @tag(name: "object") @tag(name: "with") @tag(name: "multiple") @tag(name: "tags") {"#)); - assert!( - schema_sdl.contains( - r#"type MyObjTagged @key(fields: "id") @tag(name: "tagged_simple_object") {"# - ) - ); - assert!(schema_sdl.contains( - r#"type MyInterfaceObjB implements MyInterfaceTagged @tag(name: "interface_object")"# - )); - // UNION - assert!(schema_sdl.contains(r#"union MyUnionTagged @tag(name: "tagged_union") ="#)); - // ARGUMENT_DEFINITION - assert!(schema_sdl.contains(r#"taggedArgument(id: Int! @tag(name: "tagged_argument")): Int!"#)); - // SCALAR - assert!(schema_sdl.contains(r#"scalar MyNumberTagged @tag(name: "tagged_scalar")"#)); - // ENUM - assert!(schema_sdl.contains(r#"enum MyEnumTagged @tag(name: "tagged_num")"#)); - // ENUM_VALUE - assert!(schema_sdl.contains(r#"OPTION_A_TAGGED @tag(name: "tagged_enum_option")"#)); - // INPUT_OBJECT - assert!(schema_sdl.contains(r#"input MyInputObjTagged @tag(name: "input_object_tag")"#)); - // INPUT_FIELD_DEFINITION - assert!( - schema_sdl.contains(r#"inputFieldTaggedA: Int! @tag(name: "tagged_input_object_field")"#) - ); - // no trailing spaces - assert!(!schema_sdl.contains(" \n")); - - // compare to expected schema - let path = std::path::Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()) - .join("tests/schemas/test_entity_tag.schema.graphql"); - let expected_schema = std::fs::read_to_string(&path).unwrap(); - if schema_sdl != expected_schema { - std::fs::write(path, schema_sdl).unwrap(); - panic!("schema was not up-to-date. rerun") - } -} - -#[tokio::test] -pub async fn test_entity_requires_scopes() { - struct MyCustomObjRequiresScoped; - - #[Object( - requires_scopes = "requires scopes with", - requires_scopes = "multiple scopes" - )] - impl MyCustomObjRequiresScoped { - async fn a(&self) -> i32 { - todo!() - } - - #[graphql(requires_scopes = "custom:field")] - async fn custom_object_requires_scoped(&self) -> i32 { - todo!() - } - } - - #[derive(SimpleObject)] - struct MyObjFieldRequiresScoped { - #[graphql( - requires_scopes = "fielda:scoped read:all", - requires_scopes = "read:others" - )] - obj_field_requires_scoped_a: i32, - } - - #[derive(SimpleObject)] - #[graphql(requires_scopes = "myobj:read")] - struct MyObjRequiresScoped { - a: i32, - } - - #[derive(Enum, PartialEq, Eq, Copy, Clone)] - #[graphql(requires_scopes = "enum:read")] - enum MyEnumRequiresScoped { - OptionA, - OptionB, - OptionC, - } - - #[derive(SimpleObject)] - struct MyInterfaceObjA { - requires_scoped_interface_value: String, - } - - #[derive(SimpleObject)] - #[graphql(requires_scopes = "objB:read")] - struct MyInterfaceObjB { - requires_scoped_interface_value: String, - } - - #[derive(Interface)] - #[graphql(field( - name = "requires_scoped_interface_value", - ty = "String", - requires_scopes = "read:interface" - ))] - #[graphql(requires_scopes = "read:all")] - enum MyInterfaceRequiresScoped { - MyInterfaceObjA(MyInterfaceObjA), - MyInterfaceObjB(MyInterfaceObjB), - } - - struct MyNumberRequiresScoped(i32); - - #[Scalar(requires_scopes = "scalar:read")] - impl ScalarType for MyNumberRequiresScoped { - fn parse(_value: Value) -> InputValueResult { - todo!() - } - - fn to_value(&self) -> Value { - todo!() - } - } - - #[derive(SimpleObject)] - #[graphql(complex)] - struct MyComplexObjRequiresScoped { - complex_a: i32, - } - - #[ComplexObject] - impl MyComplexObjRequiresScoped { - #[graphql(requires_scopes = "read:complex")] - async fn complex_b(&self) -> i32 { - self.complex_a - } - } - - struct Query; - - #[Object(extends)] - impl Query { - #[graphql(requires_scopes = "scoped:field")] - async fn requires_scoped_field(&self, _id: i32) -> i32 { - todo!() - } - - #[graphql(entity)] - async fn find_obj_field_requires_scoped(&self, _id: i32) -> MyObjFieldRequiresScoped { - todo!() - } - - #[graphql(entity)] - async fn find_obj_requires_scoped(&self, _id: i32) -> MyObjRequiresScoped { - todo!() - } - - async fn requires_scoped_enum(&self, _id: i32) -> MyEnumRequiresScoped { - todo!() - } - - async fn requires_scoped_interface(&self) -> MyInterfaceRequiresScoped { - todo!() - } - - async fn requires_scoped_scalar(&self) -> MyNumberRequiresScoped { - todo!() - } - - async fn requires_scoped_custom_object(&self) -> MyCustomObjRequiresScoped { - todo!() - } - - async fn requires_scoped_complex_object(&self) -> MyComplexObjRequiresScoped { - MyComplexObjRequiresScoped { complex_a: 10 } - } - } - - let schema_sdl = Schema::new(Query, EmptyMutation, EmptySubscription) - .sdl_with_options(SDLExportOptions::new().federation()); - - // FIELD_DEFINITION - assert!(schema_sdl.contains( - r#"requiresScopedField(id: Int!): Int! @requiresScopes(scopes: [["scoped:field"]])"# - )); - assert!(schema_sdl.contains( - r#"objFieldRequiresScopedA: Int! @requiresScopes(scopes: [["fielda:scoped", "read:all"], ["read:others"]])"# - )); - assert!(schema_sdl.contains( - r#"requiresScopedInterfaceValue: String! @requiresScopes(scopes: [["read:interface"]])"# - )); - assert!(schema_sdl.contains( - r#"customObjectRequiresScoped: Int! @requiresScopes(scopes: [["custom:field"]])"# - )); - assert!(schema_sdl.contains(r#"complexB: Int! @requiresScopes(scopes: [["read:complex"]])"#)); - // INTERFACE - assert!(schema_sdl.contains( - r#"interface MyInterfaceRequiresScoped @requiresScopes(scopes: [["read:all"]])"# - )); - // OBJECT - assert!(schema_sdl - .contains(r#"type MyCustomObjRequiresScoped @requiresScopes(scopes: [["requires", "scopes", "with"], ["multiple", "scopes"]]) {"#)); - assert!(schema_sdl.contains( - r#"type MyObjRequiresScoped @key(fields: "id") @requiresScopes(scopes: [["myobj:read"]]) {"# - )); - assert!(schema_sdl.contains( - r#"type MyInterfaceObjB implements MyInterfaceRequiresScoped @requiresScopes(scopes: [["objB:read"]])"# - )); - // SCALAR - assert!( - schema_sdl.contains( - r#"scalar MyNumberRequiresScoped @requiresScopes(scopes: [["scalar:read"]])"# - ) - ); - // ENUM - assert!( - schema_sdl - .contains(r#"enum MyEnumRequiresScoped @requiresScopes(scopes: [["enum:read"]])"#) - ); - // no trailing spaces - assert!(!schema_sdl.contains(" \n")); -} - -#[tokio::test] -pub async fn test_interface_object() { - #[derive(SimpleObject)] - struct VariantA { - pub id: u64, - } - - #[derive(Interface)] - #[graphql(field(name = "id", ty = "&u64"))] - enum MyInterface { - VariantA(VariantA), - } - - #[derive(SimpleObject)] - #[graphql(interface_object)] - struct MyInterfaceObject1 { - pub id: u64, - } - - struct MyInterfaceObject2; - - #[Object(interface_object)] - impl MyInterfaceObject2 { - pub async fn id(&self) -> u64 { - todo!() - } - } - - struct Query; - - #[Object(extends)] - impl Query { - #[graphql(entity)] - async fn my_interface(&self, _id: u64) -> MyInterface { - todo!() - } - - #[graphql(entity)] - async fn my_interface_object1(&self, _id: u64) -> MyInterfaceObject1 { - todo!() - } - - #[graphql(entity)] - async fn my_interface_object2(&self, _id: u64) -> MyInterfaceObject2 { - todo!() - } - } - - let schema_sdl = Schema::new(Query, EmptyMutation, EmptySubscription) - .sdl_with_options(SDLExportOptions::new().federation()); - - // Interface with @key directive - assert!(schema_sdl.contains("interface MyInterface @key(fields: \"id\")")); - - // Object with @interfaceObject directive - assert!(schema_sdl.contains("type MyInterfaceObject1 @key(fields: \"id\") @interfaceObject")); - assert!(schema_sdl.contains("type MyInterfaceObject2 @key(fields: \"id\") @interfaceObject")); -} - -#[tokio::test] -pub async fn test_unresolvable_entity() { - #[derive(SimpleObject)] - struct ResolvableObject { - id: u64, - } - - #[derive(SimpleObject)] - #[graphql(unresolvable = "id")] - struct SimpleExplicitUnresolvable { - id: u64, - } - - #[derive(SimpleObject)] - #[graphql(unresolvable)] - struct SimpleImplicitUnresolvable { - a: u64, - #[graphql(skip)] - _skipped: bool, - } - - struct ExplicitUnresolvable; - - #[Object(unresolvable = "id1 id2")] - impl ExplicitUnresolvable { - async fn id1(&self) -> u64 { - todo!() - } - - async fn id2(&self) -> u64 { - todo!() - } - } - - struct ImplicitUnresolvable; - - #[Object(unresolvable)] - impl ImplicitUnresolvable { - async fn a(&self) -> &'static str { - todo!() - } - - async fn b(&self) -> bool { - todo!() - } - - #[graphql(skip)] - async fn _skipped(&self) {} - } - - struct Query; - - #[Object] - impl Query { - async fn simple_explicit_reference(&self, _id: u64) -> SimpleExplicitUnresolvable { - todo!() - } - - async fn simple_implicit_reference(&self, _a: u64) -> SimpleImplicitUnresolvable { - todo!() - } - - async fn explicit_reference(&self, _id1: u64, _id2: u64) -> ExplicitUnresolvable { - todo!() - } - - async fn implicit_unresolvable(&self, _a: String, _b: bool) -> ImplicitUnresolvable { - todo!() - } - - #[graphql(entity)] - async fn object_entity(&self, _id: u64) -> ResolvableObject { - todo!() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let schema_sdl = schema.sdl_with_options(SDLExportOptions::new().federation()); - - assert!(schema_sdl.contains(r#"type ResolvableObject @key(fields: "id")"#)); - assert!( - schema_sdl - .contains(r#"type SimpleExplicitUnresolvable @key(fields: "id", resolvable: false)"#) - ); - assert!( - schema_sdl - .contains(r#"type SimpleImplicitUnresolvable @key(fields: "a", resolvable: false)"#) - ); - assert!( - schema_sdl - .contains(r#"type ExplicitUnresolvable @key(fields: "id1 id2", resolvable: false)"#) - ); - assert!( - schema_sdl.contains(r#"type ImplicitUnresolvable @key(fields: "a b", resolvable: false)"#) - ); - - let query = r#"{ - __type(name: "_Entity") { possibleTypes { name } } - }"#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "__type": { - "possibleTypes": [ - {"name": "ResolvableObject"}, - ] - } - }) - ); -} diff --git a/tests/field_features.rs b/tests/field_features.rs deleted file mode 100644 index f518428c0..000000000 --- a/tests/field_features.rs +++ /dev/null @@ -1,163 +0,0 @@ -#![allow(dead_code)] -#![allow(unexpected_cfgs)] - -use async_graphql::*; -use futures_util::stream::{Stream, StreamExt}; - -#[tokio::test] -pub async fn test_field_features() { - #[derive(SimpleObject)] - struct MyObj { - value: i32, - #[cfg(feature = "bson")] - value_bson: i32, - #[cfg(feature = "abc")] - value_abc: i32, - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn values(&self) -> impl Stream { - futures_util::stream::once(async move { 10 }) - } - - #[cfg(feature = "bson")] - async fn values_bson(&self) -> impl Stream { - futures_util::stream::once(async move { 10 }) - } - - #[cfg(feature = "abc")] - async fn values_abc(&self) -> Pin + Send + 'static>> { - Box::pin(futures_util::stream::once(async move { 10 })) - } - } - - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - - #[cfg(feature = "bson")] - async fn value_bson(&self) -> i32 { - 10 - } - - #[cfg(feature = "abc")] - async fn value_abc(&self) -> i32 { - 10 - } - - async fn obj(&self) -> MyObj { - MyObj { - value: 10, - #[cfg(feature = "bson")] - value_bson: 10, - #[cfg(feature = "abc")] - value_abc: 10, - } - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - let query = "{ value }"; - assert_eq!( - schema.execute(query).await.data, - value!({ - "value": 10, - }) - ); - - let query = "{ valueBson }"; - assert_eq!( - schema.execute(query).await.data, - value!({ - "valueBson": 10, - }) - ); - - let query = "{ valueAbc }"; - assert_eq!( - schema.execute(query).await.into_result().unwrap_err(), - vec![ServerError { - message: r#"Unknown field "valueAbc" on type "Query". Did you mean "value"?"# - .to_owned(), - source: None, - locations: vec![Pos { column: 3, line: 1 }], - path: Vec::new(), - extensions: None, - }] - ); - - let query = "{ obj { value } }"; - assert_eq!( - schema.execute(query).await.data, - value!({ - "obj": { "value": 10 } - }) - ); - - let query = "{ obj { valueBson } }"; - assert_eq!( - schema.execute(query).await.data, - value!({ - "obj": { "valueBson": 10 } - }) - ); - - let query = "{ obj { valueAbc } }"; - assert_eq!( - schema.execute(query).await.into_result().unwrap_err(), - vec![ServerError { - message: r#"Unknown field "valueAbc" on type "MyObj". Did you mean "value"?"# - .to_owned(), - source: None, - locations: vec![Pos { column: 9, line: 1 }], - path: Vec::new(), - extensions: None, - }] - ); - - let mut stream = schema.execute_stream("subscription { values }"); - assert_eq!( - stream - .next() - .await - .map(|resp| resp.into_result().unwrap().data) - .unwrap(), - value!({ - "values": 10 - }) - ); - - let mut stream = schema.execute_stream("subscription { valuesBson }"); - assert_eq!( - stream.next().await.map(|resp| resp.data).unwrap(), - value!({ - "valuesBson": 10 - }) - ); - - assert_eq!( - schema - .execute_stream("subscription { valuesAbc }") - .next() - .await - .unwrap() - .errors, - vec![ServerError { - message: r#"Unknown field "valuesAbc" on type "Subscription". Did you mean "values", "valuesBson"?"#.to_owned(), - source: None, - locations: vec![Pos { - column: 16, - line: 1 - }], - path: Vec::new(), - extensions: None, - }] - ); -} diff --git a/tests/fields_merge.rs b/tests/fields_merge.rs deleted file mode 100644 index b29f1b6f0..000000000 --- a/tests/fields_merge.rs +++ /dev/null @@ -1,120 +0,0 @@ -use async_graphql::*; - -#[tokio::test] -pub async fn test_field_merge() { - struct Query; - - #[Object] - impl Query { - async fn value1(&self) -> i32 { - 1 - } - - async fn value2(&self) -> i32 { - 2 - } - - async fn value3(&self) -> i32 { - 3 - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = r#" - { - value1 - ... { value2 } - ... A - } - - fragment A on Query { - value3 - } - "#; - assert_eq!( - schema.execute(query).await.data, - value!({ - "value1": 1, - "value2": 2, - "value3": 3, - }) - ); -} - -#[tokio::test] -pub async fn test_field_object_merge() { - #[derive(SimpleObject)] - struct MyObject { - a: i32, - b: i32, - c: i32, - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self) -> MyObject { - MyObject { a: 1, b: 2, c: 3 } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = r#" - { - obj { a } - ... { obj { b } } - ... A - } - - fragment A on Query { - obj { c } - } - "#; - assert_eq!( - schema.execute(query).await.data, - value!({ - "obj": { - "a": 1, - "b": 2, - "c": 3, - } - }) - ); -} - -#[tokio::test] -pub async fn test_field_object_merge2() { - #[derive(SimpleObject)] - struct MyObject { - a: i32, - b: i32, - c: i32, - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self) -> Vec { - vec![MyObject { a: 1, b: 2, c: 3 }, MyObject { a: 4, b: 5, c: 6 }] - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = r#" - { - obj { a } - obj { b } - } - "#; - assert_eq!( - schema.execute(query).await.data, - value!({ - "obj": [ - { "a": 1, "b": 2 }, - { "a": 4, "b": 5 }, - ] - }) - ); -} diff --git a/tests/generic_types.rs b/tests/generic_types.rs deleted file mode 100644 index 54303a341..000000000 --- a/tests/generic_types.rs +++ /dev/null @@ -1,418 +0,0 @@ -use async_graphql::*; -use futures_util::stream::{Stream, StreamExt}; - -#[tokio::test] -pub async fn test_generic_object() { - struct MyObj { - value: T, - } - - #[Object(name = "MyObjI32")] - impl MyObj { - async fn value(&self) -> i32 { - self.value - } - } - - #[Object(name = "MyObjBool")] - impl MyObj { - async fn value(&self) -> bool { - self.value - } - } - - struct Query; - - #[Object] - impl Query { - async fn obj_i32(&self) -> MyObj { - MyObj { value: 100 } - } - - async fn obj_bool(&self) -> MyObj { - MyObj { value: true } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = r#"{ - objI32 { value } - objBool { value } - }"# - .to_owned(); - assert_eq!( - schema.execute(&query).await.into_result().unwrap().data, - value!({ - "objI32": {"value": 100}, - "objBool": {"value": true}, - }) - ); -} - -#[tokio::test] -pub async fn test_input_object_generic() { - #[derive(InputObject)] - #[graphql( - concrete(name = "IntEqualityFilter", params(i32)), - concrete(name = "StringEqualityFilter", params(String)) - )] - struct EqualityFilter { - equals: Option, - not_equals: Option, - } - - assert_eq!(EqualityFilter::::type_name(), "IntEqualityFilter"); - assert_eq!( - EqualityFilter::::type_name(), - "StringEqualityFilter" - ); - - struct Query; - - #[Object] - impl Query { - async fn q1(&self, input: EqualityFilter) -> i32 { - input.equals.unwrap_or_default() + input.not_equals.unwrap_or_default() - } - - async fn q2(&self, input: EqualityFilter) -> String { - input.equals.unwrap_or_default() + &input.not_equals.unwrap_or_default() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = r#"{ - q1(input: { equals: 7, notEquals: 8 } ) - q2(input: { equals: "ab", notEquals: "cd" } ) - }"#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "q1": 15, - "q2": "abcd", - }) - ); - - assert_eq!( - schema - .execute( - r#"{ __type(name: "IntEqualityFilter") { inputFields { name type { name } } } }"# - ) - .await - .into_result() - .unwrap() - .data, - value!({ - "__type": { - "inputFields": [ - {"name": "equals", "type": { "name": "Int" } }, - {"name": "notEquals", "type": { "name": "Int" } }, - ] - } - }) - ); - - assert_eq!( - schema - .execute(r#"{ __type(name: "Query") { fields { name args { name type { kind ofType { name } } } } } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ - "__type": { - "fields": [ - { - "name": "q1", - "args": [{ - "name": "input", - "type": { - "kind": "NON_NULL", - "ofType": { "name": "IntEqualityFilter" }, - }, - }] - }, - { - "name": "q2", - "args": [{ - "name": "input", - "type": { - "kind": "NON_NULL", - "ofType": { "name": "StringEqualityFilter" }, - }, - }], - } - ] - } - }) - ); -} - -#[tokio::test] -pub async fn test_generic_simple_object() { - #[derive(SimpleObject)] - #[graphql(concrete(name = "MyObjIntString", params(i32, String)))] - #[graphql(concrete(name = "MyObji64f32", params(i64, u8)))] - struct MyObj { - a: A, - b: B, - } - - struct Query; - - #[Object] - impl Query { - async fn q1(&self) -> MyObj { - MyObj { - a: 100, - b: "abc".to_string(), - } - } - - async fn q2(&self) -> MyObj { - MyObj { a: 100, b: 28 } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = "{ q1 { a b } q2 { a b } }"; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "q1": { - "a": 100, - "b": "abc", - }, - "q2": { - "a": 100, - "b": 28, - } - }) - ); - - assert_eq!( - schema - .execute(r#"{ __type(name: "MyObjIntString") { fields { name type { kind ofType { name } } } } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ - "__type": { - "fields": [ - { - "name": "a", - "type": { - "kind": "NON_NULL", - "ofType": { "name": "Int" }, - }, - }, - { - "name": "b", - "type": { - "kind": "NON_NULL", - "ofType": { "name": "String" }, - }, - }, - ] - } - }) - ); - - assert_eq!( - schema - .execute( - r#"{ __type(name: "Query") { fields { name type { kind ofType { name } } } } }"# - ) - .await - .into_result() - .unwrap() - .data, - value!({ - "__type": { - "fields": [ - { - "name": "q1", - "type": { - "kind": "NON_NULL", - "ofType": { "name": "MyObjIntString" }, - }, - }, - { - "name": "q2", - "type": { - "kind": "NON_NULL", - "ofType": { "name": "MyObji64f32" }, - }, - }, - ] - } - }) - ); -} - -#[tokio::test] -pub async fn test_generic_subscription() { - struct MySubscription { - values: Vec, - } - - #[Subscription] - impl MySubscription - where - T: Clone + Send + Sync + Unpin, - { - async fn values(&self) -> Result + '_> { - Ok(async_stream::stream! { - for value in self.values.iter().cloned() { - yield value - } - }) - } - } - - struct Query; - - #[Object] - impl Query { - async fn dummy(&self) -> bool { - false - } - } - - let schema = Schema::new(Query, EmptyMutation, MySubscription { values: vec![1, 2] }); - { - let mut stream = schema - .execute_stream("subscription { values }") - .map(|resp| resp.into_result().unwrap().data); - for i in 1..=2 { - assert_eq!(value!({ "values": i }), stream.next().await.unwrap()); - } - assert!(stream.next().await.is_none()); - } -} - -#[tokio::test] -pub async fn test_concrete_object() { - struct GbObject(A, B); - - #[Object( - concrete(name = "Obj_i32i64", params(i32, i64)), - concrete(name = "Obj_f32f64", params(f32, f64)) - )] - impl GbObject { - async fn a(&self) -> &A { - &self.0 - } - - async fn b(&self) -> &B { - &self.1 - } - } - - assert_eq!(GbObject::::type_name(), "Obj_i32i64"); - assert_eq!(GbObject::::type_name(), "Obj_f32f64"); - - struct Query; - - #[Object] - impl Query { - async fn a(&self) -> GbObject { - GbObject(10, 20) - } - - async fn b(&self) -> GbObject { - GbObject(88.0, 99.0) - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ a { __typename a b } b { __typename a b } }") - .await - .into_result() - .unwrap() - .data, - value!({ - "a": { - "__typename": "Obj_i32i64", - "a": 10, - "b": 20, - }, - "b": { - "__typename": "Obj_f32f64", - "a": 88.0, - "b": 99.0, - } - }) - ); -} - -#[tokio::test] -pub async fn test_concrete_object_with_lifetime() { - #[derive(SimpleObject)] - #[graphql(concrete(name = "Bar0", params(i32)))] - #[graphql(concrete(name = "Bar1", params(i64)))] - struct Foo<'a, T> - where - T: Sync + OutputType + 'a, - { - data: &'a T, - } - - struct Query { - value1: i32, - value2: i64, - } - - #[Object] - impl Query { - async fn a(&self) -> Foo<'_, i32> { - Foo { data: &self.value1 } - } - - async fn b(&self) -> Foo<'_, i64> { - Foo { data: &self.value2 } - } - - async fn static_a(&self) -> Foo<'static, i32> { - Foo { data: &100 } - } - - async fn static_b(&self) -> Foo<'static, i32> { - Foo { data: &200 } - } - } - - let schema = Schema::new( - Query { - value1: 88, - value2: 99, - }, - EmptyMutation, - EmptySubscription, - ); - - assert_eq!( - schema - .execute( - r#"{ - a { data } - b { data } - staticA { data } - staticB { data } - }"# - ) - .await - .into_result() - .unwrap() - .data, - value!({ - "a": { "data": 88 }, - "b": { "data": 99 }, - "staticA": { "data": 100 }, - "staticB": { "data": 200 }, - }) - ); -} diff --git a/tests/guard.rs b/tests/guard.rs deleted file mode 100644 index db3280dd4..000000000 --- a/tests/guard.rs +++ /dev/null @@ -1,628 +0,0 @@ -use async_graphql::*; -use futures_util::stream::{Stream, StreamExt}; - -#[derive(Eq, PartialEq, Copy, Clone)] -enum Role { - Admin, - Guest, -} - -pub struct RoleGuard { - role: Role, -} - -impl RoleGuard { - fn new(role: Role) -> Self { - Self { role } - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl Guard for RoleGuard { - async fn check(&self, ctx: &Context<'_>) -> Result<()> { - if ctx.data_opt::() == Some(&self.role) { - Ok(()) - } else { - Err("Forbidden".into()) - } - } -} - -struct Username(String); - -struct UserGuard<'a> { - username: &'a str, -} - -impl<'a> UserGuard<'a> { - fn new(username: &'a str) -> Self { - Self { username } - } -} - -#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] -impl Guard for UserGuard<'_> { - async fn check(&self, ctx: &Context<'_>) -> Result<()> { - if ctx.data_opt::().map(|name| name.0.as_str()) == Some(self.username) { - Ok(()) - } else { - Err("Forbidden".into()) - } - } -} - -#[tokio::test] -pub async fn test_guard_simple_rule() { - #[derive(SimpleObject)] - struct Query { - #[graphql(guard = "RoleGuard::new(Role::Admin)")] - value: i32, - } - - struct Subscription; - - #[Subscription] - impl Subscription { - #[graphql(guard = "RoleGuard::new(Role::Admin)")] - async fn values(&self) -> impl Stream { - futures_util::stream::iter(vec![1, 2, 3]) - } - } - - let schema = Schema::new(Query { value: 10 }, EmptyMutation, Subscription); - - let query = "{ value }"; - assert_eq!( - schema - .execute(Request::new(query).data(Role::Admin)) - .await - .data, - value!({"value": 10}) - ); - - let query = "{ value }"; - assert_eq!( - schema - .execute(Request::new(query).data(Role::Guest)) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "Forbidden".to_string(), - source: None, - locations: vec![Pos { line: 1, column: 3 }], - path: vec![PathSegment::Field("value".to_owned())], - extensions: None, - }] - ); - - assert_eq!( - schema - .execute_stream(Request::new("subscription { values }").data(Role::Admin)) - .map(|item| item.data) - .collect::>() - .await, - vec![ - value! ({"values": 1}), - value! ({"values": 2}), - value! ({"values": 3}) - ] - ); - - assert_eq!( - schema - .execute_stream(Request::new("subscription { values }").data(Role::Guest)) - .next() - .await - .unwrap() - .errors, - vec![ServerError { - message: "Forbidden".to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 16 - }], - path: vec![PathSegment::Field("values".to_owned())], - extensions: None, - }] - ); -} - -#[tokio::test] -pub async fn test_guard_and_operator() { - #[derive(SimpleObject)] - struct Query { - #[graphql(guard = r#"RoleGuard::new(Role::Admin).and(UserGuard::new("test"))"#)] - value: i32, - } - - let schema = Schema::new(Query { value: 10 }, EmptyMutation, EmptySubscription); - - let query = "{ value }"; - assert_eq!( - schema - .execute( - Request::new(query) - .data(Role::Admin) - .data(Username("test".to_string())) - ) - .await - .data, - value!({"value": 10}) - ); - - let query = "{ value }"; - assert_eq!( - schema - .execute( - Request::new(query) - .data(Role::Guest) - .data(Username("test".to_string())) - ) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "Forbidden".to_string(), - source: None, - locations: vec![Pos { line: 1, column: 3 }], - path: vec![PathSegment::Field("value".to_owned())], - extensions: None, - }] - ); - - let query = "{ value }"; - assert_eq!( - schema - .execute( - Request::new(query) - .data(Role::Admin) - .data(Username("test1".to_string())) - ) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "Forbidden".to_string(), - source: None, - locations: vec![Pos { line: 1, column: 3 }], - path: vec![PathSegment::Field("value".to_owned())], - extensions: None, - }] - ); - - let query = "{ value }"; - assert_eq!( - schema - .execute( - Request::new(query) - .data(Role::Guest) - .data(Username("test1".to_string())) - ) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "Forbidden".to_string(), - source: None, - locations: vec![Pos { line: 1, column: 3 }], - path: vec![PathSegment::Field("value".to_owned())], - extensions: None, - }] - ); -} - -#[tokio::test] -pub async fn test_guard_or_operator() { - #[derive(SimpleObject)] - struct Query { - #[graphql(guard = r#"RoleGuard::new(Role::Admin).or(UserGuard::new("test"))"#)] - value: i32, - } - - let schema = Schema::new(Query { value: 10 }, EmptyMutation, EmptySubscription); - - let query = "{ value }"; - assert_eq!( - schema - .execute( - Request::new(query) - .data(Role::Admin) - .data(Username("test".to_string())) - ) - .await - .data, - value!({"value": 10}) - ); - - let query = "{ value }"; - assert_eq!( - schema - .execute( - Request::new(query) - .data(Role::Guest) - .data(Username("test".to_string())) - ) - .await - .data, - value!({"value": 10}) - ); - - let query = "{ value }"; - assert_eq!( - schema - .execute( - Request::new(query) - .data(Role::Admin) - .data(Username("test1".to_string())) - ) - .await - .data, - value!({"value": 10}) - ); - - let query = "{ value }"; - assert_eq!( - schema - .execute( - Request::new(query) - .data(Role::Guest) - .data(Username("test1".to_string())) - ) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "Forbidden".to_string(), - source: None, - locations: vec![Pos { line: 1, column: 3 }], - path: vec![PathSegment::Field("value".to_owned())], - extensions: None, - }] - ); -} - -#[tokio::test] -pub async fn test_guard_use_params() { - struct EqGuard { - expect: i32, - actual: i32, - } - - impl EqGuard { - fn new(expect: i32, actual: i32) -> Self { - Self { expect, actual } - } - } - - #[cfg_attr(feature = "boxed-trait", async_trait::async_trait)] - impl Guard for EqGuard { - async fn check(&self, _ctx: &Context<'_>) -> Result<()> { - if self.expect != self.actual { - Err("Forbidden".into()) - } else { - Ok(()) - } - } - } - - struct Query; - - #[Object] - impl Query { - #[graphql(guard = "EqGuard::new(100, value)")] - async fn get(&self, value: i32) -> i32 { - value - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - assert_eq!( - schema - .execute(Request::new("{ get(value: 100) }")) - .await - .into_result() - .unwrap() - .data, - value!({"get": 100}) - ); - - assert_eq!( - schema - .execute(Request::new("{ get(value: 99) }")) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "Forbidden".to_string(), - source: None, - locations: vec![Pos { line: 1, column: 3 }], - path: vec![PathSegment::Field("get".to_owned())], - extensions: None, - }] - ); -} - -#[tokio::test] -pub async fn test_guard_on_simple_object() { - #[derive(SimpleObject)] - #[graphql(guard = "RoleGuard::new(Role::Admin)")] - struct Query { - value: i32, - } - - let schema = Schema::new(Query { value: 100 }, EmptyMutation, EmptySubscription); - - let query = "{ value }"; - assert_eq!( - schema - .execute(Request::new(query).data(Role::Admin)) - .await - .data, - value!({"value": 100}) - ); - - let query = "{ value }"; - assert_eq!( - schema - .execute(Request::new(query).data(Role::Guest)) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "Forbidden".to_string(), - source: None, - locations: vec![Pos { line: 1, column: 3 }], - path: vec![PathSegment::Field("value".to_owned())], - extensions: None, - }] - ); -} - -#[tokio::test] -pub async fn test_guard_on_simple_object_field() { - #[derive(SimpleObject)] - #[graphql] - struct Query { - #[graphql(guard = "RoleGuard::new(Role::Admin)")] - value: i32, - } - - let schema = Schema::new(Query { value: 100 }, EmptyMutation, EmptySubscription); - - let query = "{ value }"; - assert_eq!( - schema - .execute(Request::new(query).data(Role::Admin)) - .await - .data, - value!({"value": 100}) - ); - - let query = "{ value }"; - assert_eq!( - schema - .execute(Request::new(query).data(Role::Guest)) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "Forbidden".to_string(), - source: None, - locations: vec![Pos { line: 1, column: 3 }], - path: vec![PathSegment::Field("value".to_owned())], - extensions: None, - }] - ); -} - -#[tokio::test] -pub async fn test_guard_on_object() { - struct Query; - - #[Object(guard = "RoleGuard::new(Role::Admin)")] - impl Query { - async fn value(&self) -> i32 { - 100 - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - let query = "{ value }"; - assert_eq!( - schema - .execute(Request::new(query).data(Role::Admin)) - .await - .data, - value!({"value": 100}) - ); - - let query = "{ value }"; - assert_eq!( - schema - .execute(Request::new(query).data(Role::Guest)) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "Forbidden".to_string(), - source: None, - locations: vec![Pos { line: 1, column: 3 }], - path: vec![PathSegment::Field("value".to_owned())], - extensions: None, - }] - ); -} - -#[tokio::test] -pub async fn test_guard_on_object_field() { - struct Query; - - #[Object] - impl Query { - #[graphql(guard = "RoleGuard::new(Role::Admin)")] - async fn value(&self) -> i32 { - 100 - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - let query = "{ value }"; - assert_eq!( - schema - .execute(Request::new(query).data(Role::Admin)) - .await - .data, - value!({"value": 100}) - ); - - let query = "{ value }"; - assert_eq!( - schema - .execute(Request::new(query).data(Role::Guest)) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "Forbidden".to_string(), - source: None, - locations: vec![Pos { line: 1, column: 3 }], - path: vec![PathSegment::Field("value".to_owned())], - extensions: None, - }] - ); -} - -#[tokio::test] -pub async fn test_guard_on_complex_object() { - #[derive(SimpleObject)] - #[graphql(complex)] - struct Query { - value1: i32, - } - - #[ComplexObject(guard = "RoleGuard::new(Role::Admin)")] - impl Query { - async fn value2(&self) -> i32 { - 100 - } - } - - let schema = Schema::new(Query { value1: 10 }, EmptyMutation, EmptySubscription); - - let query = "{ value2 }"; - assert_eq!( - schema - .execute(Request::new(query).data(Role::Admin)) - .await - .data, - value!({"value2": 100}) - ); - - let query = "{ value2 }"; - assert_eq!( - schema - .execute(Request::new(query).data(Role::Guest)) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "Forbidden".to_string(), - source: None, - locations: vec![Pos { line: 1, column: 3 }], - path: vec![PathSegment::Field("value2".to_owned())], - extensions: None, - }] - ); -} - -#[tokio::test] -pub async fn test_guard_on_complex_object_field() { - #[derive(SimpleObject)] - #[graphql(complex)] - struct Query { - value1: i32, - } - - #[ComplexObject] - impl Query { - #[graphql(guard = "RoleGuard::new(Role::Admin)")] - async fn value2(&self) -> i32 { - 100 - } - } - - let schema = Schema::new(Query { value1: 10 }, EmptyMutation, EmptySubscription); - - let query = "{ value2 }"; - assert_eq!( - schema - .execute(Request::new(query).data(Role::Admin)) - .await - .data, - value!({"value2": 100}) - ); - - let query = "{ value2 }"; - assert_eq!( - schema - .execute(Request::new(query).data(Role::Guest)) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "Forbidden".to_string(), - source: None, - locations: vec![Pos { line: 1, column: 3 }], - path: vec![PathSegment::Field("value2".to_owned())], - extensions: None, - }] - ); -} - -#[tokio::test] -pub async fn test_guard_with_fn() { - fn is_admin(ctx: &Context<'_>) -> Result<()> { - if ctx.data_opt::() == Some(&Role::Admin) { - Ok(()) - } else { - Err("Forbidden".into()) - } - } - - #[derive(SimpleObject)] - struct Query { - #[graphql(guard = "is_admin")] - value: i32, - } - - let schema = Schema::new(Query { value: 10 }, EmptyMutation, EmptySubscription); - - let query = "{ value }"; - assert_eq!( - schema - .execute(Request::new(query).data(Role::Admin)) - .await - .data, - value!({"value": 10}) - ); - - assert_eq!( - schema - .execute(Request::new(query).data(Role::Guest)) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "Forbidden".to_string(), - source: None, - locations: vec![Pos { line: 1, column: 3 }], - path: vec![PathSegment::Field("value".to_owned())], - extensions: None, - }] - ); -} diff --git a/tests/hygiene.rs b/tests/hygiene.rs deleted file mode 100644 index 339284701..000000000 --- a/tests/hygiene.rs +++ /dev/null @@ -1,109 +0,0 @@ -#![allow(dead_code, non_camel_case_types, unused_macros)] -#![no_implicit_prelude] - -// TODO: remove this: https://github.com/dtolnay/async-trait/issues/132 -use ::async_graphql::{self, InputValueResult, ScalarType, Value}; -use ::serde::{Deserialize, Serialize}; -#[cfg(feature = "boxed-trait")] -use ::std::boxed::Box; -// TODO: remove this: https://github.com/nvzqz/static-assertions-rs/issues/37 -use ::std::marker::Sized; - -struct MyObject; -#[async_graphql::Object] -impl MyObject { - #[graphql(deprecation = "abc")] - async fn value(&self) -> ::std::primitive::i32 { - 5 - } - async fn other_value(&self) -> &::std::primitive::i16 { - &5 - } - /// Add one to the number. - async fn add_one( - &self, - #[graphql(default = 0)] v: ::std::primitive::i32, - ) -> ::std::primitive::i32 { - v + 1 - } -} - -#[derive(async_graphql::SimpleObject)] -struct MySimpleObject { - /// Value. - #[graphql(owned)] - value: ::std::primitive::i32, - other_value: ::std::primitive::i16, - #[graphql(deprecation = "bar")] - bar: ::std::string::String, - #[graphql(skip)] - skipped: ::std::any::TypeId, -} - -struct MySubscription; -#[async_graphql::Subscription] -impl MySubscription { - #[graphql(deprecation = "abc")] - async fn values(&self) -> impl ::futures_util::stream::Stream { - ::futures_util::stream::iter(5..7) - } - /// Count up from the value. - async fn count_up_from( - &self, - #[graphql(default = 0)] v: ::std::primitive::i32, - ) -> impl ::futures_util::stream::Stream { - ::futures_util::stream::iter(v..v + 20) - } -} - -struct MyScalar; -#[async_graphql::Scalar] -impl ScalarType for MyScalar { - fn parse(_value: Value) -> InputValueResult { - ::std::result::Result::Ok(Self) - } - fn to_value(&self) -> Value { - Value::String(::std::borrow::ToOwned::to_owned("Hello world!")) - } -} - -#[derive(Serialize, Deserialize)] -struct MyScalar2(::std::primitive::i32); -::async_graphql::scalar!(MyScalar2); - -#[derive(Clone, Copy, PartialEq, Eq, async_graphql::Enum)] -enum MyEnum { - /// Foo. - Foo, - Bar, -} - -#[derive(async_graphql::InputObject)] -struct MyInputObject { - /// Foo. - foo: ::std::primitive::i32, - #[graphql(default)] - bar: ::std::string::String, -} - -#[derive(async_graphql::Interface)] -#[graphql( - field(name = "value", ty = "::std::primitive::i32"), - field(name = "other_value", ty = "&::std::primitive::i16") -)] -enum MyInterface { - First(MyObject), - Second(MySimpleObject), -} - -#[derive(async_graphql::Union)] -enum MyUnion { - First(MyObject), - Second(MySimpleObject), -} - -#[derive(async_graphql::MergedObject)] -struct MyMergedObject(MyObject, MySimpleObject); - -#[derive(async_graphql::MergedSubscription)] -struct MyMergedSubscription(MySubscription); diff --git a/tests/input_object.rs b/tests/input_object.rs deleted file mode 100644 index 4272a5e9b..000000000 --- a/tests/input_object.rs +++ /dev/null @@ -1,927 +0,0 @@ -use async_graphql::*; - -#[tokio::test] -pub async fn test_input_object_default_value() { - #[derive(InputObject)] - struct MyInput { - #[graphql(default = 999)] - a: i32, - - #[graphql(default_with = "vec![1, 2, 3]")] - b: Vec, - - #[graphql(default = "abc")] - c: String, - - #[graphql(default = 999)] - d: i32, - - #[graphql(default = 999)] - e: i32, - } - - struct MyOutput { - a: i32, - b: Vec, - c: String, - d: Option, - e: Option, - } - - #[Object] - impl MyOutput { - async fn a(&self) -> i32 { - self.a - } - - async fn b(&self) -> &Vec { - &self.b - } - - async fn c(&self) -> &String { - &self.c - } - - async fn d(&self) -> &Option { - &self.d - } - - async fn e(&self) -> &Option { - &self.e - } - } - - struct Root; - - #[Object] - impl Root { - async fn a(&self, input: MyInput) -> MyOutput { - MyOutput { - a: input.a, - b: input.b, - c: input.c, - d: Some(input.d), - e: Some(input.e), - } - } - } - - let schema = Schema::new(Root, EmptyMutation, EmptySubscription); - let query = r#"{ - a(input:{e:777}) { - a b c d e - } - }"# - .to_owned(); - assert_eq!( - schema.execute(&query).await.data, - value!({ - "a": { - "a": 999, - "b": [1, 2, 3], - "c": "abc", - "d": 999, - "e": 777, - } - }) - ); -} - -#[tokio::test] -pub async fn test_inputobject_derive_and_item_attributes() { - use serde::Deserialize; - - #[derive(Deserialize, PartialEq, Debug, InputObject)] - struct MyInputObject { - #[serde(alias = "other")] - real: i32, - } - - assert_eq!( - serde_json::from_str::(r#"{ "other" : 100 }"#).unwrap(), - MyInputObject { real: 100 } - ); -} - -#[tokio::test] -pub async fn test_inputobject_flatten_recursive() { - #[derive(InputObject, Debug, Eq, PartialEq)] - struct A { - a: i32, - } - - #[derive(InputObject, Debug, Eq, PartialEq)] - struct B { - #[graphql(default = 70)] - b: i32, - #[graphql(flatten)] - a_obj: A, - } - - #[derive(InputObject, Debug, Eq, PartialEq)] - struct MyInputObject { - #[graphql(flatten)] - b_obj: B, - c: i32, - } - - assert_eq!( - MyInputObject::parse(Some(value!({ - "a": 10, - "b": 20, - "c": 30, - }))) - .unwrap(), - MyInputObject { - b_obj: B { - b: 20, - a_obj: A { a: 10 } - }, - c: 30, - } - ); - - assert_eq!( - MyInputObject { - b_obj: B { - b: 20, - a_obj: A { a: 10 } - }, - c: 30, - } - .to_value(), - value!({ - "a": 10, - "b": 20, - "c": 30, - }) - ); - - struct Query; - - #[Object] - impl Query { - async fn test(&self, input: MyInputObject) -> i32 { - input.c + input.b_obj.b + input.b_obj.a_obj.a - } - - async fn test_with_default( - &self, - #[graphql(default_with = r#"MyInputObject { - b_obj: B { - b: 2, - a_obj: A { a: 1 } - }, - c: 3, - }"#)] - input: MyInputObject, - ) -> i32 { - input.c + input.b_obj.b + input.b_obj.a_obj.a - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute( - r#"{ - test(input:{a:10, b: 20, c: 30}) - }"# - ) - .await - .into_result() - .unwrap() - .data, - value!({ - "test": 60, - }) - ); - - assert_eq!( - schema - .execute( - r#"{ - test(input:{a:10, c: 30}) - }"# - ) - .await - .into_result() - .unwrap() - .data, - value!({ - "test": 110, - }) - ); - - assert_eq!( - schema - .execute( - r#"{ - testWithDefault - }"# - ) - .await - .into_result() - .unwrap() - .data, - value!({ - "testWithDefault": 6, - }) - ); -} - -#[tokio::test] -pub async fn test_inputobject_flatten_multiple() { - #[derive(InputObject, Debug, Eq, PartialEq)] - struct A { - a: i32, - } - - #[derive(InputObject, Debug, Eq, PartialEq)] - struct B { - b: i32, - } - - #[derive(InputObject, Debug, Eq, PartialEq)] - struct C { - c: i32, - } - - #[derive(InputObject, Debug, Eq, PartialEq)] - struct Abc { - #[graphql(flatten)] - a: A, - - #[graphql(flatten)] - b: B, - - #[graphql(flatten)] - c: C, - } - - assert_eq!( - Abc::parse(Some(value!({ - "a": 10, - "b": 20, - "c": 30, - }))) - .unwrap(), - Abc { - a: A { a: 10 }, - b: B { b: 20 }, - c: C { c: 30 } - } - ); - - assert_eq!( - Abc { - a: A { a: 10 }, - b: B { b: 20 }, - c: C { c: 30 } - } - .to_value(), - value!({ - "a": 10, - "b": 20, - "c": 30, - }) - ); -} - -#[tokio::test] -pub async fn test_input_object_skip_field() { - #[derive(InputObject)] - struct MyInput2 { - a: i32, - #[graphql(skip)] - b: i32, - } - - struct Root; - - #[Object] - impl Root { - async fn a(&self, input: MyInput2) -> i32 { - assert_eq!(input.b, i32::default()); - input.a - } - } - - let schema = Schema::new(Root, EmptyMutation, EmptySubscription); - let query = r#"{ - a(input:{a: 777}) - }"#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "a": 777 - }) - ); -} - -#[tokio::test] -pub async fn test_box_input_object() { - #[derive(InputObject)] - struct MyInput { - value: i32, - input: Option>, - } - - struct Root; - - #[Object] - impl Root { - async fn q(&self, input: MyInput) -> i32 { - input.value - + input.input.as_ref().unwrap().value - + input.input.as_ref().unwrap().input.as_ref().unwrap().value - } - } - - let schema = Schema::new(Root, EmptyMutation, EmptySubscription); - let query = r#"{ - q(input: {value: 100, input: { value: 200, input: { value: 300 } } }) - }"#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "q": 600 - }) - ); -} - -#[tokio::test] -pub async fn test_both_input_output() { - #[derive(SimpleObject, InputObject)] - #[graphql(input_name = "MyObjectInput")] - #[allow(dead_code)] - struct MyObject { - #[graphql(default = 10)] - a: i32, - b: bool, - #[graphql(skip)] - c: String, - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self, input: MyObject) -> MyObject { - input - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ obj(input: {a: 1, b: true}) { a b } }") - .await - .into_result() - .unwrap() - .data, - value!({ - "obj": { - "a": 1, - "b": true, - } - }) - ); - - assert_eq!( - schema - .execute("{ obj(input: {b: true}) { a b } }") - .await - .into_result() - .unwrap() - .data, - value!({ - "obj": { - "a": 10, - "b": true, - } - }) - ); - - assert_eq!(::type_name(), "MyObjectInput"); - assert_eq!(::type_name(), "MyObject"); -} - -#[tokio::test] -pub async fn test_both_input_output_generic() { - #[derive(SimpleObject, InputObject)] - #[graphql(concrete(name = "MyObjectU32", params(u32)))] - #[graphql(concrete(name = "MyObjectString", params(String)))] - #[allow(dead_code)] - struct MyObject { - a: T, - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self, input: MyObject) -> MyObject { - MyObject:: { - a: format!("{}", input.a), - } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ obj(input: {a: 123}) { a } }") - .await - .into_result() - .unwrap() - .data, - value!({ - "obj": { - "a": "123", - } - }) - ); - - assert_eq!( as InputType>::type_name(), "MyObjectU32"); - assert_eq!( as OutputType>::type_name(), "MyObjectU32"); - assert_eq!( - as InputType>::type_name(), - "MyObjectString" - ); - assert_eq!( - as OutputType>::type_name(), - "MyObjectString" - ); -} - -#[tokio::test] -pub async fn test_both_input_output_generic_with_nesting() { - #[derive(Clone, Copy, PartialEq, Eq, Enum, serde::Serialize)] - enum MyEnum { - Option1, - Option2, - } - - #[derive(SimpleObject, InputObject)] - #[graphql(concrete(name = "MyObjectU32", params(u32)))] - #[graphql(concrete( - name = "MyObjectMyEnum", - input_name = "MyObjectMyEnumInput", - params(MyEnum) - ))] - #[allow(dead_code)] - struct MyObject { - a: T, - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self, input: MyObject) -> MyObject { - MyObject:: { a: input.a } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ obj(input: {a: OPTION_1}) { a } }") - .await - .into_result() - .unwrap() - .data, - value!({ - "obj": { - "a": "OPTION_1", - } - }) - ); - - assert_eq!( as InputType>::type_name(), "MyObjectU32"); - assert_eq!( as OutputType>::type_name(), "MyObjectU32"); - assert_eq!( - as InputType>::type_name(), - "MyObjectMyEnumInput" - ); - assert_eq!( - as OutputType>::type_name(), - "MyObjectMyEnum" - ); -} - -#[tokio::test] -pub async fn test_both_input_output_2() { - #[derive(SimpleObject, InputObject)] - #[graphql(name = "MyObj", input_name = "MyObjectInput")] - #[allow(dead_code)] - struct MyObject { - #[graphql(default = 10)] - a: i32, - b: bool, - #[graphql(skip)] - c: String, - } - - assert_eq!(::type_name(), "MyObjectInput"); - assert_eq!(::type_name(), "MyObj"); -} - -#[test] -#[should_panic] -pub fn test_both_input_output_with_same_name() { - #[derive(SimpleObject, InputObject)] - #[allow(dead_code)] - struct MyObject { - #[graphql(default = 10)] - a: i32, - b: bool, - #[graphql(skip)] - c: String, - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self, input: MyObject) -> MyObject { - input - } - } - - Schema::new(Query, EmptyMutation, EmptySubscription); -} - -#[tokio::test] -pub async fn test_both_input_output_flatten() { - #[derive(SimpleObject, InputObject)] - #[graphql(input_name = "ABCInput")] - #[graphql(name = "ABC")] - #[allow(clippy::upper_case_acronyms)] - struct ABC { - a: i32, - #[graphql(flatten)] - bc: BC, - } - - #[derive(SimpleObject, InputObject)] - #[graphql(input_name = "BCInput")] - struct BC { - b: i32, - c: i32, - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self, input: ABC) -> ABC { - input - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ obj(input: { a: 1, b: 2, c: 3 }) { a b c } }") - .await - .into_result() - .unwrap() - .data, - value!({ - "obj": { - "a": 1, - "b": 2, - "c": 3 - } - }) - ); -} - -#[tokio::test] -pub async fn test_skip_input() { - #[derive(SimpleObject, InputObject)] - #[graphql(input_name = "MyObjectInput")] - #[allow(dead_code)] - struct MyObject { - a: i32, - #[graphql(skip_input)] - b: i32, - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self, input: MyObject) -> MyObject { - input - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ obj(input: { a: 1 }) { a b } }") - .await - .into_result() - .unwrap() - .data, - value!({ - "obj": { - "a": 1, - "b": 0, - } - }) - ); -} - -#[tokio::test] -pub async fn test_skip_output() { - #[derive(SimpleObject, InputObject)] - #[graphql(input_name = "MyObjectInput")] - #[allow(dead_code)] - struct MyObject { - a: i32, - #[graphql(skip_output)] - b: i32, - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self, input: MyObject) -> MyObject { - input - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ obj(input: { a: 1, b: 2 }) { a } }") - .await - .into_result() - .unwrap() - .data, - value!({ - "obj": { - "a": 1, - } - }) - ); -} - -#[tokio::test] -pub async fn test_complex_output() { - #[derive(SimpleObject, InputObject)] - #[graphql(input_name = "MyObjectInput")] - #[graphql(complex)] - #[allow(dead_code)] - struct MyObject { - a: i32, - } - - #[ComplexObject] - impl MyObject { - async fn double(&self) -> i32 { - self.a * 2 - } - } - - struct Query; - #[Object] - impl Query { - async fn obj(&self, input: MyObject) -> MyObject { - input - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ obj(input: { a: 1 }) { a, double } }") - .await - .into_result() - .unwrap() - .data, - value!({ - "obj": { - "a": 1, - "double": 2, - } - }) - ); -} - -#[tokio::test] -pub async fn test_input_object_process_with() { - mod processor { - pub fn string(input: &mut String) { - while let Some(ch) = input.pop() { - if !ch.is_whitespace() { - input.push(ch); - break; - } - } - } - } - #[derive(InputObject)] - struct MyInput { - // processor does nothing on default value - #[graphql(default = " ", process_with = "processor::string")] - a: String, - - #[graphql(process_with = "processor::string")] - b: String, - } - - struct MyOutput { - a: String, - b: String, - } - - #[Object] - impl MyOutput { - async fn a(&self) -> &String { - &self.a - } - - async fn b(&self) -> &String { - &self.b - } - } - - struct Root; - - #[Object] - impl Root { - async fn a(&self, input: MyInput) -> MyOutput { - MyOutput { - a: input.a, - b: input.b, - } - } - } - - let schema = Schema::new(Root, EmptyMutation, EmptySubscription); - let query = r#"{ - a(input:{b: "test b "}) { - a b - } - }"# - .to_owned(); - assert_eq!( - schema.execute(&query).await.data, - value!({ - "a": { - "a": " ", - "b": "test b", - } - }) - ); - - let schema = Schema::new(Root, EmptyMutation, EmptySubscription); - let query = r#"{ - a(input:{a: "test a ", b: "test"}) { - a b - } - }"# - .to_owned(); - assert_eq!( - schema.execute(&query).await.data, - value!({ - "a": { - "a": "test a", - "b": "test", - } - }) - ); -} - -#[tokio::test] -pub async fn test_input_object_validator() { - fn check_my_object(obj: &MyInput) -> Result<(), &'static str> { - if obj.a < 100 || obj.b < 100 { - Err("invalid MyInput") - } else { - Ok(()) - } - } - - #[derive(InputObject)] - #[graphql(validator = "check_my_object")] - struct MyInput { - a: i32, - b: i32, - } - - struct Query; - - #[Object] - impl Query { - async fn a(&self, input: MyInput) -> i32 { - input.a + input.b - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - assert_eq!( - schema - .execute("{ a(input: { a: 200, b: 300 }) }") - .await - .data, - value!({ "a": 500 }) - ); - - assert_eq!( - schema - .execute("{ a(input: { a: 100, b: 25 }) }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "MyInput": invalid MyInput"#.to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 12 - }], - path: vec![PathSegment::Field("a".to_string())], - extensions: None - }] - ); -} - -#[tokio::test] -pub async fn test_custom_validator_with_extensions_input() { - struct MyValidator { - expect: i32, - } - - impl MyValidator { - pub fn new(n: i32) -> Self { - MyValidator { expect: n } - } - } - - impl CustomValidator for MyValidator { - fn check(&self, value: &i32) -> Result<(), InputValueError> { - if *value == self.expect { - Ok(()) - } else { - Err( - InputValueError::custom(format!("expect 100, actual {}", value)) - .with_extension("code", 99), - ) - } - } - } - - #[derive(InputObject, Debug)] - struct ValueInput { - #[graphql(validator(custom = "MyValidator::new(100)"))] - v: i32, - } - - struct Query; - - #[Object] - impl Query { - async fn value(&self, n: ValueInput) -> i32 { - n.v - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ value(n: {v: 100}) }") - .await - .into_result() - .unwrap() - .data, - value!({ "value": 100 }) - ); - - let mut error_extensions = ErrorExtensionValues::default(); - error_extensions.set("code", 99); - assert_eq!( - schema - .execute("{ value(n: {v: 11}) }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "Int": expect 100, actual 11 (occurred while parsing "ValueInput")"#.to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 12 - }], - path: vec![PathSegment::Field("value".to_string())], - extensions: Some(error_extensions) - }] - ); -} diff --git a/tests/input_value.rs b/tests/input_value.rs deleted file mode 100644 index 79b1dec1f..000000000 --- a/tests/input_value.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::sync::Arc; - -use async_graphql::*; - -#[tokio::test] -pub async fn test_input_value_custom_error() { - struct Query; - - #[Object] - impl Query { - async fn parse_int(&self, _n: i8) -> bool { - true - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = r#"{ parseInt(n:289) }"#; - assert_eq!( - schema.execute(query).await.into_result().unwrap_err(), - vec![ServerError { - message: "Failed to parse \"Int\": Only integers from -128 to 127 are accepted." - .to_owned(), - source: None, - locations: vec![Pos { - line: 1, - column: 14, - }], - path: vec![PathSegment::Field("parseInt".to_owned())], - extensions: None, - }], - ); -} - -#[tokio::test] -pub async fn test_input_box_str() { - struct Query; - - #[Object] - impl Query { - async fn box_str(&self, s: Box) -> String { - s.to_string() - } - - async fn arc_str(&self, s: Arc) -> String { - s.to_string() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = r#"{ boxStr(s: "abc") arcStr(s: "def") }"#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "boxStr": "abc", - "arcStr": "def", - }) - ); -} - -#[tokio::test] -pub async fn test_input_box_slice() { - struct Query; - - #[Object] - impl Query { - async fn box_slice(&self, s: Box<[i32]>) -> Box<[i32]> { - s - } - - async fn arc_slice(&self, s: Arc<[i32]>) -> Arc<[i32]> { - s - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = r#"{ boxSlice(s: [1, 2, 3]) arcSlice(s: [4, 5, 6]) }"#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "boxSlice": [1, 2, 3], - "arcSlice": [4, 5, 6], - }) - ); -} diff --git a/tests/interface.rs b/tests/interface.rs deleted file mode 100644 index 4badd26c6..000000000 --- a/tests/interface.rs +++ /dev/null @@ -1,550 +0,0 @@ -use async_graphql::*; - -#[tokio::test] -pub async fn test_interface_simple_object() { - #[derive(SimpleObject)] - struct MyObj { - id: i32, - title: String, - } - - #[derive(Interface)] - #[graphql(field(name = "id", ty = "&i32"))] - enum Node { - MyObj(MyObj), - } - - struct Query; - - #[Object] - impl Query { - async fn node(&self) -> Node { - MyObj { - id: 33, - title: "haha".to_string(), - } - .into() - } - } - - let query = r#"{ - node { - ... on Node { - id - } - } - }"#; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "node": { - "id": 33, - } - }) - ); -} - -#[tokio::test] -pub async fn test_interface_simple_object2() { - #[derive(SimpleObject)] - struct MyObj { - id: i32, - title: String, - } - - #[derive(Interface)] - #[graphql(field(name = "id", ty = "&i32"))] - enum Node { - MyObj(MyObj), - } - - struct Query; - - #[Object] - impl Query { - async fn node(&self) -> Node { - MyObj { - id: 33, - title: "haha".to_string(), - } - .into() - } - } - - let query = r#"{ - node { - ... on Node { - id - } - } - }"#; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "node": { - "id": 33, - } - }) - ); -} - -#[tokio::test] -pub async fn test_multiple_interfaces() { - struct MyObj; - - #[Object] - impl MyObj { - async fn value_a(&self) -> i32 { - 1 - } - - async fn value_b(&self) -> i32 { - 2 - } - - async fn value_c(&self) -> i32 { - 3 - } - } - - #[derive(Interface)] - #[graphql(field(name = "value_a", ty = "i32"))] - enum InterfaceA { - MyObj(MyObj), - } - - #[derive(Interface)] - #[graphql(field(name = "value_b", ty = "i32"))] - enum InterfaceB { - MyObj(MyObj), - } - - struct Query; - - #[Object] - impl Query { - async fn my_obj(&self) -> InterfaceB { - MyObj.into() - } - } - - let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .register_output_type::() // `InterfaceA` is not directly referenced, so manual registration is required. - .finish(); - let query = r#"{ - myObj { - ... on InterfaceA { - valueA - } - ... on InterfaceB { - valueB - } - ... on MyObj { - valueC - } - } - }"#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "myObj": { - "valueA": 1, - "valueB": 2, - "valueC": 3, - } - }) - ); -} - -#[tokio::test] -pub async fn test_multiple_objects_in_multiple_interfaces() { - struct MyObjOne; - - #[Object] - impl MyObjOne { - async fn value_a(&self) -> i32 { - 1 - } - - async fn value_b(&self) -> i32 { - 2 - } - - async fn value_c(&self) -> i32 { - 3 - } - } - - struct MyObjTwo; - - #[Object] - impl MyObjTwo { - async fn value_a(&self) -> i32 { - 1 - } - } - - #[derive(Interface)] - #[graphql(field(name = "value_a", ty = "i32"))] - enum InterfaceA { - MyObjOne(MyObjOne), - MyObjTwo(MyObjTwo), - } - - #[derive(Interface)] - #[graphql(field(name = "value_b", ty = "i32"))] - enum InterfaceB { - MyObjOne(MyObjOne), - } - - struct Query; - - #[Object] - impl Query { - async fn my_obj(&self) -> Vec { - vec![MyObjOne.into(), MyObjTwo.into()] - } - } - - let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .register_output_type::() // `InterfaceB` is not directly referenced, so manual registration is required. - .finish(); - let query = r#"{ - myObj { - ... on InterfaceA { - valueA - } - ... on InterfaceB { - valueB - } - ... on MyObjOne { - valueC - } - } - }"#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "myObj": [{ - "valueA": 1, - "valueB": 2, - "valueC": 3, - }, { - "valueA": 1 - }] - }) - ); -} - -#[tokio::test] -pub async fn test_interface_field_result() { - struct MyObj; - - #[Object] - impl MyObj { - async fn value(&self) -> FieldResult { - Ok(10) - } - } - - #[derive(Interface)] - #[graphql(field(name = "value", ty = "i32"))] - enum Node { - MyObj(MyObj), - } - - struct Query; - - #[Object] - impl Query { - async fn node(&self) -> Node { - MyObj.into() - } - } - - let query = r#"{ - node { - ... on Node { - value - } - } - }"#; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "node": { - "value": 10, - } - }) - ); -} - -#[tokio::test] -pub async fn test_interface_field_method() { - struct A; - - #[Object] - impl A { - #[graphql(name = "created_at")] - pub async fn created_at(&self) -> i32 { - 1 - } - } - - struct B; - - #[Object] - impl B { - #[graphql(name = "created_at")] - pub async fn created_at(&self) -> i32 { - 2 - } - } - - #[derive(Interface)] - #[graphql(field(name = "created_at", method = "created_at", ty = "i32"))] - enum MyInterface { - A(A), - B(B), - } - - struct Query; - - #[Object] - impl Query { - async fn test(&self) -> MyInterface { - A.into() - } - } - - let query = "{ test { created_at } }"; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "test": { - "created_at": 1, - } - }) - ); -} - -#[tokio::test] -pub async fn test_interface_implement_other_interface() { - #[derive(Interface)] - #[graphql(field(name = "id", ty = "ID"))] - pub enum Entity { - Company(Company), - Organization(Organization), - } - - #[derive(Interface)] - #[graphql(field(name = "id", ty = "ID"))] - pub enum Node { - Entity(Entity), - } - - pub struct Company {} - - #[Object] - impl Company { - pub async fn id(&self) -> ID { - "88".into() - } - } - - pub struct Organization {} - - #[Object] - impl Organization { - pub async fn id(&self) -> ID { - "99".into() - } - } - - struct Query; - - #[Object] - impl Query { - async fn company(&self) -> Node { - Entity::Company(Company {}).into() - } - - async fn organization(&self) -> Node { - Entity::Organization(Organization {}).into() - } - } - - let query = r#" - { - company { id } - organization { id } - } - "#; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "company": { - "id": "88", - }, - "organization": { - "id": "99", - } - }) - ); -} - -#[tokio::test] -pub async fn test_issue_330() { - #[derive(Interface)] - #[graphql(field( - desc = "The code represented as a number.", - name = "number", - ty = "String" - ))] - pub enum Code { - Barcode(Barcode), - Qrcode(Qrcode), - } - - pub struct Barcode(String); - - #[Object] - impl Barcode { - pub async fn number(&self) -> String { - format!("barcode:{}", self.0) - } - } - - pub struct Qrcode(String); - - #[Object] - impl Qrcode { - pub async fn number(&self) -> String { - format!("qrcode:{}", self.0) - } - } - - #[derive(Interface)] - #[graphql(field(desc = "The article number.", name = "number", ty = "Code"))] - pub enum Article { - Book(Book), - } - - pub struct Book { - code: String, - } - - #[Object] - impl Book { - pub async fn number(&self) -> Barcode { - Barcode(self.code.clone()) - } - } - - struct Query; - - #[Object] - impl Query { - pub async fn book(&self) -> Article { - Book { - code: "123456".to_string(), - } - .into() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ book { number { number } } }") - .await - .into_result() - .unwrap() - .data, - value!({ - "book": { - "number": { "number": "barcode:123456" } - } - }) - ); -} - -#[tokio::test] -pub async fn test_interface_with_oneof_object() { - #[derive(SimpleObject, InputObject)] - #[graphql(input_name = "MyObjAInput")] - struct MyObjA { - id: i32, - title_a: String, - } - - #[derive(SimpleObject, InputObject)] - #[graphql(input_name = "MyObjBInput")] - struct MyObjB { - id: i32, - title_b: String, - } - - #[derive(OneofObject, Interface)] - #[graphql(input_name = "NodeInput", field(name = "id", ty = "&i32"))] - enum Node { - MyObjA(MyObjA), - MyObjB(MyObjB), - } - - struct Query; - - #[Object] - impl Query { - async fn node(&self, input: Node) -> Node { - input - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query_a = r#"{ - node(input: { myObjA: { id: 10, titleA: "abc" } }) { - id - ... on MyObjA { - titleA - } - ... on MyObjB { - titleB - } - } - }"#; - assert_eq!( - schema.execute(query_a).await.into_result().unwrap().data, - value!({ - "node": { - "id": 10, - "titleA": "abc", - } - }) - ); - let query_b = r#"{ - node(input: { myObjB: { id: 10, titleB: "abc" } }) { - id - ... on MyObjA { - titleA - } - ... on MyObjB { - titleB - } - } - }"#; - assert_eq!( - schema.execute(query_b).await.into_result().unwrap().data, - value!({ - "node": { - "id": 10, - "titleB": "abc", - } - }) - ); -} diff --git a/tests/interface_exporting.rs b/tests/interface_exporting.rs deleted file mode 100644 index 68a043363..000000000 --- a/tests/interface_exporting.rs +++ /dev/null @@ -1,220 +0,0 @@ -use async_graphql::*; -use serde::Deserialize; - -#[derive(SimpleObject)] -struct ObjectA { - id: i32, - title: String, -} - -#[derive(SimpleObject)] -struct ObjectB { - id: i32, - title: String, -} - -#[derive(Interface)] -#[graphql(field(name = "id", ty = "&i32"))] -enum ImplicitInterface { - ObjectA(ObjectA), - ObjectB(ObjectB), -} - -#[derive(Interface)] -#[graphql(field(name = "title", ty = "String"))] -enum ExplicitInterface { - ObjectA(ObjectA), - ObjectB(ObjectB), -} - -#[derive(Interface)] -#[graphql(visible = false)] -#[graphql(field(name = "title", ty = "String"))] -enum InvisibleInterface { - ObjectA(ObjectA), - ObjectB(ObjectB), -} - -#[derive(SimpleObject)] -struct ObjectC { - id: i32, - title: String, -} - -#[derive(Interface)] -#[graphql(field(name = "id", ty = "&i32"))] -enum UnreferencedInterface { - ObjectC(ObjectC), -} - -#[derive(Union)] -enum ObjectUnion { - ObjectA(ObjectA), - ObjectB(ObjectB), -} - -struct Query; - -#[Object] -impl Query { - async fn implicit(&self) -> ObjectUnion { - ObjectA { - id: 33, - title: "haha".to_string(), - } - .into() - } - - async fn explicit(&self) -> ExplicitInterface { - ObjectA { - id: 40, - title: "explicit".to_string(), - } - .into() - } -} - -fn build_schema() -> Schema { - Schema::build(Query, EmptyMutation, EmptySubscription) - .register_output_type::() - .register_output_type::() - .register_output_type::() - .finish() -} - -#[tokio::test] -pub async fn test_interface_exports_interfaces_on_object_type() { - #[derive(Deserialize)] - struct QueryResponse { - #[serde(rename = "__type")] - ty: TypeResponse, - } - - #[derive(Deserialize)] - struct TypeResponse { - name: String, - kind: String, - interfaces: Vec, - } - - #[derive(Deserialize)] - struct InterfaceResponse { - name: String, - } - - let schema = build_schema(); - - let resp: QueryResponse = from_value( - schema - .execute(r#"{ __type(name: "ObjectA") { name kind interfaces { name }} }"#) - .await - .into_result() - .unwrap() - .data, - ) - .unwrap(); - - assert_eq!(resp.ty.name, "ObjectA"); - assert_eq!(resp.ty.kind, "OBJECT"); - assert!( - resp.ty - .interfaces - .iter() - .any(|i| i.name == "ExplicitInterface") - ); - assert!( - resp.ty - .interfaces - .iter() - .any(|i| i.name == "ImplicitInterface") - ); - assert!( - !resp - .ty - .interfaces - .iter() - .any(|i| i.name == "InvisibleInterface") - ); -} - -#[tokio::test] -pub async fn test_interface_exports_explicit_interface_type() { - let schema = build_schema(); - - let data = schema - .execute(r#"{ __type(name: "ExplicitInterface") { name kind } }"#) - .await - .into_result() - .unwrap() - .data; - - assert_eq!( - data, - value!({ - "__type": { - "name": "ExplicitInterface", - "kind": "INTERFACE", - } - }) - ); -} - -#[tokio::test] -pub async fn test_interface_exports_implicit_interface_type() { - let schema = build_schema(); - - let data = schema - .execute(r#"{ __type(name: "ImplicitInterface") { name kind } }"#) - .await - .into_result() - .unwrap() - .data; - - assert_eq!( - data, - value!({ - "__type": { - "name": "ImplicitInterface", - "kind": "INTERFACE", - } - }) - ); -} - -#[tokio::test] -pub async fn test_interface_no_export_invisible_interface_type() { - let schema = build_schema(); - - let data = schema - .execute(r#"{ __type(name: "InvisibleInterface") { name } }"#) - .await - .into_result() - .unwrap() - .data; - - assert_eq!( - data, - value!({ - "__type": null, - }) - ); -} - -#[tokio::test] -pub async fn test_interface_no_export_unreferenced_interface_type() { - let schema = build_schema(); - - let data = schema - .execute(r#"{ __type(name: "UnreferencedInterface") { name } }"#) - .await - .into_result() - .unwrap() - .data; - - assert_eq!( - data, - value!({ - "__type": null, - }) - ); -} diff --git a/tests/introspection.rs b/tests/introspection.rs deleted file mode 100644 index f514b8a39..000000000 --- a/tests/introspection.rs +++ /dev/null @@ -1,1659 +0,0 @@ -#![allow(clippy::uninlined_format_args)] -#![allow(clippy::diverging_sub_expression)] - -use async_graphql::*; -use chrono::{NaiveDate, NaiveDateTime}; -use futures_util::stream::{self, Stream}; - -#[derive(Clone, Debug)] -struct Circle { - radius: f32, -} - -/// Circle -#[Object] -impl Circle { - async fn scale(&self, s: f32) -> TestInterface { - Circle { - radius: self.radius * s, - } - .into() - } -} - -#[derive(Clone, Debug)] -struct Square { - width: f32, -} - -/// Square -#[Object] -impl Square { - #[graphql(deprecation = "Field scale is deprecated")] - async fn scale(&self, s: f32) -> TestInterface { - Square { - width: self.width * s, - } - .into() - } -} - -#[derive(Clone, Debug, Interface)] -#[graphql(field(name = "scale", ty = "TestInterface", arg(name = "s", ty = "f32")))] -enum TestInterface { - Circle(Circle), - Square(Square), -} - -/// Test Union -#[derive(Clone, Debug, Union)] -enum TestUnion { - Circle(Circle), - Square(Square), -} - -/// Test Enum -#[derive(Enum, Copy, Clone, Eq, PartialEq)] -enum TestEnum { - /// Kind 1 - Kind1, - - /// Kind 2 - #[graphql(deprecation = "Kind 2 deprecated")] - Kind2, -} - -#[derive(Clone, Debug, SimpleObject)] -struct SimpleList { - items: Vec, -} - -#[derive(Clone, Debug, SimpleObject)] -struct SimpleOption { - required: i32, - optional: Option, -} - -/// TestScalar -#[derive(Clone, Debug)] -struct TestScalar(i32); - -/// Test scalar -#[Scalar] -impl ScalarType for TestScalar { - fn parse(_value: Value) -> InputValueResult { - Ok(TestScalar(42)) - } - - fn is_valid(_value: &Value) -> bool { - true - } - - fn to_value(&self) -> Value { - Value::Number(self.0.into()) - } -} - -/// Is SimpleObject -/// and some more ```lorem ipsum``` -#[derive(SimpleObject)] -struct SimpleObject { - /// Value a with # 'some' `markdown`"." - /// and some more lorem ipsum - a: i32, - - /// Value b description - b: String, - - /// Value c description - c: ID, - - d: SimpleOption, - - #[graphql(deprecation = "Field e is deprecated")] - e: bool, - - #[graphql(deprecation)] - e2: bool, - - #[graphql(deprecation = true)] - e3: bool, - - #[graphql(deprecation = false)] - e4: bool, - - f: TestEnum, - - g: TestInterface, - - h: TestUnion, - - i: SimpleList, - - j: TestScalar, -} - -struct Query; - -/// Global query -#[Object] -#[allow(unreachable_code)] -impl Query { - /// Get a simple object - async fn simple_object(&self) -> SimpleObject { - unimplemented!() - } -} - -/// Simple Input -#[derive(InputObject)] -pub struct SimpleInput { - pub a: String, -} - -struct Mutation; - -/// Global mutation -#[Object] -#[allow(unreachable_code)] -impl Mutation { - /// simple_mutation description - /// line2 - /// line3 - async fn simple_mutation(&self, _input: SimpleInput) -> SimpleObject { - unimplemented!() - } -} - -struct Subscription; - -/// Global subscription -#[Subscription] -impl Subscription { - /// simple_subscription description - async fn simple_subscription( - &self, - #[graphql(default = 1)] step: i32, - ) -> impl Stream { - stream::once(async move { step }) - } -} - -// #[tokio::test] -// pub async fn test_introspection_schema() { -// let schema = Schema::new(Query, Mutation, Subscription); - -// let query = r#" -// { -// __schema { -// directives { -// name -// locations -// } -// subscriptionType { -// name -// fields { name } -// } -// types { -// name -// } -// queryType { -// name -// } -// mutationType { -// name -// } -// queryType { -// name -// } -// } -// } -// "#; - -// let res_json = value!({ -// "__schema": { -// } -// }); - -// let res = schema.execute(query).await.unwrap().data; - -// // pretty print result -// // println!("{}", serde_json::to_string_pretty(&res).unwrap()); - -// assert_eq!(res, res_json) -// } - -// #[tokio::test] -// pub async fn test_introspection_documentation() { -// let schema = Schema::new(Query, EmptyMutation, EmptySubscription); -// -// let query = r#" -// { -// __type(name: "SimpleObject") { -// name -// description -// fields { -// name -// description -// } -// } -// } -// "#; -// -// let res_json = value!({ -// "__type": { -// "name": "SimpleObject", -// "description": "Is SimpleObject", -// "fields": [ -// { -// "name": "a", -// "description": "Value a with # 'some' `markdown`\".\"" -// }, -// { -// "name": "b", -// "description": "Value b description" -// }, -// { -// "name": "c", -// "description": "Value c description" -// }, -// { -// "name": "d", -// "description": "" -// }, -// { -// "name": "f", -// "description": null -// }, -// { -// "name": "g", -// "description": null -// }, -// { -// "name": "h", -// "description": null -// }, -// { -// "name": "i", -// "description": null -// }, -// { -// "name": "j", -// "description": null -// } -// ] -// } -// }); -// -// let res = schema.execute(query).await.unwrap().data; -// -// assert_eq!(res, res_json) -// } - -#[tokio::test] -pub async fn test_introspection_deprecation() { - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - let get_object_query = |obj, is_deprecated| { - format!( - r#" - {{ - __type(name: "{}") {{ - fields(includeDeprecated: {}) {{ - name - isDeprecated - deprecationReason - }} - }} - }} - "#, - obj, is_deprecated - ) - }; - - // SimpleObject with deprecated inclusive - let mut query = get_object_query("SimpleObject", "true"); - - let mut res_json = value!({ - "__type": { - "fields": [ - { - "name": "a", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "b", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "c", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "d", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "e", - "isDeprecated": true, - "deprecationReason": "Field e is deprecated" - }, - { - "name": "e2", - "isDeprecated": true, - "deprecationReason": null - }, - { - "name": "e3", - "isDeprecated": true, - "deprecationReason": null - }, - { - "name": "e4", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "f", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "g", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "h", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "i", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "j", - "isDeprecated": false, - "deprecationReason": null - } - ] - } - }); - - let mut res = schema.execute(&query).await.into_result().unwrap().data; - - assert_eq!(res, res_json); - - // SimpleObject with deprecated fields exclusive - query = get_object_query("SimpleObject", "false"); - - res_json = value!({ - "__type": { - "fields": [ - { - "name": "a", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "b", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "c", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "d", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "e4", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "f", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "g", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "h", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "i", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "j", - "isDeprecated": false, - "deprecationReason": null - } - ] - } - }); - - res = schema.execute(&query).await.into_result().unwrap().data; - - assert_eq!(res, res_json); - - // Object with only one deprecated field inclusive - query = get_object_query("Square", "true"); - - res_json = value!({ - "__type": { - "fields": [ - { - "name": "scale", - "isDeprecated": true, - "deprecationReason": "Field scale is deprecated" - } - ] - } - }); - - res = schema.execute(&query).await.into_result().unwrap().data; - - assert_eq!(res, res_json); - - // Object with only one deprecated field exclusive - query = get_object_query("Square", "false"); - - res_json = value!({ - "__type": { - "fields": [] - } - }); - - res = schema.execute(&query).await.into_result().unwrap().data; - - assert_eq!(res, res_json); - - let get_enum_query = |obj, is_deprecated| { - format!( - r#" - {{ - __type(name: "{}") {{ - enumValues(includeDeprecated: {}) {{ - name - isDeprecated - deprecationReason - }} - }} - }} - "#, - obj, is_deprecated - ) - }; - - // Enum with deprecated value inclusive - query = get_enum_query("TestEnum", "true"); - - res_json = value!({ - "__type": { - "enumValues": [ - { - "name": "KIND_1", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "KIND_2", - "isDeprecated": true, - "deprecationReason": "Kind 2 deprecated" - } - ] - } - }); - - res = schema.execute(&query).await.into_result().unwrap().data; - - assert_eq!(res, res_json); - - // Enum with deprecated value exclusive - query = get_enum_query("TestEnum", "false"); - - res_json = value!({ - "__type": { - "enumValues": [ - { - "name": "KIND_1", - "isDeprecated": false, - "deprecationReason": null - } - ] - } - }); - - res = schema.execute(&query).await.into_result().unwrap().data; - - assert_eq!(res, res_json); -} - -#[tokio::test] -pub async fn test_introspection_type_kind() { - let schema = Schema::new(Query, Mutation, EmptySubscription); - - let get_type_kind_query = |obj| { - format!( - r#"{{ - __type(name: "{}") {{ - name - kind - }} - }}"#, - obj - ) - }; - - // Test simple object - let mut query = get_type_kind_query("SimpleObject"); - - let mut res_json = value!({ - "__type": { - "name": "SimpleObject", - "kind": "OBJECT" - } - }); - - let mut res = schema.execute(&query).await.into_result().unwrap().data; - - assert_eq!(res, res_json); - - // Test object - query = get_type_kind_query("Square"); - - res_json = value!({ - "__type": { - "name": "Square", - "kind": "OBJECT" - } - }); - - res = schema.execute(&query).await.into_result().unwrap().data; - - assert_eq!(res, res_json); - - // Test enum - query = get_type_kind_query("TestEnum"); - - res_json = value!({ - "__type": { - "name": "TestEnum", - "kind": "ENUM" - } - }); - - res = schema.execute(&query).await.into_result().unwrap().data; - - assert_eq!(res, res_json); - - // Test union - query = get_type_kind_query("TestUnion"); - - res_json = value!({ - "__type": { - "name": "TestUnion", - "kind": "UNION" - } - }); - - res = schema.execute(&query).await.into_result().unwrap().data; - - assert_eq!(res, res_json); - - // Test scalar - query = get_type_kind_query("ID"); - - res_json = value!({ - "__type": { - "name": "ID", - "kind": "SCALAR" - } - }); - - res = schema.execute(&query).await.into_result().unwrap().data; - - assert_eq!(res, res_json); - - let get_field_kind_query = |obj| { - format!( - r#"{{ - __type(name: "{}") {{ - fields {{ - name - type {{ name kind ofType {{ name kind }} }} - }} - }} - }}"#, - obj - ) - }; - - // Test list - query = get_field_kind_query("SimpleList"); - - res_json = value!({ - "__type": { - "fields": [ - { - "name": "items", - "type": { - "name": null, - "kind": "NON_NULL", - "ofType": { - "name": null, - "kind": "LIST" - } - } - } - ] - } - }); - - res = schema.execute(&query).await.into_result().unwrap().data; - - assert_eq!(res, res_json); - - // Test NON_NULL - query = get_field_kind_query("SimpleOption"); - - res_json = value!({ - "__type": { - "fields": [ - { - "name": "required", - "type": { - "name": null, - "kind": "NON_NULL", - "ofType": { - "name": "Int", - "kind": "SCALAR" - } - } - }, - { - "name": "optional", - "type": { - "name": "Int", - "kind": "SCALAR", - "ofType": null - } - } - ] - } - }); - - res = schema.execute(&query).await.into_result().unwrap().data; - - assert_eq!(res, res_json); -} - -#[tokio::test] -pub async fn test_introspection_scalar() { - let schema = Schema::new(Query, Mutation, EmptySubscription); - - let query = r#" - { - __type(name: "TestScalar") { - kind - name - description - } - } - "#; - - let res_json = value!({ - "__type": { - "kind": "SCALAR", - "name": "TestScalar", - "description": "Test scalar", - } - }); - - let res = schema.execute(query).await.into_result().unwrap().data; - - assert_eq!(res, res_json) -} - -#[tokio::test] -pub async fn test_introspection_union() { - let schema = Schema::new(Query, Mutation, EmptySubscription); - - let query = r#" - { - __type(name: "TestUnion") { - kind - name - description - possibleTypes { name } - } - } - "#; - - let res_json = value!({ - "__type": { - "kind": "UNION", - "name": "TestUnion", - "description": "Test Union", - "possibleTypes": [ - { - "name": "Circle" - }, - { - "name": "Square" - } - ] - } - }); - - let res = schema.execute(query).await.into_result().unwrap().data; - - assert_eq!(res, res_json) -} - -#[tokio::test] -pub async fn test_introspection_interface() { - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - // Test if possibleTypes and other fields are set - let mut query = r#" - { - __type(name: "TestInterface") { - kind - name - description - possibleTypes { name } - fields { name } - } - } - "#; - - let mut res_json = value!({ - "__type": { - "kind": "INTERFACE", - "name": "TestInterface", - "description": null, - "possibleTypes": [ - { - "name": "Circle" - }, - { - "name": "Square" - } - ], - "fields": [ - { - "name": "scale" - } - ] - } - }); - - let mut res = schema.execute(query).await.into_result().unwrap().data; - - assert_eq!(res, res_json); - - // Test if interfaces and other fields are set - query = r#" - { - __type(name: "Circle") { - kind - name - description - interfaces { name } - fields { name } - } - } - "#; - - res_json = value!({ - "__type": { - "kind": "OBJECT", - "name": "Circle", - "description": "Circle", - "interfaces": [ - { - "name": "TestInterface" - } - ], - "fields": [ - { - "name": "scale" - } - ] - } - }); - - res = schema.execute(query).await.into_result().unwrap().data; - - assert_eq!(res, res_json); -} - -#[tokio::test] -pub async fn test_introspection_enum() { - let schema = Schema::new(Query, Mutation, EmptySubscription); - - let query = r#" - { - __type(name: "TestEnum") { - kind - name - description - enumValues(includeDeprecated: true) { - name - description - isDeprecated - deprecationReason - } - } - } - "#; - - let res_json = value!({ - "__type": { - "kind": "ENUM", - "name": "TestEnum", - "description": "Test Enum", - "enumValues": [ - { - "name": "KIND_1", - "description": "Kind 1", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "KIND_2", - "description": "Kind 2", - "isDeprecated": true, - "deprecationReason": "Kind 2 deprecated" - } - ] - } - }); - - let res = schema.execute(query).await.into_result().unwrap().data; - - println!("{}", serde_json::to_string_pretty(&res).unwrap()); - - assert_eq!(res, res_json) -} - -#[tokio::test] -pub async fn test_introspection_input_object() { - let schema = Schema::new(Query, Mutation, EmptySubscription); - - let query = r#" - { - __type(name: "SimpleInput") { - kind - name - description - inputFields { name } - } - } - "#; - - let res_json = value!({ - "__type": { - "kind": "INPUT_OBJECT", - "name": "SimpleInput", - "description": "Simple Input", - "inputFields": [ - { - "name": "a" - } - ], - } - }); - - let res = schema.execute(query).await.into_result().unwrap().data; - - assert_eq!(res, res_json) -} - -#[tokio::test] -pub async fn test_introspection_mutation() { - let schema = Schema::new(Query, Mutation, EmptySubscription); - - let query = r#" - { - __type(name: "Mutation") { - name - kind - description - fields { - description - name - type { kind name } - args { name } - } - } - } - "#; - - let res_json = value!({ - "__type": { - "name": "Mutation", - "kind": "OBJECT", - "description": "Global mutation", - "fields": [ - { - "description": "simple_mutation description\nline2\nline3", - "name": "simpleMutation", - "type": { - "kind": "NON_NULL", - "name": null - }, - "args": [ - { - "name": "input" - } - ] - } - ] - } - }); - - let res = schema.execute(query).await.into_result().unwrap().data; - - assert_eq!(res, res_json) -} - -#[tokio::test] -pub async fn test_introspection_subscription() { - let schema = Schema::new(Query, EmptyMutation, Subscription); - - let query = r#" - { - __type(name: "Subscription") { - name - kind - description - fields { - description - name - type { kind name } - args { name } - } - } - } - "#; - - let res_json = value!({ - "__type": { - "name": "Subscription", - "kind": "OBJECT", - "description": "Global subscription", - "fields": [ - { - "description": "simple_subscription description", - "name": "simpleSubscription", - "type": { - "kind": "NON_NULL", - "name": null - }, - "args": [ - { - "name": "step" - } - ] - } - ] - } - }); - - let res = schema.execute(query).await.into_result().unwrap().data; - - assert_eq!(res, res_json) -} - -// #[tokio::test] -// pub async fn test_introspection_full() { -// let schema = Schema::new(Query, EmptyMutation, Subscription); -// -// let query = r#" -// { -// __type(name: "SimpleObject") { -// kind -// name -// description -// fields(includeDeprecated: true) { -// name -// description -// args { name } -// type { name kind ofType { name kind } } -// isDeprecated -// deprecationReason -// } -// interfaces { name } -// possibleTypes { name } -// enumValues { name } -// inputFields { name } -// ofType { name } -// } -// } -// "#; -// -// let res_json = value!({ -// "__type": { -// "kind": "OBJECT", -// "name": "SimpleObject", -// "description": "Is SimpleObject", -// "fields": [ -// { -// "name": "a", -// "description": "Value a with # 'some' `markdown`\".\"", -// "args": [], -// "type": { -// "name": null, -// "kind": "NON_NULL", -// "ofType": { -// "name": "Int", -// "kind": "SCALAR" -// } -// }, -// "isDeprecated": false, -// "deprecationReason": null -// }, -// { -// "name": "b", -// "description": "Value b description", -// "args": [], -// "type": { -// "name": null, -// "kind": "NON_NULL", -// "ofType": { -// "name": "String", -// "kind": "SCALAR" -// } -// }, -// "isDeprecated": false, -// "deprecationReason": null -// }, -// { -// "name": "c", -// "description": "Value c description", -// "args": [], -// "type": { -// "name": null, -// "kind": "NON_NULL", -// "ofType": { -// "name": "ID", -// "kind": "SCALAR" -// } -// }, -// "isDeprecated": false, -// "deprecationReason": null -// }, -// { -// "name": "d", -// "description": "", -// "args": [], -// "type": { -// "name": null, -// "kind": "NON_NULL", -// "ofType": { -// "name": "SimpleOption", -// "kind": "OBJECT" -// } -// }, -// "isDeprecated": false, -// "deprecationReason": null -// }, -// { -// "name": "e", -// "description": null, -// "args": [], -// "type": { -// "name": null, -// "kind": "NON_NULL", -// "ofType": { -// "name": "Boolean", -// "kind": "SCALAR" -// } -// }, -// "isDeprecated": true, -// "deprecationReason": "Field e is deprecated" -// }, -// { -// "name": "f", -// "description": null, -// "args": [], -// "type": { -// "name": null, -// "kind": "NON_NULL", -// "ofType": { -// "name": "TestEnum", -// "kind": "ENUM" -// } -// }, -// "isDeprecated": false, -// "deprecationReason": null -// }, -// { -// "name": "g", -// "description": null, -// "args": [], -// "type": { -// "name": null, -// "kind": "NON_NULL", -// "ofType": { -// "name": "TestInterface", -// "kind": "INTERFACE" -// } -// }, -// "isDeprecated": false, -// "deprecationReason": null -// }, -// { -// "name": "h", -// "description": null, -// "args": [], -// "type": { -// "name": null, -// "kind": "NON_NULL", -// "ofType": { -// "name": "TestUnion", -// "kind": "UNION" -// } -// }, -// "isDeprecated": false, -// "deprecationReason": null -// }, -// { -// "name": "i", -// "description": null, -// "args": [], -// "type": { -// "name": null, -// "kind": "NON_NULL", -// "ofType": { -// "name": "SimpleList", -// "kind": "OBJECT" -// } -// }, -// "isDeprecated": false, -// "deprecationReason": null -// }, -// { -// "name": "j", -// "description": null, -// "args": [], -// "type": { -// "name": null, -// "kind": "NON_NULL", -// "ofType": { -// "name": "TestScalar", -// "kind": "SCALAR" -// } -// }, -// "isDeprecated": false, -// "deprecationReason": null -// } -// ], -// "interfaces": [], -// "possibleTypes": null, -// "enumValues": null, -// "inputFields": null, -// "ofType": null -// } -// }); -// -// let res = schema.execute(query).await.unwrap().data; -// -// // pretty print result -// // println!("{}", serde_json::to_string_pretty(&res).unwrap()); -// -// assert_eq!(res, res_json) -// } - -#[tokio::test] -pub async fn test_disable_introspection() { - #[derive(SimpleObject)] - struct Query { - value: i32, - } - - let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .disable_introspection() - .finish(); - assert_eq!( - schema - .execute("{ __type(name: \"Query\") { name } }") - .await - .into_result() - .unwrap() - .data, - value!({ "__type": null }) - ); - - assert_eq!( - schema - .execute(Request::new("{ __type(name: \"Query\") { name } }").disable_introspection()) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": null }) - ); - - let schema = Schema::build(Query, EmptyMutation, EmptySubscription).finish(); - assert_eq!( - schema - .execute("{ __type(name: \"Query\") { name } }") - .await - .into_result() - .unwrap() - .data, - value!({ "__type": { "name": "Query" } }) - ); - - assert_eq!( - schema - .execute(Request::new("{ __type(name: \"Query\") { name } }").disable_introspection()) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": null }) - ); -} - -#[tokio::test] -pub async fn test_introspection_only() { - let schema = Schema::build(Query, Mutation, EmptySubscription) - .introspection_only() - .finish(); - - // Test whether introspection works. - let query = r#" - { - __type(name: "Mutation") { - name - kind - description - fields { - description - name - type { kind name } - args { name } - } - } - } - "#; - let res_json = value!({ - "__type": { - "name": "Mutation", - "kind": "OBJECT", - "description": "Global mutation", - "fields": [ - { - "description": "simple_mutation description\nline2\nline3", - "name": "simpleMutation", - "type": { - "kind": "NON_NULL", - "name": null - }, - "args": [ - { - "name": "input" - } - ] - } - ] - } - }); - let res = schema.execute(query).await.into_result().unwrap().data; - assert_eq!(res, res_json); - - // Test whether introspection works. - let query = r#" - { - __type(name: "Query") { - name - kind - description - fields { - description - name - type { kind name } - args { name } - } - } - } - "#; - let res_json = value!({ - "__type": { - "name": "Query", - "kind": "OBJECT", - "description": "Global query", - "fields": [ - { - "description": "Get a simple object", - "name": "simpleObject", - "type": { "kind": "NON_NULL", "name": null }, - "args": [] - } - ] - } - }); - let res = schema.execute(query).await.into_result().unwrap().data; - assert_eq!(res, res_json); - - // Queries shouldn't work in introspection only mode. - let query = r#" - { - simpleObject { - a - } - } - "#; - let res_json = value!({ "simpleObject": null }); - let res = schema.execute(query).await.into_result().unwrap().data; - assert_eq!(res, res_json); - - // Mutations shouldn't work in introspection only mode. - let query = r#" - mutation { - simpleMutation(input: { a: "" }) { - a - } - } - "#; - let res_json = value!({ "simpleMutation": null }); - let res = schema.execute(query).await.into_result().unwrap().data; - assert_eq!(res, res_json); -} - -#[tokio::test] -pub async fn test_introspection_default() { - #[derive(serde::Serialize, serde::Deserialize, Default)] - pub struct MyStruct { - a: i32, - b: i32, - } - - #[derive(InputObject)] - pub struct DefaultInput { - #[graphql(default)] - pub str: String, - #[graphql(default_with = "NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11)")] - pub date: NaiveDateTime, - // a required json with no default - pub json: serde_json::Value, - // basic default (JSON null) - #[graphql(default)] - pub json_def: serde_json::Value, - // complex default (JSON object) - #[graphql(default_with = "serde_json::Value::Object(Default::default())")] - pub json_def_obj: serde_json::Value, - #[graphql(default)] - pub json_def_struct: Json, - } - - struct LocalMutation; - - #[Object] - #[allow(unreachable_code)] - impl LocalMutation { - async fn simple_mutation(&self, _input: DefaultInput) -> SimpleObject { - unimplemented!() - } - } - - let schema = Schema::build(Query, LocalMutation, EmptySubscription) - .introspection_only() - .finish(); - - // Test whether introspection works. - let query = r#" - { - __type(name: "DefaultInput") { - name - kind - inputFields { - name - defaultValue - type { kind ofType { kind name } } - } - } - } - "#; - let res_json = value!({ - "__type": { - "name": "DefaultInput", - "kind": "INPUT_OBJECT", - "inputFields": [ - { - "name": "str", - "defaultValue": "\"\"", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "String" - }, - }, - }, - { - "name": "date", - "defaultValue": "\"2016-07-08T09:10:11\"", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "NaiveDateTime" - }, - }, - }, - { - "name": "json", - "defaultValue": null, - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "JSON" - }, - }, - }, - { - "name": "jsonDef", - "defaultValue": "\"null\"", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "JSON" - }, - }, - }, - { - "name": "jsonDefObj", - "defaultValue": "\"{}\"", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "JSON" - }, - }, - }, - { - "name": "jsonDefStruct", - "defaultValue": "\"{\\\"a\\\":0,\\\"b\\\":0}\"", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "JSON" - }, - }, - }, - ] - } - }); - let res = schema.execute(query).await.into_result().unwrap().data; - assert_eq!(res, res_json); -} - -#[tokio::test] -pub async fn test_introspection_directives() { - struct Query; - - #[Object] - impl Query { - pub async fn a(&self) -> String { - "a".into() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = r#" - query IntrospectionQuery { - __schema { - directives { - name - locations - args { - ...InputValue - } - } - } - } - - fragment InputValue on __InputValue { - name - type { - ...TypeRef - } - defaultValue - } - - fragment TypeRef on __Type { - kind - name - ofType { - kind - name - } - } - "#; - - let res_json = value!({"__schema": { - "directives": [ - { - "name": "deprecated", - "locations": [ - "FIELD_DEFINITION", - "ARGUMENT_DEFINITION", - "INPUT_FIELD_DEFINITION", - "ENUM_VALUE" - ], - "args": [ - { - "name": "reason", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": "\"No longer supported\"" - } - ] - }, - { - "name": "include", - "locations": [ - "FIELD", - "FRAGMENT_SPREAD", - "INLINE_FRAGMENT" - ], - "args": [ - { - "name": "if", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean" - } - }, - "defaultValue": null - } - ] - }, - { - "name": "oneOf", - "locations": [ - "INPUT_OBJECT" - ], - "args": [] - }, - { - "name": "skip", - "locations": [ - "FIELD", - "FRAGMENT_SPREAD", - "INLINE_FRAGMENT" - ], - "args": [ - { - "name": "if", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean" - } - }, - "defaultValue": null - } - ] - }, - { - "name": "specifiedBy", - "locations": [ - "SCALAR" - ], - "args": [ - { - "name": "url", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String" - } - }, - "defaultValue": null - } - ] - } - ] - }}); - let res = schema.execute(query).await.into_result().unwrap().data; - - assert_eq!(res, res_json); -} diff --git a/tests/introspection_visible.rs b/tests/introspection_visible.rs deleted file mode 100644 index 325137403..000000000 --- a/tests/introspection_visible.rs +++ /dev/null @@ -1,669 +0,0 @@ -#![allow(clippy::diverging_sub_expression)] - -use async_graphql::*; -use futures_util::Stream; -use serde::Deserialize; - -#[tokio::test] -pub async fn test_type_visible() { - #[derive(SimpleObject)] - #[graphql(visible = false)] - struct MyObj { - a: i32, - } - - struct Query; - - #[Object] - #[allow(unreachable_code)] - impl Query { - async fn obj(&self) -> MyObj { - todo!() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - assert_eq!( - schema - .execute(r#"{ __type(name: "MyObj") { name } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ - "__type": null, - }) - ); - - #[derive(Deserialize)] - struct QueryResponse { - #[serde(rename = "__schema")] - schema: SchemaResponse, - } - - #[derive(Deserialize)] - struct SchemaResponse { - types: Vec, - } - - #[derive(Deserialize)] - struct TypeResponse { - name: String, - } - - let resp: QueryResponse = from_value( - schema - .execute(r#"{ __schema { types { name } } }"#) - .await - .into_result() - .unwrap() - .data, - ) - .unwrap(); - - assert!(!resp.schema.types.into_iter().any(|ty| ty.name == "MyObj")); -} - -#[tokio::test] -pub async fn test_field_visible() { - #[derive(SimpleObject)] - struct MyObj { - a: i32, - #[graphql(visible = false)] - b: i32, - } - - struct Query; - - #[Object] - #[allow(unreachable_code)] - impl Query { - async fn obj(&self) -> MyObj { - todo!() - } - - #[graphql(visible = false)] - async fn c(&self) -> i32 { - todo!() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - #[derive(Debug, Deserialize)] - struct QueryResponse { - #[serde(rename = "__type")] - ty: TypeResponse, - } - - #[derive(Debug, Deserialize)] - struct TypeResponse { - fields: Vec, - } - - #[derive(Debug, Deserialize)] - struct FieldResponse { - name: String, - } - - let resp: QueryResponse = from_value( - schema - .execute(r#"{ __type(name: "MyObj") { fields { name } } }"#) - .await - .into_result() - .unwrap() - .data, - ) - .unwrap(); - assert_eq!( - resp.ty - .fields - .iter() - .map(|field| field.name.as_str()) - .collect::>(), - vec!["a"] - ); - - let resp: QueryResponse = from_value( - schema - .execute(r#"{ __type(name: "Query") { fields { name } } }"#) - .await - .into_result() - .unwrap() - .data, - ) - .unwrap(); - assert_eq!( - resp.ty - .fields - .iter() - .map(|field| field.name.as_str()) - .collect::>(), - vec!["obj"] - ); -} - -#[tokio::test] -pub async fn test_enum_value_visible() { - #[derive(Enum, Eq, PartialEq, Copy, Clone)] - enum MyEnum { - A, - B, - #[graphql(visible = false)] - C, - } - - struct Query; - - #[Object] - #[allow(unreachable_code)] - impl Query { - async fn e(&self) -> MyEnum { - todo!() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - #[derive(Debug, Deserialize)] - struct QueryResponse { - #[serde(rename = "__type")] - ty: TypeResponse, - } - - #[derive(Debug, Deserialize)] - #[serde(rename_all = "camelCase")] - struct TypeResponse { - enum_values: Vec, - } - - #[derive(Debug, Deserialize)] - struct EnumValueResponse { - name: String, - } - - let resp: QueryResponse = from_value( - schema - .execute(r#"{ __type(name: "MyEnum") { enumValues { name } } }"#) - .await - .into_result() - .unwrap() - .data, - ) - .unwrap(); - assert_eq!( - resp.ty - .enum_values - .iter() - .map(|value| value.name.as_str()) - .collect::>(), - vec!["A", "B"] - ); -} - -#[tokio::test] -pub async fn test_visible_fn() { - mod nested { - use async_graphql::Context; - - pub struct IsAdmin(pub bool); - - pub fn is_admin(ctx: &Context<'_>) -> bool { - ctx.data_unchecked::().0 - } - } - - use nested::IsAdmin; - - #[derive(SimpleObject)] - #[graphql(visible = "nested::is_admin")] - struct MyObj { - a: i32, - } - - struct Query; - - #[Object] - #[allow(unreachable_code)] - impl Query { - async fn obj(&self) -> MyObj { - todo!() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - assert_eq!( - schema - .execute(Request::new(r#"{ __type(name: "MyObj") { name } }"#).data(IsAdmin(false))) - .await - .into_result() - .unwrap() - .data, - value!({ - "__type": null, - }) - ); - - assert_eq!( - schema - .execute(Request::new(r#"{ __type(name: "MyObj") { name } }"#).data(IsAdmin(true))) - .await - .into_result() - .unwrap() - .data, - value!({ - "__type": { - "name": "MyObj", - }, - }) - ); -} - -#[tokio::test] -pub async fn test_indirect_hiding_type() { - #[derive(Enum, Eq, PartialEq, Copy, Clone)] - enum MyEnum1 { - A, - } - - #[derive(Enum, Eq, PartialEq, Copy, Clone)] - enum MyEnum2 { - A, - } - - struct MyDirective; - - impl CustomDirective for MyDirective {} - - #[Directive(location = "Field")] - fn my_directive1(_a: MyEnum1) -> impl CustomDirective { - MyDirective - } - - #[Directive(location = "Field", visible = false)] - fn my_directive2(_a: MyEnum2) -> impl CustomDirective { - MyDirective - } - - #[derive(SimpleObject)] - struct MyObj1 { - a: i32, - b: MyObj2, - c: MyObj3, - } - - #[derive(SimpleObject)] - struct MyObj2 { - a: i32, - } - - #[derive(SimpleObject)] - struct MyObj3 { - a: i32, - #[graphql(visible = false)] - b: MyObj5, - } - - #[derive(SimpleObject)] - #[graphql(visible = false)] - struct MyObj4 { - a: i32, - } - - #[derive(SimpleObject)] - struct MyObj5 { - a: i32, - } - - #[derive(InputObject)] - struct MyInputObj1 { - a: i32, - b: MyInputObj2, - c: MyInputObj3, - } - - #[derive(InputObject)] - struct MyInputObj2 { - a: i32, - } - - #[derive(InputObject)] - struct MyInputObj3 { - a: i32, - #[graphql(visible = false)] - b: MyInputObj4, - } - - #[derive(InputObject)] - struct MyInputObj4 { - a: i32, - } - - #[derive(InputObject)] - struct MyInputObj5 { - a: i32, - } - - #[derive(Union)] - enum MyUnion { - MyObj3(MyObj3), - MyObj4(MyObj4), - } - - #[derive(Interface)] - #[graphql(field(name = "a", ty = "&i32"))] - enum MyInterface { - MyObj3(MyObj3), - MyObj4(MyObj4), - } - - #[derive(Interface)] - #[graphql(visible = false, field(name = "a", ty = "&i32"))] - enum MyInterface2 { - MyObj3(MyObj3), - MyObj4(MyObj4), - } - - struct Query; - - #[Object] - #[allow(unreachable_code)] - impl Query { - #[graphql(visible = false)] - async fn obj1(&self) -> MyObj1 { - todo!() - } - - async fn obj3(&self) -> MyObj3 { - todo!() - } - - #[graphql(visible = false)] - async fn input_obj1(&self, _obj: MyInputObj1) -> i32 { - todo!() - } - - async fn input_obj3(&self, _obj: MyInputObj3) -> i32 { - todo!() - } - - async fn input_obj5(&self, #[graphql(visible = false)] _obj: Option) -> i32 { - todo!() - } - - async fn union1(&self) -> MyUnion { - todo!() - } - - async fn interface1(&self) -> MyInterface { - todo!() - } - - async fn interface2(&self) -> MyInterface2 { - todo!() - } - } - - let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .directive(my_directive1) - .directive(my_directive2) - .finish(); - assert_eq!( - schema - .execute(r#"{ __type(name: "MyObj1") { name } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": null }) - ); - assert_eq!( - schema - .execute(r#"{ __type(name: "MyObj2") { name } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": null }) - ); - assert_eq!( - schema - .execute(r#"{ __type(name: "MyObj3") { name } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": { "name": "MyObj3" } }) - ); - - assert_eq!( - schema - .execute(r#"{ __type(name: "MyInputObj1") { name } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": null }) - ); - assert_eq!( - schema - .execute(r#"{ __type(name: "MyInputObj2") { name } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": null }) - ); - assert_eq!( - schema - .execute(r#"{ __type(name: "MyInputObj3") { name } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": { "name": "MyInputObj3" } }) - ); - - assert_eq!( - schema - .execute(r#"{ __type(name: "MyUnion") { possibleTypes { name } } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": { "possibleTypes": [{ "name": "MyObj3" }] } }) - ); - - assert_eq!( - schema - .execute(r#"{ __type(name: "MyInterface") { possibleTypes { name } } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": { "possibleTypes": [{ "name": "MyObj3" }] } }) - ); - - assert_eq!( - schema - .execute(r#"{ __type(name: "MyInterface2") { name } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": null }) - ); - - assert_eq!( - schema - .execute(r#"{ __type(name: "MyObj3") { interfaces { name } } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": { "interfaces": [{ "name": "MyInterface" }] } }) - ); - - assert_eq!( - schema - .execute(r#"{ __type(name: "MyObj3") { fields { name } } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": { "fields": [ - { "name": "a" }, - ]}}) - ); - - assert_eq!( - schema - .execute(r#"{ __type(name: "MyObj5") { name } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": null }) - ); - - assert_eq!( - schema - .execute(r#"{ __type(name: "MyInputObj3") { inputFields { name } } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": { "inputFields": [ - { "name": "a" }, - ]}}) - ); - - assert_eq!( - schema - .execute(r#"{ __type(name: "MyInputObj4") { name } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": null }) - ); - - assert_eq!( - schema - .execute(r#"{ __type(name: "MyInputObj5") { name } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": null }) - ); - - assert_eq!( - schema - .execute(r#"{ __type(name: "MyEnum1") { name } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": { "name": "MyEnum1" } }) - ); - - assert_eq!( - schema - .execute(r#"{ __type(name: "MyEnum2") { name } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": null }) - ); -} - -#[tokio::test] -pub async fn root() { - struct Query; - - #[Object] - #[allow(unreachable_code)] - impl Query { - async fn value(&self) -> i32 { - todo!() - } - } - - struct Mutation; - - #[Object(visible = false)] - #[allow(unreachable_code)] - impl Mutation { - async fn value(&self) -> i32 { - todo!() - } - } - - struct Subscription; - - #[Subscription(visible = false)] - #[allow(unreachable_code)] - impl Subscription { - async fn value(&self) -> impl Stream { - futures_util::stream::iter(vec![1, 2, 3]) - } - } - - let schema = Schema::new(Query, Mutation, Subscription); - assert_eq!( - schema - .execute(r#"{ __type(name: "Mutation") { name } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": null }) - ); - - let schema = Schema::new(Query, Mutation, Subscription); - assert_eq!( - schema - .execute(r#"{ __schema { mutationType { name } } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__schema": { "mutationType": null } }) - ); - - assert_eq!( - schema - .execute(r#"{ __type(name: "Subscription") { name } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__type": null }) - ); - - let schema = Schema::new(Query, Mutation, Subscription); - assert_eq!( - schema - .execute(r#"{ __schema { subscriptionType { name } } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__schema": { "subscriptionType": null } }) - ); - - let schema = Schema::new(Query, Mutation, Subscription); - assert_eq!( - schema - .execute(r#"{ __schema { queryType { name } } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ "__schema": { "queryType": { "name": "Query" } } }) - ); -} diff --git a/tests/json_type.rs b/tests/json_type.rs deleted file mode 100644 index 906541481..000000000 --- a/tests/json_type.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::collections::HashMap; - -use async_graphql::*; - -#[tokio::test] -pub async fn test_json_scalar() { - #[derive(serde::Serialize, serde::Deserialize)] - struct MyData(HashMap); - - #[derive(serde::Serialize, Clone)] - struct MyDataOutput(HashMap); - - struct Query { - data: MyDataOutput, - } - - #[Object] - impl Query { - async fn data(&self) -> Json { - let mut items = HashMap::new(); - items.insert("a".to_string(), 10); - items.insert("b".to_string(), 20); - Json(MyData(items)) - } - - async fn data_output(&self) -> Json<&MyDataOutput> { - Json(&self.data) - } - - async fn data_output_clone(&self) -> Json { - Json(self.data.clone()) - } - } - - let schema = Schema::new( - Query { - data: { - let mut items = HashMap::new(); - items.insert("a".to_string(), 10); - items.insert("b".to_string(), 20); - MyDataOutput(items) - }, - }, - EmptyMutation, - EmptySubscription, - ); - let query = r#"{ data dataOutput dataOutputClone }"#; - assert_eq!( - schema.execute(query).await.data, - value!({ - "data": { "a": 10, "b": 20}, - "dataOutput": { "a": 10, "b": 20}, - "dataOutputClone": { "a": 10, "b": 20}, - }) - ); -} diff --git a/tests/lifetime.rs b/tests/lifetime.rs deleted file mode 100644 index 23bb6f91b..000000000 --- a/tests/lifetime.rs +++ /dev/null @@ -1,30 +0,0 @@ -#![allow(clippy::diverging_sub_expression)] - -use async_graphql::*; -use static_assertions_next::_core::marker::PhantomData; - -#[derive(SimpleObject)] -struct ObjA<'a> { - value: &'a i32, -} - -struct ObjB<'a>(PhantomData<&'a i32>); - -#[Object] -#[allow(unreachable_code)] -impl<'a> ObjB<'a> { - async fn value(&self) -> &'a i32 { - todo!() - } -} - -#[derive(Union)] -enum MyUnion1<'a> { - ObjA(ObjA<'a>), -} - -#[derive(Interface)] -#[graphql(field(name = "value", ty = "&&'a i32"))] -enum MyInterface<'a> { - ObjA(ObjA<'a>), -} diff --git a/tests/list.rs b/tests/list.rs deleted file mode 100644 index 6f3bd2253..000000000 --- a/tests/list.rs +++ /dev/null @@ -1,184 +0,0 @@ -#![allow(clippy::uninlined_format_args)] - -use std::{ - cmp::Ordering, - collections::{BTreeSet, HashSet, LinkedList, VecDeque}, -}; - -use async_graphql::*; - -#[tokio::test] -pub async fn test_list_type() { - #[derive(InputObject)] - struct MyInput { - value: Vec, - } - - struct Root { - value_vec: Vec, - value_hash_set: HashSet, - value_btree_set: BTreeSet, - value_linked_list: LinkedList, - value_vec_deque: VecDeque, - } - - #[Object] - impl Root { - async fn value_vec(&self) -> Vec { - self.value_vec.clone() - } - - async fn value_slice(&self) -> &[i32] { - &self.value_vec - } - - async fn value_linked_list(&self) -> LinkedList { - self.value_linked_list.clone() - } - - async fn value_hash_set(&self) -> HashSet { - self.value_hash_set.clone() - } - - async fn value_btree_set(&self) -> BTreeSet { - self.value_btree_set.clone() - } - - async fn value_vec_deque(&self) -> VecDeque { - self.value_vec_deque.clone() - } - - async fn value_input_slice(&self, a: Vec) -> Vec { - a - } - - async fn test_arg(&self, input: Vec) -> Vec { - input - } - - async fn test_input<'a>(&self, input: MyInput) -> Vec { - input.value - } - } - - let schema = Schema::new( - Root { - value_vec: vec![1, 2, 3, 4, 5], - value_hash_set: vec![1, 2, 3, 4, 5].into_iter().collect(), - value_btree_set: vec![1, 2, 3, 4, 5].into_iter().collect(), - value_linked_list: vec![1, 2, 3, 4, 5].into_iter().collect(), - value_vec_deque: vec![1, 2, 3, 4, 5].into_iter().collect(), - }, - EmptyMutation, - EmptySubscription, - ); - let json_value: serde_json::Value = vec![1, 2, 3, 4, 5].into(); - let query = format!( - r#"{{ - valueVec - valueSlice - valueLinkedList - valueHashSet - valueBtreeSet - valueVecDeque - testArg(input: {0}) - testInput(input: {{value: {0}}}) - valueInputSlice1: valueInputSlice(a: [1, 2, 3]) - valueInputSlice2: valueInputSlice(a: 55) - }} - "#, - json_value - ); - let mut res = schema.execute(&query).await.data; - - if let Value::Object(obj) = &mut res { - if let Some(Value::List(array)) = obj.get_mut("valueHashSet") { - array.sort_by(|a, b| { - if let (Value::Number(a), Value::Number(b)) = (a, b) { - if let (Some(a), Some(b)) = (a.as_i64(), b.as_i64()) { - return a.cmp(&b); - } - } - Ordering::Less - }); - } - } - - assert_eq!( - res, - value!({ - "valueVec": vec![1, 2, 3, 4, 5], - "valueSlice": vec![1, 2, 3, 4, 5], - "valueLinkedList": vec![1, 2, 3, 4, 5], - "valueHashSet": vec![1, 2, 3, 4, 5], - "valueBtreeSet": vec![1, 2, 3, 4, 5], - "valueVecDeque": vec![1, 2, 3, 4, 5], - "testArg": vec![1, 2, 3, 4, 5], - "testInput": vec![1, 2, 3, 4, 5], - "valueInputSlice1": vec![1, 2, 3], - "valueInputSlice2": vec![55], - }) - ); -} - -#[tokio::test] -pub async fn test_array_type() { - struct Query; - - #[Object] - impl Query { - async fn values(&self) -> [i32; 6] { - [1, 2, 3, 4, 5, 6] - } - - async fn array_input(&self, values: [i32; 6]) -> [i32; 6] { - assert_eq!(values, [1, 2, 3, 4, 5, 6]); - values - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - assert_eq!( - schema - .execute("{ values }") - .await - .into_result() - .unwrap() - .data, - value!({ - "values": [1, 2, 3, 4, 5, 6] - }) - ); - - assert_eq!( - schema - .execute("{ arrayInput(values: [1, 2, 3, 4, 5, 6]) }") - .await - .into_result() - .unwrap() - .data, - value!({ - "arrayInput": [1, 2, 3, 4, 5, 6] - }) - ); - - assert_eq!( - schema - .execute("{ arrayInput(values: [1, 2, 3, 4, 5]) }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "[Int!]": Expected input type "[Int; 6]", found [Int; 5]."# - .to_owned(), - source: None, - locations: vec![Pos { - line: 1, - column: 22, - }], - path: vec![PathSegment::Field("arrayInput".to_owned())], - extensions: None, - }], - ); -} diff --git a/tests/maybe_undefined.rs b/tests/maybe_undefined.rs deleted file mode 100644 index da68a8d26..000000000 --- a/tests/maybe_undefined.rs +++ /dev/null @@ -1,57 +0,0 @@ -use async_graphql::*; - -#[tokio::test] -pub async fn test_maybe_undefined_type() { - #[derive(InputObject)] - struct MyInput { - value: MaybeUndefined, - } - - struct Query; - - #[Object] - impl Query { - async fn value1(&self, input: MaybeUndefined) -> i32 { - if input.is_null() { - 1 - } else if input.is_undefined() { - 2 - } else { - input.take().unwrap() - } - } - - async fn value2(&self, input: MyInput) -> i32 { - if input.value.is_null() { - 1 - } else if input.value.is_undefined() { - 2 - } else { - input.value.take().unwrap() - } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = r#" - { - v1:value1(input: 99) - v2:value1(input: null) - v3:value1 - v4:value2(input: { value: 99} ) - v5:value2(input: { value: null} ) - v6:value2(input: {} ) - } - "#; - assert_eq!( - schema.execute(query).await.data, - value!({ - "v1": 99, - "v2": 1, - "v3": 2, - "v4": 99, - "v5": 1, - "v6": 2, - }) - ); -} diff --git a/tests/merged_object.rs b/tests/merged_object.rs deleted file mode 100644 index e3092015f..000000000 --- a/tests/merged_object.rs +++ /dev/null @@ -1,516 +0,0 @@ -use async_graphql::*; -use futures_util::stream::{Stream, StreamExt}; - -#[derive(SimpleObject)] -struct Object1 { - a: i32, -} - -#[derive(SimpleObject)] -struct Object2 { - b: i32, -} - -#[derive(SimpleObject)] -struct Object3 { - c: i32, -} - -#[tokio::test] -pub async fn test_merged_object_macro() { - #[derive(MergedObject)] - struct MyObj(Object1, Object2, Object3); - - struct Query; - - #[Object] - impl Query { - async fn obj(&self) -> MyObj { - MyObj(Object1 { a: 10 }, Object2 { b: 20 }, Object3 { c: 30 }) - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = "{ obj { a b c } }"; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "obj": { - "a": 10, - "b": 20, - "c": 30, - } - }) - ); -} - -#[tokio::test] -pub async fn test_merged_object_derive() { - #[derive(MergedObject)] - struct MyObj(Object1, Object2, Object3); - - struct Query; - - #[Object] - impl Query { - async fn obj(&self) -> MyObj { - MyObj(Object1 { a: 10 }, Object2 { b: 20 }, Object3 { c: 30 }) - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = "{ obj { a b c } }"; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "obj": { - "a": 10, - "b": 20, - "c": 30, - } - }) - ); -} - -#[tokio::test] -pub async fn test_merged_object_default() { - mod a { - use super::*; - - #[derive(SimpleObject)] - pub struct QueryA { - pub a: i32, - } - - impl Default for QueryA { - fn default() -> Self { - Self { a: 10 } - } - } - } - - mod b { - use super::*; - - #[derive(SimpleObject)] - pub struct QueryB { - pub b: i32, - } - - impl Default for QueryB { - fn default() -> Self { - Self { b: 20 } - } - } - } - - #[derive(MergedObject, Default)] - struct Query(a::QueryA, b::QueryB); - - let schema = Schema::new(Query::default(), EmptyMutation, EmptySubscription); - let query = "{ a b }"; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "a": 10, - "b": 20, - }) - ); -} - -#[tokio::test] -pub async fn test_merged_subscription() { - #[derive(Default)] - struct Subscription1; - - #[Subscription] - impl Subscription1 { - async fn events1(&self) -> impl Stream { - futures_util::stream::iter(0..10) - } - } - - #[derive(Default)] - struct Subscription2; - - #[Subscription] - impl Subscription2 { - async fn events2(&self) -> impl Stream { - futures_util::stream::iter(10..20) - } - } - - #[derive(MergedSubscription, Default)] - struct Subscription(Subscription1, Subscription2); - - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription::default()); - - { - let mut stream = schema - .execute_stream("subscription { events1 }") - .map(|resp| resp.into_result().unwrap().data); - for i in 0i32..10 { - assert_eq!( - value!({ - "events1": i, - }), - stream.next().await.unwrap() - ); - } - assert!(stream.next().await.is_none()); - } - - { - let mut stream = schema - .execute_stream("subscription { events2 }") - .map(|resp| resp.into_result().unwrap().data); - for i in 10i32..20 { - assert_eq!( - value!({ - "events2": i, - }), - stream.next().await.unwrap() - ); - } - assert!(stream.next().await.is_none()); - } -} - -#[tokio::test] -pub async fn test_generic_merged_subscription() { - struct Subscription1 { - values: Vec, - } - - #[Subscription] - impl Subscription1 - where - T: Clone + OutputType, - { - async fn events1(&self) -> impl Stream { - futures_util::stream::iter(self.values.clone()) - } - } - - struct Subscription2 { - values: Vec, - } - - #[Subscription] - impl Subscription2 - where - T: Clone + OutputType, - { - async fn events2(&self) -> impl Stream { - futures_util::stream::iter(self.values.clone()) - } - } - - #[derive(MergedSubscription)] - struct Subscription(Subscription1, Subscription2) - where - T1: Clone + OutputType, - T2: Clone + OutputType; - - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - let subscription = Subscription( - Subscription1 { - values: vec![1, 2, 3], - }, - Subscription2 { - values: vec!["a", "b", "c"], - }, - ); - let schema = Schema::new(Query, EmptyMutation, subscription); - - { - let mut stream = schema - .execute_stream("subscription { events1 }") - .map(|resp| resp.into_result().unwrap().data); - for i in &[1, 2, 3] { - assert_eq!( - value!({ - "events1": i, - }), - stream.next().await.unwrap() - ); - } - assert!(stream.next().await.is_none()); - } - - { - let mut stream = schema - .execute_stream("subscription { events2 }") - .map(|resp| resp.into_result().unwrap().data); - for i in ["a", "b", "c"] { - assert_eq!( - value!({ - "events2": i, - }), - stream.next().await.unwrap() - ); - } - assert!(stream.next().await.is_none()); - } -} - -#[tokio::test] -pub async fn test_merged_entity() { - #[derive(SimpleObject)] - struct Fruit { - id: ID, - name: String, - } - - #[derive(SimpleObject)] - struct Vegetable { - id: ID, - name: String, - } - - #[derive(Default)] - struct FruitQuery; - - #[Object] - impl FruitQuery { - #[graphql(entity)] - async fn get_fruit(&self, id: ID) -> Fruit { - Fruit { - id, - name: "Apple".into(), - } - } - } - - #[derive(Default)] - struct VegetableQuery; - - #[Object] - impl VegetableQuery { - #[graphql(entity)] - async fn get_vegetable(&self, id: ID) -> Vegetable { - Vegetable { - id, - name: "Carrot".into(), - } - } - } - - #[derive(MergedObject, Default)] - struct Query(FruitQuery, VegetableQuery); - - let schema = Schema::new(Query::default(), EmptyMutation, EmptySubscription); - let query = r#"{ - _entities(representations: [{__typename: "Fruit", id: "1"}]) { - __typename - ... on Fruit { - id - name - } - } - }"#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "_entities": [ - {"__typename": "Fruit", "id": "1", "name": "Apple"}, - ] - }) - ); -} - -#[tokio::test] -pub async fn test_issue_316() { - #[derive(SimpleObject)] - struct Fruit { - id: ID, - name: String, - } - - struct Query; - - #[Object] - impl Query { - #[graphql(entity)] - async fn get_fruit(&self, id: ID) -> Fruit { - Fruit { - id, - name: "Apple".into(), - } - } - } - - #[derive(Default)] - struct Mutation1; - - #[Object] - impl Mutation1 { - async fn action1(&self) -> Fruit { - Fruit { - id: ID("hello".into()), - name: "Apple".into(), - } - } - } - - #[derive(MergedObject, Default)] - struct Mutation(Mutation1); - - // This works - let schema = Schema::new(Query, Mutation1, EmptySubscription); - assert!(schema.execute("{ _service { sdl }}").await.is_ok()); - - // This fails - let schema = Schema::new(Query, Mutation::default(), EmptySubscription); - assert!(schema.execute("{ _service { sdl }}").await.is_ok()); -} - -#[tokio::test] -pub async fn test_issue_333() { - #[derive(SimpleObject)] - struct ObjectA<'a> { - field_a: &'a str, - } - - #[derive(SimpleObject)] - struct ObjectB<'a> { - field_b: &'a str, - } - - #[derive(MergedObject)] - pub struct Object<'a>(ObjectA<'a>, ObjectB<'a>); - - struct Query { - a: String, - b: String, - } - - #[Object] - impl Query { - async fn obj(&self) -> Object<'_> { - Object(ObjectA { field_a: &self.a }, ObjectB { field_b: &self.b }) - } - } - - let schema = Schema::new( - Query { - a: "haha".to_string(), - b: "hehe".to_string(), - }, - EmptyMutation, - EmptySubscription, - ); - assert_eq!( - schema - .execute("{ obj { fieldA fieldB } }") - .await - .into_result() - .unwrap() - .data, - value!({ - "obj": { - "fieldA": "haha", - "fieldB": "hehe", - } - }) - ) -} - -#[tokio::test] -pub async fn test_issue_539() { - // https://github.com/async-graphql/async-graphql/issues/539#issuecomment-862209442 - - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - #[derive(SimpleObject)] - struct A { - a: Option>, - } - - #[derive(SimpleObject)] - struct B { - b: Option>, - } - - #[derive(MergedObject)] - pub struct Mutation(A, B); - - let schema = Schema::new( - Query, - Mutation(A { a: None }, B { b: None }), - EmptySubscription, - ); - assert_eq!( - schema - .execute("{ __type(name: \"Mutation\") { fields { name type { name } } } }") - .await - .into_result() - .unwrap() - .data, - value!({ - "__type": { - "fields": [ - { - "name": "a", - "type": { "name": "A" }, - }, - { - "name": "b", - "type": { "name": "B" }, - } - ] - } - }) - ) -} - -#[tokio::test] -pub async fn test_issue_694() { - struct Query; - - #[Object] - impl Query { - async fn test(&self) -> bool { - true - } - } - - #[derive(MergedObject)] - pub struct QueryRoot(Query, EmptyMutation); - - let schema = Schema::new( - QueryRoot(Query, EmptyMutation), - EmptyMutation, - EmptySubscription, - ); - assert_eq!( - schema.execute("{ test }").await.into_result().unwrap().data, - value!({ - "test": true, - }) - ); -} diff --git a/tests/mut_args.rs b/tests/mut_args.rs deleted file mode 100644 index db9c7a997..000000000 --- a/tests/mut_args.rs +++ /dev/null @@ -1,25 +0,0 @@ -#![allow(clippy::uninlined_format_args)] - -use async_graphql::*; - -#[tokio::test] -pub async fn test_mut_args() { - struct Query; - - #[Object] - impl Query { - async fn test(&self, mut a: i32, mut b: String) -> String { - a += 1; - b += "a"; - format!("{}{}", a, b) - } - } - - let schema = Schema::build(Query, EmptyMutation, EmptySubscription).finish(); - assert_eq!( - schema.execute("{ test(a: 10, b: \"abc\") }").await.data, - value!({ - "test": "11abca" - }) - ); -} diff --git a/tests/mutation.rs b/tests/mutation.rs deleted file mode 100644 index 406f40437..000000000 --- a/tests/mutation.rs +++ /dev/null @@ -1,253 +0,0 @@ -use std::{sync::Arc, time::Duration}; - -use async_graphql::*; -use tokio::sync::Mutex; - -#[tokio::test] -pub async fn test_root_mutation_execution_order() { - type List = Arc>>; - - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Mutation; - - #[Object] - impl Mutation { - async fn append1(&self, ctx: &Context<'_>) -> bool { - tokio::time::sleep(Duration::from_secs(1)).await; - ctx.data_unchecked::().lock().await.push(1); - true - } - - async fn append2(&self, ctx: &Context<'_>) -> bool { - tokio::time::sleep(Duration::from_millis(500)).await; - ctx.data_unchecked::().lock().await.push(2); - true - } - } - - let list = List::default(); - let schema = Schema::build(Query, Mutation, EmptySubscription) - .data(list.clone()) - .finish(); - schema.execute("mutation { append1 append2 }").await; - assert_eq!(&*list.lock().await, &[1, 2]); -} - -#[tokio::test] -pub async fn test_mutation_fragment() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Mutation; - - #[Object] - impl Mutation { - async fn action(&self) -> bool { - true - } - } - - let schema = Schema::new(Query, Mutation, EmptySubscription); - let resp = schema - .execute( - r#" - mutation { - ... { - actionInUnnamedFragment: action - } - ... on Mutation { - actionInNamedFragment: action - } - }"#, - ) - .await; - assert_eq!( - resp.data, - value!({ - "actionInUnnamedFragment": true, - "actionInNamedFragment": true, - }) - ); -} - -#[tokio::test] -pub async fn test_serial_object() { - type List = Arc>>; - - struct MyObj; - - #[Object(serial)] - impl MyObj { - async fn append1(&self, ctx: &Context<'_>) -> bool { - tokio::time::sleep(Duration::from_secs(1)).await; - ctx.data_unchecked::().lock().await.push(1); - true - } - - async fn append2(&self, ctx: &Context<'_>) -> bool { - tokio::time::sleep(Duration::from_millis(500)).await; - ctx.data_unchecked::().lock().await.push(2); - true - } - } - - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Mutation; - - #[Object] - impl Mutation { - async fn obj(&self) -> MyObj { - MyObj - } - } - - let list = List::default(); - let schema = Schema::build(Query, Mutation, EmptySubscription) - .data(list.clone()) - .finish(); - schema.execute("mutation { obj { append1 append2 } }").await; - assert_eq!(&*list.lock().await, &[1, 2]); -} - -#[tokio::test] -pub async fn test_serial_simple_object() { - type List = Arc>>; - - #[derive(SimpleObject)] - #[graphql(complex, serial)] - struct MyObj { - value: i32, - } - - #[ComplexObject] - impl MyObj { - async fn append1(&self, ctx: &Context<'_>) -> bool { - tokio::time::sleep(Duration::from_secs(1)).await; - ctx.data_unchecked::().lock().await.push(1); - true - } - - async fn append2(&self, ctx: &Context<'_>) -> bool { - tokio::time::sleep(Duration::from_millis(500)).await; - ctx.data_unchecked::().lock().await.push(2); - true - } - } - - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Mutation; - - #[Object] - impl Mutation { - async fn obj(&self) -> MyObj { - MyObj { value: 10 } - } - } - - let list = List::default(); - let schema = Schema::build(Query, Mutation, EmptySubscription) - .data(list.clone()) - .finish(); - schema.execute("mutation { obj { append1 append2 } }").await; - assert_eq!(&*list.lock().await, &[1, 2]); -} - -#[tokio::test] -pub async fn test_serial_merged_object() { - type List = Arc>>; - - #[derive(MergedObject)] - #[graphql(serial)] - struct MyObj(MyObj1, MyObj2); - - struct MyObj1; - - #[Object] - impl MyObj1 { - async fn append1(&self, ctx: &Context<'_>) -> bool { - tokio::time::sleep(Duration::from_secs(1)).await; - ctx.data_unchecked::().lock().await.push(1); - true - } - - async fn append2(&self, ctx: &Context<'_>) -> bool { - tokio::time::sleep(Duration::from_millis(500)).await; - ctx.data_unchecked::().lock().await.push(2); - true - } - } - - struct MyObj2; - - #[Object] - impl MyObj2 { - async fn append3(&self, ctx: &Context<'_>) -> bool { - tokio::time::sleep(Duration::from_millis(200)).await; - ctx.data_unchecked::().lock().await.push(3); - true - } - - async fn append4(&self, ctx: &Context<'_>) -> bool { - tokio::time::sleep(Duration::from_millis(700)).await; - ctx.data_unchecked::().lock().await.push(4); - true - } - } - - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Mutation; - - #[Object] - impl Mutation { - async fn obj(&self) -> MyObj { - MyObj(MyObj1, MyObj2) - } - } - - let list = List::default(); - let schema = Schema::build(Query, Mutation, EmptySubscription) - .data(list.clone()) - .finish(); - schema - .execute("mutation { obj { append1 append2 append3 append4 } }") - .await; - assert_eq!(&*list.lock().await, &[1, 2, 3, 4]); -} diff --git a/tests/name_conflict.rs b/tests/name_conflict.rs deleted file mode 100644 index babf7a240..000000000 --- a/tests/name_conflict.rs +++ /dev/null @@ -1,265 +0,0 @@ -use async_graphql::*; - -#[test] -#[should_panic] -fn object() { - mod t { - use async_graphql::*; - - pub struct MyObj; - - #[Object] - impl MyObj { - async fn a(&self) -> i32 { - 1 - } - } - } - - struct MyObj; - - #[Object] - impl MyObj { - async fn b(&self) -> i32 { - 1 - } - } - - #[derive(SimpleObject)] - struct Query { - a: MyObj, - b: t::MyObj, - } - - Schema::new( - Query { - a: MyObj, - b: t::MyObj, - }, - EmptyMutation, - EmptySubscription, - ); -} - -#[test] -#[should_panic] -fn simple_object() { - mod t { - use async_graphql::*; - - #[derive(SimpleObject, Default)] - pub struct MyObj { - a: i32, - } - } - - #[derive(SimpleObject, Default)] - struct MyObj { - a: i32, - } - - #[derive(SimpleObject)] - struct Query { - a: MyObj, - b: t::MyObj, - } - - Schema::new( - Query { - a: MyObj::default(), - b: t::MyObj::default(), - }, - EmptyMutation, - EmptySubscription, - ); -} - -#[test] -#[should_panic] -fn merged_object() { - mod t { - use async_graphql::*; - - #[derive(SimpleObject, Default)] - pub struct Query { - a: i32, - } - } - - #[derive(SimpleObject, Default)] - struct Query { - a: i32, - } - - #[derive(MergedObject)] - struct QueryRoot(Query, t::Query); - - Schema::new( - QueryRoot(Query::default(), t::Query::default()), - EmptyMutation, - EmptySubscription, - ); -} - -#[test] -#[should_panic] -fn merged_object_root() { - mod example { - use async_graphql::{MergedObject, SimpleObject}; - - #[derive(SimpleObject, Default)] - pub struct Obj { - i: i32, - } - - #[derive(MergedObject, Default)] - pub struct Merged(Obj); - } - - #[derive(SimpleObject, Default)] - pub struct Obj2 { - j: i32, - } - - #[derive(MergedObject, Default)] - pub struct Merged(example::Merged, Obj2); - - Schema::new(Merged::default(), EmptyMutation, EmptySubscription); -} - -#[test] -#[should_panic] -fn enum_type() { - mod t { - use async_graphql::*; - - #[derive(Enum, Eq, PartialEq, Copy, Clone)] - pub enum MyEnum { - A, - } - } - - #[derive(Enum, Eq, PartialEq, Copy, Clone)] - enum MyEnum { - B, - } - - #[derive(SimpleObject)] - struct Query { - a: MyEnum, - b: t::MyEnum, - } - - Schema::new( - Query { - a: MyEnum::B, - b: t::MyEnum::A, - }, - EmptyMutation, - EmptySubscription, - ); -} - -#[test] -#[should_panic] -fn union() { - mod t { - use async_graphql::*; - - #[derive(SimpleObject, Default)] - pub struct ObjA { - a: i32, - } - - #[derive(SimpleObject, Default)] - pub struct ObjB { - a: i32, - } - - #[derive(SimpleObject, Default)] - pub struct ObjC { - a: i32, - } - - #[derive(Union)] - pub enum MyUnion { - ObjA(ObjA), - ObjB(ObjB), - } - } - - #[derive(Union)] - pub enum MyUnion { - ObjA(t::ObjA), - ObjB(t::ObjB), - ObjC(t::ObjC), - } - - #[derive(SimpleObject)] - struct Query { - a: MyUnion, - b: t::MyUnion, - } - - Schema::new( - Query { - a: MyUnion::ObjA(t::ObjA::default()), - b: t::MyUnion::ObjB(t::ObjB::default()), - }, - EmptyMutation, - EmptySubscription, - ); -} - -#[test] -#[should_panic] -fn interface() { - mod t { - use async_graphql::*; - - #[derive(SimpleObject, Default)] - pub struct ObjA { - pub a: i32, - } - - #[derive(SimpleObject, Default)] - pub struct ObjB { - pub a: i32, - } - - #[derive(SimpleObject, Default)] - pub struct ObjC { - pub a: i32, - } - - #[derive(Interface)] - #[graphql(field(name = "a", ty = "&i32"))] - pub enum MyInterface { - ObjA(ObjA), - ObjB(ObjB), - ObjC(ObjC), - } - } - - #[derive(Interface)] - #[graphql(field(name = "a", ty = "&i32"))] - enum MyInterface { - ObjA(t::ObjA), - ObjB(t::ObjB), - } - - #[derive(SimpleObject)] - struct Query { - a: MyInterface, - b: t::MyInterface, - } - - Schema::new( - Query { - a: MyInterface::ObjA(t::ObjA::default()), - b: t::MyInterface::ObjB(t::ObjB::default()), - }, - EmptyMutation, - EmptySubscription, - ); -} diff --git a/tests/object.rs b/tests/object.rs deleted file mode 100644 index deba33913..000000000 --- a/tests/object.rs +++ /dev/null @@ -1,296 +0,0 @@ -use async_graphql::*; - -#[tokio::test] -async fn test_flatten() { - #[derive(SimpleObject)] - struct A { - a: i32, - b: i32, - } - - struct B; - - #[Object] - impl B { - #[graphql(flatten)] - async fn a(&self) -> A { - A { a: 100, b: 200 } - } - - async fn c(&self) -> i32 { - 300 - } - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self) -> B { - B - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = "{ __type(name: \"B\") { fields { name } } }"; - assert_eq!( - schema.execute(query).await.data, - value!({ - "__type": { - "fields": [ - {"name": "a"}, - {"name": "b"}, - {"name": "c"} - ] - } - }) - ); - - let query = "{ obj { a b c } }"; - assert_eq!( - schema.execute(query).await.data, - value!({ - "obj": { - "a": 100, - "b": 200, - "c": 300, - } - }) - ); -} - -#[tokio::test] -async fn test_flatten_with_context() { - #[derive(SimpleObject)] - struct A { - a: i32, - b: i32, - } - - struct B; - - #[Object] - impl B { - #[graphql(flatten)] - async fn a(&self, _ctx: &Context<'_>) -> A { - A { a: 100, b: 200 } - } - - async fn c(&self) -> i32 { - 300 - } - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self) -> B { - B - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = "{ __type(name: \"B\") { fields { name } } }"; - assert_eq!( - schema.execute(query).await.data, - value!({ - "__type": { - "fields": [ - {"name": "a"}, - {"name": "b"}, - {"name": "c"} - ] - } - }) - ); - - let query = "{ obj { a b c } }"; - assert_eq!( - schema.execute(query).await.data, - value!({ - "obj": { - "a": 100, - "b": 200, - "c": 300, - } - }) - ); -} - -#[tokio::test] -async fn test_object_process_with_field() { - struct Query; - - #[Object] - impl Query { - async fn test( - &self, - #[graphql(process_with = "str::make_ascii_uppercase")] processed_arg: String, - ) -> String { - processed_arg - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = "{ test(processedArg: \"smol\") }"; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "test": "SMOL" - }) - ); -} - -#[tokio::test] -async fn ignore_name_conflicts() { - #[derive(SimpleObject)] - #[graphql(name = "MyObj")] - struct MyObj { - name: String, - } - - #[derive(SimpleObject)] - #[graphql(name = "MyObj")] - struct MyObjRef<'a> { - name: &'a str, - } - - struct Query; - - #[Object] - impl Query { - async fn obj_owned(&self) -> MyObj { - MyObj { - name: "a".to_string(), - } - } - - async fn obj_ref(&self) -> MyObjRef<'_> { - MyObjRef { name: "b" } - } - } - - let schema = Schema::build_with_ignore_name_conflicts( - Query, - EmptyMutation, - EmptySubscription, - ["MyObj"], - ) - .finish(); - - let query = r#" - { - objOwned { name } - objRef { name } - } - "#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "objOwned": { "name": "a" }, - "objRef": { "name": "b" }, - }) - ); -} - -#[tokio::test] -async fn test_impl_dyn_trait() { - use async_graphql::*; - - trait MyTrait: Send + Sync { - fn name(&self) -> &str; - } - - #[Object] - impl dyn MyTrait + '_ { - #[graphql(name = "name")] - async fn gql_name(&self) -> &str { - self.name() - } - } - - struct MyObj(String); - - impl MyTrait for MyObj { - fn name(&self) -> &str { - &self.0 - } - } - - struct Query; - - #[Object] - impl Query { - async fn objs(&self) -> Vec> { - vec![ - Box::new(MyObj("a".to_string())), - Box::new(MyObj("b".to_string())), - ] - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - let res = schema - .execute("{ objs { name } }") - .await - .into_result() - .unwrap() - .data; - assert_eq!( - res, - value!({ - "objs": [ - { "name": "a" }, - { "name": "b" }, - ] - }) - ); -} - -#[tokio::test] -async fn test_optional_output_with_try() { - struct B { - some: Option, - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self, ctx: &Context<'_>) -> Option { - let x = ctx.data_unchecked::(); - - if x.some? { Some(300) } else { None } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - let res = schema - .execute(Request::from("{ obj }").data(B { some: Some(true) })) - .await - .into_result() - .unwrap() - .data; - assert_eq!( - res, - value!({ - "obj": 300 , - }) - ); - - let res = schema - .execute(Request::from("{ obj }").data(B { some: None })) - .await - .into_result() - .unwrap() - .data; - assert_eq!( - res, - value!({ - "obj": null, - }) - ); -} diff --git a/tests/oneof_object.rs b/tests/oneof_object.rs deleted file mode 100644 index a0d504c3a..000000000 --- a/tests/oneof_object.rs +++ /dev/null @@ -1,347 +0,0 @@ -#![allow(clippy::uninlined_format_args)] - -use async_graphql::{ - registry::{MetaType, Registry}, - *, -}; - -#[tokio::test] -async fn test_oneof_object() { - #[derive(Debug, InputObject, PartialEq)] - struct MyInput { - a: i32, - b: String, - } - - #[derive(Debug, OneofObject, PartialEq)] - enum MyOneofObj { - A(i32), - B(MyInput), - } - - assert_eq!( - MyOneofObj::parse(Some(value!({ - "a": 100, - }))) - .unwrap(), - MyOneofObj::A(100) - ); - - assert_eq!( - MyOneofObj::A(100).to_value(), - value!({ - "a": 100, - }) - ); - - assert_eq!( - MyOneofObj::parse(Some(value!({ - "b": { - "a": 200, - "b": "abc", - }, - }))) - .unwrap(), - MyOneofObj::B(MyInput { - a: 200, - b: "abc".to_string() - }) - ); - - assert_eq!( - MyOneofObj::B(MyInput { - a: 200, - b: "abc".to_string() - }) - .to_value(), - value!({ - "b": { - "a": 200, - "b": "abc", - }, - }) - ); - - struct Query; - - #[Object] - impl Query { - async fn test(&self, obj: MyOneofObj) -> String { - match obj { - MyOneofObj::A(value) => format!("a:{}", value), - MyOneofObj::B(MyInput { a, b }) => format!("b:{}/{}", a, b), - } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - assert_eq!( - schema - .execute("{ test(obj: {a: 100}) }") - .await - .into_result() - .unwrap() - .data, - value!({ - "test": "a:100" - }) - ); - - assert_eq!( - schema - .execute(r#"{ test(obj: {b: {a: 200, b: "abc"}}) }"#) - .await - .into_result() - .unwrap() - .data, - value!({ - "test": "b:200/abc" - }) - ); - - assert_eq!( - schema - .execute(r#"{ __type(name: "MyOneofObj") { name isOneOf } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ - "__type": { "name": "MyOneofObj", "isOneOf": true } - }) - ); -} - -#[tokio::test] -async fn test_oneof_object_concrete() { - #[derive(Debug, OneofObject, PartialEq)] - #[graphql( - concrete(name = "MyObjI32", params(i32)), - concrete(name = "MyObjString", params(String)) - )] - enum MyObj { - A(i32), - B(T), - } - - assert_eq!(MyObj::::type_name(), "MyObjI32"); - assert_eq!(MyObj::::type_name(), "MyObjString"); - - assert_eq!( - MyObj::::parse(Some(value!({ - "a": 100, - }))) - .unwrap(), - MyObj::A(100) - ); - - assert_eq!( - MyObj::::A(100).to_value(), - value!({ - "a": 100, - }) - ); - - assert_eq!( - MyObj::::B("abc".to_string()).to_value(), - value!({ - "b": "abc", - }) - ); - - struct Query; - - #[Object] - impl Query { - async fn test(&self, obj: MyObj) -> String { - match obj { - MyObj::A(value) => format!("a:{}", value), - MyObj::B(value) => format!("b:{}", value), - } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - assert_eq!( - schema - .execute("{ test(obj: {a: 100}) }") - .await - .into_result() - .unwrap() - .data, - value!({ - "test": "a:100" - }) - ); - - assert_eq!( - schema - .execute(r#"{ test(obj: {b: "abc"}) }"#) - .await - .into_result() - .unwrap() - .data, - value!({ - "test": "b:abc" - }) - ); -} - -#[tokio::test] -async fn test_oneof_object_rename_fields() { - #[derive(OneofObject)] - #[graphql(rename_fields = "lowercase")] - enum MyInput { - Name(i32), - CreateAt(String), - } - - let mut registry = Registry::default(); - MyInput::create_type_info(&mut registry); - - let ty: &MetaType = registry.types.get("MyInput").unwrap(); - match ty { - MetaType::InputObject { input_fields, .. } => { - assert_eq!( - input_fields.keys().collect::>(), - vec!["name", "createat"] - ); - } - _ => unreachable!(), - } -} - -#[tokio::test] -async fn test_oneof_object_rename_field() { - #[derive(OneofObject)] - enum MyInput { - Name(i32), - #[graphql(name = "create_At")] - CreateAt(String), - } - - let mut registry = Registry::default(); - MyInput::create_type_info(&mut registry); - - let ty: &MetaType = registry.types.get("MyInput").unwrap(); - match ty { - MetaType::InputObject { input_fields, .. } => { - assert_eq!( - input_fields.keys().collect::>(), - vec!["name", "create_At"] - ); - } - _ => unreachable!(), - } -} - -#[tokio::test] -async fn test_oneof_object_validation() { - #[derive(Debug, OneofObject, PartialEq)] - enum MyOneofObj { - #[graphql(validator(maximum = 10))] - A(i32), - #[graphql(validator(max_length = 3))] - B(String), - } - - assert_eq!( - MyOneofObj::parse(Some(value!({ - "a": 5, - }))) - .unwrap(), - MyOneofObj::A(5) - ); - - assert_eq!( - MyOneofObj::parse(Some(value!({ - "a": 20, - }))) - .unwrap_err() - .into_server_error(Default::default()) - .message, - r#"Failed to parse "Int": the value is 20, must be less than or equal to 10 (occurred while parsing "MyOneofObj")"# - ); -} - -#[tokio::test] -async fn test_oneof_object_vec() { - use async_graphql::*; - - #[derive(SimpleObject)] - pub struct User { - name: String, - } - - #[derive(OneofObject)] - pub enum UserBy { - Email(String), - RegistrationNumber(i64), - } - - pub struct Query; - - #[Object] - impl Query { - async fn search_users(&self, by: Vec) -> Vec { - by.into_iter() - .map(|user| match user { - UserBy::Email(email) => email, - UserBy::RegistrationNumber(id) => format!("{}", id), - }) - .collect() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = r#" - { - searchUsers(by: [ - { email: "a@a.com" }, - { registrationNumber: 100 }, - { registrationNumber: 200 }, - ]) - } - "#; - let data = schema.execute(query).await.into_result().unwrap().data; - assert_eq!( - data, - value!({ - "searchUsers": [ - "a@a.com", "100", "200" - ] - }) - ); -} - -#[tokio::test] -async fn test_issue_923() { - #[derive(OneofObject)] - enum Filter { - Any(Vec), - All(Vec), - } - - pub struct Query; - - #[Object] - impl Query { - async fn query(&self, filter: Filter) -> bool { - match filter { - Filter::Any(values) => assert_eq!(values, vec!["a".to_string(), "b".to_string()]), - Filter::All(values) => assert_eq!(values, vec!["c".to_string(), "d".to_string()]), - } - true - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - let query = r#"{ query(filter: {any: ["a", "b"]}) }"#; - schema.execute(query).await.into_result().unwrap(); - - let query = r#"{ query(filter: {all: ["c", "d"]}) }"#; - schema.execute(query).await.into_result().unwrap(); -} diff --git a/tests/optional.rs b/tests/optional.rs deleted file mode 100644 index 82c4a3e0b..000000000 --- a/tests/optional.rs +++ /dev/null @@ -1,84 +0,0 @@ -use async_graphql::*; - -#[tokio::test] -pub async fn test_optional_type() { - #[derive(InputObject)] - struct MyInput { - value: Option, - } - - struct Root { - value1: Option, - value2: Option, - } - - #[Object] - impl Root { - async fn value1(&self) -> Option { - self.value1 - } - - async fn value1_ref(&self) -> &Option { - &self.value1 - } - - async fn value2(&self) -> Option { - self.value2 - } - - async fn value2_ref(&self) -> &Option { - &self.value2 - } - - async fn test_arg(&self, input: Option) -> Option { - input - } - - async fn test_arg2(&self, input: Option>) -> Option> { - input - } - - async fn test_input<'a>(&self, input: MyInput) -> Option { - input.value - } - } - - let schema = Schema::new( - Root { - value1: Some(10), - value2: None, - }, - EmptyMutation, - EmptySubscription, - ); - let query = r#"{ - value1 - value1Ref - value2 - value2Ref - testArg1: testArg(input: 10) - testArg2: testArg - testArg3: testArg(input: null) - testArg21: testArg2(input: null) - testArg22: testArg2(input: [1, 2, 3]) - testInput1: testInput(input: {value: 10}) - testInput2: testInput(input: {}) - }"# - .to_owned(); - assert_eq!( - schema.execute(&query).await.into_result().unwrap().data, - value!({ - "value1": 10, - "value1Ref": 10, - "value2": null, - "value2Ref": null, - "testArg1": 10, - "testArg2": null, - "testArg3": null, - "testArg21": null, - "testArg22": [1, 2, 3], - "testInput1": 10, - "testInput2": null, - }) - ); -} diff --git a/tests/preserve_order.rs b/tests/preserve_order.rs deleted file mode 100644 index f4aa96bd0..000000000 --- a/tests/preserve_order.rs +++ /dev/null @@ -1,60 +0,0 @@ -use async_graphql::*; - -#[tokio::test] -pub async fn test_preserve_order() { - #[derive(SimpleObject)] - struct Root { - a: i32, - b: i32, - c: i32, - } - - let schema = Schema::new(Root { a: 1, b: 2, c: 3 }, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ a c b }") - .await - .into_result() - .unwrap() - .data, - value!({ - "a": 1, "c": 3, "b": 2 - }) - ); - assert_eq!( - serde_json::to_string( - &schema - .execute("{ a c b }") - .await - .into_result() - .unwrap() - .data - ) - .unwrap(), - r#"{"a":1,"c":3,"b":2}"# - ); - - assert_eq!( - schema - .execute("{ c b a }") - .await - .into_result() - .unwrap() - .data, - value!({ - "c": 3, "b": 2, "a": 1 - }) - ); - assert_eq!( - serde_json::to_string( - &schema - .execute("{ c b a }") - .await - .into_result() - .unwrap() - .data - ) - .unwrap(), - r#"{"c":3,"b":2,"a":1}"# - ); -} diff --git a/tests/proc_macro_in_macro_rules.rs b/tests/proc_macro_in_macro_rules.rs deleted file mode 100644 index 0dbbe73fe..000000000 --- a/tests/proc_macro_in_macro_rules.rs +++ /dev/null @@ -1,95 +0,0 @@ -#[tokio::test] -pub async fn test_object() { - macro_rules! test_data { - ($test_name:ident) => { - #[derive(Debug, Clone)] - pub struct $test_name { - value: i32, - } - - #[async_graphql::Object] - impl $test_name { - async fn value(&self) -> i32 { - self.value - } - } - }; - } - - test_data!(A); -} - -#[tokio::test] -pub async fn test_subscription() { - macro_rules! test_data { - ($test_name:ident) => { - #[derive(Debug, Clone)] - pub struct $test_name { - value: i32, - } - - #[async_graphql::Subscription] - impl $test_name { - async fn value(&self) -> impl futures_util::stream::Stream + 'static { - let value = self.value; - futures_util::stream::once(async move { value }) - } - } - }; - } - - test_data!(A); -} - -#[tokio::test] -pub async fn test_scalar() { - macro_rules! test_data { - ($test_name:ident) => { - #[derive(Debug, Clone)] - pub struct $test_name(i64); - - #[async_graphql::Scalar] - impl async_graphql::ScalarType for $test_name { - fn parse(value: async_graphql::Value) -> async_graphql::InputValueResult { - match value { - async_graphql::Value::Number(n) if n.is_i64() => { - Ok($test_name(n.as_i64().unwrap())) - } - _ => Err(async_graphql::InputValueError::expected_type(value)), - } - } - - fn to_value(&self) -> async_graphql::Value { - self.0.to_value() - } - } - }; - } - - test_data!(A); -} - -#[tokio::test] -pub async fn test_oneof_object_type() { - macro_rules! test_data { - ($test_name:ident, $type1:ty, $type2:ty) => { - #[derive(async_graphql::OneofObject)] - enum $test_name { - Type1($type1), - Type2($type2), - } - }; - } - - #[derive(async_graphql::InputObject)] - struct A { - a: i32, - } - - #[derive(async_graphql::InputObject)] - struct B { - b: i32, - } - - test_data!(C, A, B); -} diff --git a/tests/raw_ident.rs b/tests/raw_ident.rs deleted file mode 100644 index 5b0beef2d..000000000 --- a/tests/raw_ident.rs +++ /dev/null @@ -1,74 +0,0 @@ -use async_graphql::*; -use futures_util::stream::{Stream, StreamExt, TryStreamExt}; - -#[tokio::test] -pub async fn test_input_value_custom_error() { - #[derive(Enum, Copy, Clone, Eq, PartialEq)] - #[allow(non_camel_case_types)] - enum MyEnum { - r#type, - } - - #[derive(SimpleObject)] - struct MyObject { - r#match: i32, - } - - #[derive(InputObject)] - struct MyInputObject { - r#match: i32, - } - - struct Query; - - #[Object] - impl Query { - async fn r#type(&self, r#match: i32) -> i32 { - r#match - } - - async fn obj(&self, obj: MyInputObject) -> MyObject { - MyObject { - r#match: obj.r#match, - } - } - - async fn enum_value(&self, value: MyEnum) -> MyEnum { - value - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn r#type(&self) -> impl Stream { - futures_util::stream::iter(0..10) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - let query = r#" - { - type(match: 99) - obj(obj: { match: 88} ) { match } - enumValue(value: TYPE) - }"#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "type": 99, - "obj": { "match": 88 }, - "enumValue": "TYPE", - }) - ); - - let mut stream = schema - .execute_stream("subscription { type }") - .map(|resp| resp.into_result()) - .map_ok(|resp| resp.data); - for i in 0..10 { - assert_eq!(value!({ "type": i }), stream.next().await.unwrap().unwrap()); - } - assert!(stream.next().await.is_none()); -} diff --git a/tests/rename.rs b/tests/rename.rs deleted file mode 100644 index 1d35116ab..000000000 --- a/tests/rename.rs +++ /dev/null @@ -1,138 +0,0 @@ -use async_graphql::*; -use futures_util::stream::{Stream, StreamExt}; - -#[tokio::test] -pub async fn test_enum() { - #[derive(Enum, Eq, PartialEq, Copy, Clone)] - #[graphql(rename_items = "camelCase")] - #[allow(non_camel_case_types)] - enum MyEnum { - CREATE_OBJECT, - } - - #[derive(SimpleObject)] - struct Query { - value: MyEnum, - } - - assert_eq!( - Schema::new( - Query { - value: MyEnum::CREATE_OBJECT - }, - EmptyMutation, - EmptySubscription - ) - .execute("{ value }") - .await - .into_result() - .unwrap() - .data, - value!({"value": "createObject"}) - ); -} - -#[tokio::test] -pub async fn test_simple_object() { - #[derive(SimpleObject)] - #[graphql(rename_fields = "UPPERCASE")] - struct Query { - a: i32, - } - - assert_eq!( - Schema::new(Query { a: 100 }, EmptyMutation, EmptySubscription) - .execute("{ A }") - .await - .into_result() - .unwrap() - .data, - value!({"A": 100}) - ); -} - -#[tokio::test] -pub async fn test_object() { - struct Query; - - #[Object(rename_fields = "UPPERCASE", rename_args = "PascalCase")] - impl Query { - async fn a(&self, ab1_cd2: i32) -> i32 { - 100 + ab1_cd2 - } - } - - assert_eq!( - Schema::new(Query, EmptyMutation, EmptySubscription) - .execute("{ A(Ab1Cd2:10) }") - .await - .into_result() - .unwrap() - .data, - value!({"A": 110}) - ); -} - -#[tokio::test] -pub async fn test_input_object() { - #[derive(InputObject)] - #[graphql(rename_fields = "snake_case")] - #[allow(non_snake_case)] - struct Obj { - a: i32, - AbCd: i32, - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self, obj: Obj) -> i32 { - obj.a + obj.AbCd - } - } - - assert_eq!( - Schema::new(Query, EmptyMutation, EmptySubscription) - .execute("{ obj(obj: {a: 10, ab_cd: 30}) }") - .await - .into_result() - .unwrap() - .data, - value!({"obj": 40}) - ); -} - -#[tokio::test] -pub async fn test_subscription() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Subscription; - - #[Subscription(rename_fields = "SCREAMING_SNAKE_CASE", rename_args = "lowercase")] - #[allow(non_snake_case)] - impl Subscription { - async fn create_object(&self, ObjectId: i32) -> impl Stream { - futures_util::stream::once(async move { ObjectId }) - } - } - - assert_eq!( - Schema::new(Query, EmptyMutation, Subscription) - .execute_stream("subscription { CREATE_OBJECT(objectid: 100) }") - .next() - .await - .unwrap() - .into_result() - .unwrap() - .data, - value!({"CREATE_OBJECT": 100}) - ); -} diff --git a/tests/result.rs b/tests/result.rs deleted file mode 100644 index dd8be645c..000000000 --- a/tests/result.rs +++ /dev/null @@ -1,280 +0,0 @@ -use async_graphql::*; -use futures_util::stream::Stream; - -#[tokio::test] -pub async fn test_fieldresult() { - struct Query; - - #[Object] - impl Query { - async fn error(&self) -> Result { - Err("TestError".into()) - } - - async fn opt_error(&self) -> Option> { - Some(Err("TestError".into())) - } - - async fn vec_error(&self) -> Vec> { - vec![Ok(1), Err("TestError".into())] - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - let resp = schema.execute("{ error1:optError error2:optError }").await; - assert_eq!( - resp.data, - value!({ - "error1": null, - "error2": null, - }) - ); - assert_eq!( - resp.errors, - vec![ - ServerError { - message: "TestError".to_string(), - source: None, - locations: vec![Pos { line: 1, column: 3 }], - path: vec![PathSegment::Field("error1".to_owned())], - extensions: None, - }, - ServerError { - message: "TestError".to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 19, - }], - path: vec![PathSegment::Field("error2".to_owned())], - extensions: None, - }, - ] - ); - - assert_eq!( - schema - .execute("{ optError }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "TestError".to_string(), - source: None, - locations: vec![Pos { line: 1, column: 3 }], - path: vec![PathSegment::Field("optError".to_owned())], - extensions: None, - }] - ); - - assert_eq!( - schema - .execute("{ vecError }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "TestError".to_string(), - source: None, - locations: vec![Pos { line: 1, column: 3 }], - path: vec![ - PathSegment::Field("vecError".to_owned()), - PathSegment::Index(1) - ], - extensions: None, - }] - ); -} - -#[tokio::test] -pub async fn test_custom_error() { - #[derive(Clone)] - struct MyError; - - impl From for Error { - fn from(_: MyError) -> Self { - Error::new("custom error") - } - } - - #[derive(SimpleObject)] - #[graphql(complex)] - struct MyObj { - value1: i32, - } - - #[ComplexObject] - impl MyObj { - async fn value2(&self) -> Result { - Err(MyError) - } - } - - #[derive(Interface)] - #[graphql(field(name = "value2", ty = "i32"))] - enum MyInterface { - MyObj(MyObj), - } - - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> Result { - Err(MyError) - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn value1(&self) -> Result, MyError> { - Err::>, _>(MyError) - } - - async fn value2(&self) -> impl Stream> { - futures_util::stream::once(async move { Err(MyError) }) - } - } -} - -#[tokio::test] -pub async fn test_error_propagation() { - struct ParentObject; - - #[Object] - impl ParentObject { - async fn child(&self) -> ChildObject { - ChildObject - } - - async fn child_opt(&self) -> Option { - Some(ChildObject) - } - } - - struct ChildObject; - - #[Object] - impl ChildObject { - async fn name(&self) -> Result { - Err("myerror".into()) - } - - async fn name_opt(&self) -> Option> { - Some(Err("myerror".into())) - } - } - - struct Query; - - #[Object] - impl Query { - async fn parent(&self) -> ParentObject { - ParentObject - } - - async fn parent_opt(&self) -> Option { - Some(ParentObject) - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let resp = schema.execute("{ parent { child { name } } }").await; - - assert_eq!( - resp.errors, - vec![ServerError { - message: "myerror".to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 20, - }], - path: vec![ - PathSegment::Field("parent".to_owned()), - PathSegment::Field("child".to_owned()), - PathSegment::Field("name".to_owned()), - ], - extensions: None, - }] - ); - - let resp = schema.execute("{ parent { childOpt { name } } }").await; - assert_eq!( - resp.data, - value!({ - "parent": { - "childOpt": null, - } - }) - ); - assert_eq!( - resp.errors, - vec![ServerError { - message: "myerror".to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 23, - }], - path: vec![ - PathSegment::Field("parent".to_owned()), - PathSegment::Field("childOpt".to_owned()), - PathSegment::Field("name".to_owned()), - ], - extensions: None, - }] - ); - - let resp = schema.execute("{ parentOpt { child { name } } }").await; - assert_eq!(resp.data, value!({ "parentOpt": null })); - assert_eq!( - resp.errors, - vec![ServerError { - message: "myerror".to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 23, - }], - path: vec![ - PathSegment::Field("parentOpt".to_owned()), - PathSegment::Field("child".to_owned()), - PathSegment::Field("name".to_owned()), - ], - extensions: None, - }] - ); - - let resp = schema.execute("{ parentOpt { child { nameOpt } } }").await; - assert_eq!( - resp.data, - value!({ - "parentOpt": { - "child": { - "nameOpt": null, - } - }, - }) - ); - assert_eq!( - resp.errors, - vec![ServerError { - message: "myerror".to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 23, - }], - path: vec![ - PathSegment::Field("parentOpt".to_owned()), - PathSegment::Field("child".to_owned()), - PathSegment::Field("nameOpt".to_owned()), - ], - extensions: None, - }] - ); -} diff --git a/tests/scalar.rs b/tests/scalar.rs deleted file mode 100644 index b6007c9f8..000000000 --- a/tests/scalar.rs +++ /dev/null @@ -1,72 +0,0 @@ -#![allow(clippy::diverging_sub_expression)] - -use async_graphql::*; - -mod test_mod { - use serde::{Deserialize, Serialize}; - - #[derive(Serialize, Deserialize)] - pub struct MyValue { - a: i32, - } -} - -scalar!( - test_mod::MyValue, - "MV", - "DESC", - "https://tools.ietf.org/html/rfc4122" -); - -#[tokio::test] -pub async fn test_scalar_macro() { - struct Query; - - #[Object] - #[allow(unreachable_code)] - impl Query { - async fn value(&self) -> test_mod::MyValue { - todo!() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute(r#"{ __type(name:"MV") { name description specifiedByURL } }"#) - .await - .into_result() - .unwrap() - .data, - value!({ - "__type": { - "name": "MV", - "description": "DESC", - "specifiedByURL": "https://tools.ietf.org/html/rfc4122", - } - }) - ); -} - -#[tokio::test] -pub async fn test_float_inf() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> f32 { - f32::INFINITY - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ value }") - .await - .into_result() - .unwrap() - .data, - value!({ "value": null }) - ); -} diff --git a/tests/schema.rs b/tests/schema.rs deleted file mode 100644 index f6aade1ff..000000000 --- a/tests/schema.rs +++ /dev/null @@ -1,51 +0,0 @@ -use ::http::HeaderValue; -use async_graphql::*; - -#[tokio::test] -pub async fn test_schema_default() { - #[derive(Default)] - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - type MySchema = Schema; - - let _schema = MySchema::default(); -} - -#[tokio::test] -pub async fn test_http_headers() { - #[derive(Default)] - struct Query; - - #[Object] - impl Query { - async fn value(&self, ctx: &Context<'_>) -> i32 { - ctx.insert_http_header("A", "1"); - 10 - } - - async fn err(&self, ctx: &Context<'_>) -> FieldResult { - ctx.insert_http_header("A", "1"); - Err("error".into()) - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let resp = schema.execute("{ value }").await; - assert_eq!( - resp.http_headers.get("A"), - Some(&HeaderValue::from_static("1")) - ); - - let resp = schema.execute("{ err }").await; - assert_eq!( - resp.http_headers.get("A"), - Some(&HeaderValue::from_static("1")) - ); -} diff --git a/tests/schemas/test_dynamic_schema.graphql b/tests/schemas/test_dynamic_schema.graphql deleted file mode 100644 index aa2b7b806..000000000 --- a/tests/schemas/test_dynamic_schema.graphql +++ /dev/null @@ -1,48 +0,0 @@ -input InputType @a @b { - a: String! @input_a(test: 5) -} - -type OutputType implements TestInterface @type { - id: String! @test - name: String! - body: String -} - -type OutputType2 { - a: [Int!]! -} - -type Query { - interface(x: TestEnum @validate): TestInterface! - output_type(input: InputType!): OutputType - enum(input: [TestEnum]!): TestEnum @pin - union: TestUnion! - scalar: TestScalar -} - -enum TestEnum @oneOf { - A - B @default - C -} - -interface TestInterface @test(a: 5, b: true, c: "str") { - id: String! @id - name: String! -} - -scalar TestScalar @json - -union TestUnion @wrap = OutputType | OutputType2 - -""" -Directs the executor to include this field or fragment only when the `if` argument is true. -""" -directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -""" -Directs the executor to skip this field or fragment when the `if` argument is true. -""" -directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -schema { - query: Query -} diff --git a/tests/schemas/test_entity_inaccessible.schema.graphql b/tests/schemas/test_entity_inaccessible.schema.graphql deleted file mode 100644 index c80444807..000000000 --- a/tests/schemas/test_entity_inaccessible.schema.graphql +++ /dev/null @@ -1,74 +0,0 @@ -type MyCustomObjInaccessible @inaccessible { - a: Int! - customObjectInaccessible: Int! @inaccessible -} - -enum MyEnumInaccessible @inaccessible { - OPTION_A - OPTION_B - OPTION_C -} - -enum MyEnumVariantInaccessible { - OPTION_A_INACCESSIBLE @inaccessible - OPTION_B - OPTION_C -} - -input MyInputObjFieldInaccessible { - inputFieldInaccessibleA: Int! @inaccessible -} - -input MyInputObjInaccessible @inaccessible { - a: Int! -} - -interface MyInterfaceInaccessible @inaccessible { - inaccessibleInterfaceValue: String! @inaccessible -} - -type MyInterfaceObjA implements MyInterfaceInaccessible { - inaccessibleInterfaceValue: String! -} - -type MyInterfaceObjB implements MyInterfaceInaccessible @inaccessible { - inaccessibleInterfaceValue: String! -} - -scalar MyNumberInaccessible @inaccessible - -type MyObjFieldInaccessible @key(fields: "id") { - objFieldInaccessibleA: Int! @inaccessible -} - -type MyObjInaccessible @key(fields: "id") @inaccessible { - a: Int! -} - -union MyUnionInaccessible @inaccessible = MyInterfaceObjA | MyInterfaceObjB - -extend type Query { - enumVariantInaccessible(id: Int!): MyEnumVariantInaccessible! - enumInaccessible(id: Int!): MyEnumInaccessible! - inaccessibleField(id: Int!): Int! @inaccessible - inaccessibleArgument(id: Int! @inaccessible): Int! - inaccessibleInterface: MyInterfaceInaccessible! - inaccessibleUnion: MyUnionInaccessible! - inaccessibleScalar: MyNumberInaccessible! - inaccessibleInputField(value: MyInputObjFieldInaccessible!): Int! - inaccessibleInput(value: MyInputObjInaccessible!): Int! - inaccessibleCustomObject: MyCustomObjInaccessible! -} - -""" -Directs the executor to include this field or fragment only when the `if` argument is true. -""" -directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -""" -Directs the executor to skip this field or fragment when the `if` argument is true. -""" -directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -extend schema @link( - url: "https://specs.apollo.dev/federation/v2.5", - import: ["@key", "@tag", "@shareable", "@inaccessible", "@override", "@external", "@provides", "@requires", "@composeDirective", "@interfaceObject", "@requiresScopes"] -) diff --git a/tests/schemas/test_entity_tag.schema.graphql b/tests/schemas/test_entity_tag.schema.graphql deleted file mode 100644 index 52493f71b..000000000 --- a/tests/schemas/test_entity_tag.schema.graphql +++ /dev/null @@ -1,74 +0,0 @@ -type MyCustomObjTagged @tag(name: "tagged") @tag(name: "object") @tag(name: "with") @tag(name: "multiple") @tag(name: "tags") { - a: Int! - customObjectTagged: Int! @tag(name: "tagged_custom_object_field") -} - -enum MyEnumTagged @tag(name: "tagged_num") { - OPTION_A - OPTION_B - OPTION_C -} - -enum MyEnumVariantTagged { - OPTION_A_TAGGED @tag(name: "tagged_enum_option") - OPTION_B - OPTION_C -} - -input MyInputObjFieldTagged { - inputFieldTaggedA: Int! @tag(name: "tagged_input_object_field") -} - -input MyInputObjTagged @tag(name: "input_object_tag") { - a: Int! -} - -type MyInterfaceObjA implements MyInterfaceTagged { - taggedInterfaceValue: String! -} - -type MyInterfaceObjB implements MyInterfaceTagged @tag(name: "interface_object") { - taggedInterfaceValue: String! -} - -interface MyInterfaceTagged @tag(name: "tagged_interface") { - taggedInterfaceValue: String! @tag(name: "tagged_interface_field") -} - -scalar MyNumberTagged @tag(name: "tagged_scalar") - -type MyObjFieldTagged @key(fields: "id") { - objFieldTaggedA: Int! @tag(name: "tagged_field") -} - -type MyObjTagged @key(fields: "id") @tag(name: "tagged_simple_object") { - a: Int! -} - -union MyUnionTagged @tag(name: "tagged_union") = MyInterfaceObjA | MyInterfaceObjB - -extend type Query { - enumVariantTagged(id: Int!): MyEnumVariantTagged! - enumTagged(id: Int!): MyEnumTagged! - taggedField(id: Int!): Int! @tag(name: "tagged_\"field\"") - taggedArgument(id: Int! @tag(name: "tagged_argument")): Int! - taggedInterface: MyInterfaceTagged! - taggedUnion: MyUnionTagged! - taggedScalar: MyNumberTagged! - taggedInputField(value: MyInputObjFieldTagged!): Int! - taggedInput(value: MyInputObjTagged!): Int! - taggedCustomObject: MyCustomObjTagged! -} - -""" -Directs the executor to include this field or fragment only when the `if` argument is true. -""" -directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -""" -Directs the executor to skip this field or fragment when the `if` argument is true. -""" -directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -extend schema @link( - url: "https://specs.apollo.dev/federation/v2.5", - import: ["@key", "@tag", "@shareable", "@inaccessible", "@override", "@external", "@provides", "@requires", "@composeDirective", "@interfaceObject", "@requiresScopes"] -) diff --git a/tests/schemas/test_fed2_compose.schema.graphql b/tests/schemas/test_fed2_compose.schema.graphql deleted file mode 100644 index 7cbcf3bc7..000000000 --- a/tests/schemas/test_fed2_compose.schema.graphql +++ /dev/null @@ -1,36 +0,0 @@ -type Query @testDirective(scope: "object type", input: 3) { - value: String! @testDirective(scope: "object field", input: 4) @noArgsDirective - anotherValue: SimpleValue! -} - -type SimpleValue @testDirective(scope: "simple object type", input: 1, opt: 3) { - someData: String! @testDirective(scope: "field and param with \" symbol", input: 2, opt: 3) -} - -type Subscription @testDirective(scope: "object type", input: 3) { - value: String! @testDirective(scope: "object field", input: 4) @noArgsDirective - anotherValue: SimpleValue! -} - -""" -Directs the executor to include this field or fragment only when the `if` argument is true. -""" -directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -directive @noArgsDirective on FIELD_DEFINITION -""" -Directs the executor to skip this field or fragment when the `if` argument is true. -""" -directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -directive @testDirective(scope: String!, input: Int!, opt: Int) on FIELD_DEFINITION | OBJECT -extend schema @link( - url: "https://specs.apollo.dev/federation/v2.5", - import: ["@key", "@tag", "@shareable", "@inaccessible", "@override", "@external", "@provides", "@requires", "@composeDirective", "@interfaceObject", "@requiresScopes"] -) - -extend schema @link( - url: "https://custom.spec.dev/extension/v1.0" - import: ["@noArgsDirective","@testDirective"] -) - @composeDirective(name: "@noArgsDirective") - @composeDirective(name: "@testDirective") - diff --git a/tests/schemas/test_fed2_compose_2.schema.graphql b/tests/schemas/test_fed2_compose_2.schema.graphql deleted file mode 100644 index 6cef834c8..000000000 --- a/tests/schemas/test_fed2_compose_2.schema.graphql +++ /dev/null @@ -1,64 +0,0 @@ -type Query { - testArgument(arg1: String! @type_directive_argument_definition(description: "This is ARGUMENT_DEFINITION in Object.arg1"), arg2: String! @type_directive_argument_definition(description: "This is ARGUMENT_DEFINITION in Object.arg2")): String! - testInputObject(arg: TestInput!): String! - testComplexObject: TestComplexObject! - testSimpleObject: TestSimpleObject! - testOneOfObject(arg: TestOneOfObject!): String! - testEnum(arg: TestEnum!): String! - testInterface: TestObjectForInterface! -} - -type TestComplexObject @type_directive_object(description: "This is OBJECT in (Complex / Simple)Object") { - field: String! @type_directive_field_definition(description: "This is FIELD_DEFINITION in (Complex / Simple)Object") - test(arg1: String! @type_directive_argument_definition(description: "This is ARGUMENT_DEFINITION in ComplexObject.arg1"), arg2: String! @type_directive_argument_definition(description: "This is ARGUMENT_DEFINITION in ComplexObject.arg2")): String! @type_directive_field_definition(description: "This is FIELD_DEFINITION in ComplexObject") -} - -enum TestEnum @type_directive_enum(description: "This is ENUM in Enum") { - FOO @type_directive_enum_value(description: "This is ENUM_VALUE in Enum") - BAR @type_directive_enum_value(description: "This is ENUM_VALUE in Enum") -} - -input TestInput @type_directive_input_object(description: "This is INPUT_OBJECT in InputObject") { - field: String! @type_directive_input_field_definition(description: "This is INPUT_FIELD_DEFINITION") -} - -interface TestInterface @type_directive_interface(description: "This is INTERFACE in Interface") { - field(arg1: String! @type_directive_argument_definition(description: "This is ARGUMENT_DEFINITION in Interface.arg1"), arg2: String! @type_directive_argument_definition(description: "This is ARGUMENT_DEFINITION in Interface.arg2")): String! @type_directive_field_definition(description: "This is INTERFACE in Interface") -} - -type TestObjectForInterface implements TestInterface { - field(arg1: String! @type_directive_argument_definition(description: "This is ARGUMENT_DEFINITION in Interface.arg1"), arg2: String! @type_directive_argument_definition(description: "This is ARGUMENT_DEFINITION in Interface.arg2")): String! -} - -input TestOneOfObject @oneOf @type_directive_input_object(description: "This is INPUT_OBJECT in OneofObject") { - foo: String @type_directive_input_field_definition(description: "This is INPUT_FIELD_DEFINITION in OneofObject") - bar: Int @type_directive_input_field_definition(description: "This is INPUT_FIELD_DEFINITION in OneofObject") -} - -type TestSimpleObject @type_directive_object(description: "This is OBJECT in SimpleObject") { - field: String! @type_directive_field_definition(description: "This is FIELD_DEFINITION in SimpleObject") -} - -""" -Directs the executor to include this field or fragment only when the `if` argument is true. -""" -directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -""" -Indicates that an Input Object is a OneOf Input Object (and thus requires exactly one of its field be provided) -""" -directive @oneOf on INPUT_OBJECT -""" -Directs the executor to skip this field or fragment when the `if` argument is true. -""" -directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -directive @type_directive_argument_definition(description: String!) on ARGUMENT_DEFINITION -directive @type_directive_enum(description: String!) on ENUM -directive @type_directive_enum_value(description: String!) on ENUM_VALUE -directive @type_directive_field_definition(description: String!) on FIELD_DEFINITION -directive @type_directive_input_field_definition(description: String!) on INPUT_FIELD_DEFINITION -directive @type_directive_input_object(description: String!) on INPUT_OBJECT -directive @type_directive_interface(description: String!) on INTERFACE -directive @type_directive_object(description: String!) on OBJECT -schema { - query: Query -} diff --git a/tests/schemas/test_fed2_link.schema.graphqls b/tests/schemas/test_fed2_link.schema.graphqls deleted file mode 100644 index ffb86d444..000000000 --- a/tests/schemas/test_fed2_link.schema.graphqls +++ /dev/null @@ -1,28 +0,0 @@ -extend type Product @key(fields: "upc") { - upc: String! @external - reviews: [Review!]! -} - -type Review { - body: String! - author: User! - product: Product! -} - -extend type User @key(fields: "id") { - id: ID! @external - reviews: [Review!]! -} - -""" -Directs the executor to include this field or fragment only when the `if` argument is true. -""" -directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -""" -Directs the executor to skip this field or fragment when the `if` argument is true. -""" -directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -extend schema @link( - url: "https://specs.apollo.dev/federation/v2.5", - import: ["@key", "@tag", "@shareable", "@inaccessible", "@override", "@external", "@provides", "@requires", "@composeDirective", "@interfaceObject", "@requiresScopes"] -) diff --git a/tests/schemas/test_space_schema.graphql b/tests/schemas/test_space_schema.graphql deleted file mode 100644 index ea066534a..000000000 --- a/tests/schemas/test_space_schema.graphql +++ /dev/null @@ -1,26 +0,0 @@ -type A { - a: Int! - b: Int! -} - -type B { - a: A! - b: Int! -} - -type Query { - a: A! - obj: B! -} - -""" -Directs the executor to include this field or fragment only when the `if` argument is true. -""" -directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -""" -Directs the executor to skip this field or fragment when the `if` argument is true. -""" -directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -schema { - query: Query -} diff --git a/tests/serializer_uuid.rs b/tests/serializer_uuid.rs deleted file mode 100644 index ea32bdf86..000000000 --- a/tests/serializer_uuid.rs +++ /dev/null @@ -1,42 +0,0 @@ -use async_graphql::*; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize)] -#[repr(transparent)] -#[serde(transparent)] -struct AssetId(pub uuid::Uuid); - -async_graphql::scalar!(AssetId); - -#[tokio::test] -/// Test case for -/// https://github.com/async-graphql/async-graphql/issues/603 -pub async fn test_serialize_uuid() { - let generated_uuid = uuid::Uuid::new_v4(); - - struct Query { - data: AssetId, - } - - #[Object] - impl Query { - async fn data(&self) -> &AssetId { - &self.data - } - } - - let schema = Schema::new( - Query { - data: AssetId(generated_uuid), - }, - EmptyMutation, - EmptySubscription, - ); - let query = r#"{ data }"#; - assert_eq!( - schema.execute(query).await.data, - value!({ - "data": generated_uuid.to_string(), - }) - ); -} diff --git a/tests/simple_object.rs b/tests/simple_object.rs deleted file mode 100644 index f4f5fd8b1..000000000 --- a/tests/simple_object.rs +++ /dev/null @@ -1,106 +0,0 @@ -use async_graphql::*; - -#[tokio::test] -async fn test_flatten() { - #[derive(SimpleObject)] - struct A { - a: i32, - b: i32, - } - - #[derive(SimpleObject)] - struct B { - #[graphql(flatten)] - a: A, - c: i32, - } - - struct Query; - - #[Object] - impl Query { - async fn obj(&self) -> B { - B { - a: A { a: 100, b: 200 }, - c: 300, - } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = "{ __type(name: \"B\") { fields { name } } }"; - assert_eq!( - schema.execute(query).await.data, - value!({ - "__type": { - "fields": [ - {"name": "a"}, - {"name": "b"}, - {"name": "c"} - ] - } - }) - ); - - let query = "{ obj { a b c } }"; - assert_eq!( - schema.execute(query).await.data, - value!({ - "obj": { - "a": 100, - "b": 200, - "c": 300, - } - }) - ); -} - -#[tokio::test] -async fn recursive_fragment_definition() { - #[derive(SimpleObject)] - struct Hello { - world: String, - } - - struct Query; - - // this setup is actually completely irrelevant we just need to be able to - // execute a query - #[Object] - impl Query { - async fn obj(&self) -> Hello { - Hello { - world: "Hello World".into(), - } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = "fragment f on Query {...f} { __typename }"; - assert!(schema.execute(query).await.into_result().is_err()); -} - -#[tokio::test] -async fn recursive_fragment_definition_nested() { - #[derive(SimpleObject)] - struct Hello { - world: String, - } - - struct Query; - - // this setup is actually completely irrelevant we just need to be able to - // execute a query - #[Object] - impl Query { - async fn obj(&self) -> Hello { - Hello { - world: "Hello World".into(), - } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = "fragment f on Query { a { ...f a { ...f } } } { __typename }"; - assert!(schema.execute(query).await.into_result().is_err()); -} diff --git a/tests/subscription.rs b/tests/subscription.rs deleted file mode 100644 index 2c074ffb6..000000000 --- a/tests/subscription.rs +++ /dev/null @@ -1,424 +0,0 @@ -use async_graphql::*; -use futures_util::stream::{Stream, StreamExt, TryStreamExt}; - -struct Query; - -#[Object] -impl Query { - async fn value(&self) -> i32 { - 10 - } -} - -#[tokio::test] -pub async fn test_subscription() { - #[derive(SimpleObject)] - struct Event { - a: i32, - b: i32, - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn values(&self, start: i32, end: i32) -> impl Stream { - futures_util::stream::iter(start..end) - } - - async fn events(&self, start: i32, end: i32) -> impl Stream { - futures_util::stream::iter((start..end).map(|n| Event { a: n, b: n * 10 })) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - - { - let mut stream = schema - .execute_stream("subscription { values(start: 10, end: 20) }") - .map(|resp| resp.into_result().unwrap().data); - for i in 10..20 { - assert_eq!(value!({ "values": i }), stream.next().await.unwrap()); - } - assert!(stream.next().await.is_none()); - } - - { - let mut stream = schema - .execute_stream("subscription { events(start: 10, end: 20) { a b } }") - .map(|resp| resp.into_result().unwrap().data); - for i in 10..20 { - assert_eq!( - value!({ "events": {"a": i, "b": i * 10} }), - stream.next().await.unwrap() - ); - } - assert!(stream.next().await.is_none()); - } -} - -#[tokio::test] -pub async fn test_subscription_with_ctx_data() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct MyObject; - - #[Object] - impl MyObject { - async fn value(&self, ctx: &Context<'_>) -> i32 { - *ctx.data_unchecked::() - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn values(&self, ctx: &Context<'_>) -> impl Stream { - let value = *ctx.data_unchecked::(); - futures_util::stream::once(async move { value }) - } - - async fn objects(&self) -> impl Stream { - futures_util::stream::once(async move { MyObject }) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - - { - let mut stream = schema - .execute_stream(Request::new("subscription { values objects { value } }").data(100i32)) - .map(|resp| resp.data); - assert_eq!(value!({ "values": 100 }), stream.next().await.unwrap()); - assert_eq!( - value!({ "objects": { "value": 100 } }), - stream.next().await.unwrap() - ); - assert!(stream.next().await.is_none()); - } -} - -#[tokio::test] -pub async fn test_subscription_with_token() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Subscription; - - struct Token(String); - - #[Subscription] - impl Subscription { - async fn values(&self, ctx: &Context<'_>) -> Result> { - if ctx.data_unchecked::().0 != "123456" { - return Err("forbidden".into()); - } - Ok(futures_util::stream::once(async move { 100 })) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - - { - let mut stream = schema - .execute_stream( - Request::new("subscription { values }").data(Token("123456".to_string())), - ) - .map(|resp| resp.into_result().unwrap().data); - assert_eq!(value!({ "values": 100 }), stream.next().await.unwrap()); - assert!(stream.next().await.is_none()); - } - - { - assert!( - schema - .execute_stream( - Request::new("subscription { values }").data(Token("654321".to_string())) - ) - .next() - .await - .unwrap() - .is_err() - ); - } -} - -#[tokio::test] -pub async fn test_subscription_inline_fragment() { - #[derive(SimpleObject)] - struct Event { - a: i32, - b: i32, - } - - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn events(&self, start: i32, end: i32) -> impl Stream { - futures_util::stream::iter((start..end).map(|n| Event { a: n, b: n * 10 })) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - let mut stream = schema - .execute_stream( - r#" - subscription { - events(start: 10, end: 20) { - a - ... { - b - } - } - } - "#, - ) - .map(|resp| resp.data); - for i in 10..20 { - assert_eq!( - value!({ "events": {"a": i, "b": i * 10} }), - stream.next().await.unwrap() - ); - } - assert!(stream.next().await.is_none()); -} - -#[tokio::test] -pub async fn test_subscription_fragment() { - #[derive(SimpleObject)] - struct Event { - a: i32, - b: i32, - } - - #[derive(Interface)] - #[graphql(field(name = "a", ty = "&i32"))] - enum MyInterface { - Event(Event), - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn events(&self, start: i32, end: i32) -> impl Stream { - futures_util::stream::iter((start..end).map(|n| Event { a: n, b: n * 10 })) - } - } - - let schema = Schema::build(Query, EmptyMutation, Subscription) - .register_output_type::() - .finish(); - let mut stream = schema - .execute_stream( - r#" - subscription s { - events(start: 10, end: 20) { - ... on MyInterface { - a - } - b - } - } - "#, - ) - .map(|resp| resp.data); - for i in 10i32..20 { - assert_eq!( - value!({ "events": {"a": i, "b": i * 10} }), - stream.next().await.unwrap() - ); - } - assert!(stream.next().await.is_none()); -} - -#[tokio::test] -pub async fn test_subscription_fragment2() { - #[derive(SimpleObject)] - struct Event { - a: i32, - b: i32, - } - - #[derive(Interface)] - #[graphql(field(name = "a", ty = "&i32"))] - enum MyInterface { - Event(Event), - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn events(&self, start: i32, end: i32) -> impl Stream { - futures_util::stream::iter((start..end).map(|n| Event { a: n, b: n * 10 })) - } - } - - let schema = Schema::build(Query, EmptyMutation, Subscription) - .register_output_type::() - .finish(); - let mut stream = schema - .execute_stream( - r#" - subscription s { - events(start: 10, end: 20) { - ... Frag - } - } - - fragment Frag on Event { - a b - } - "#, - ) - .map(|resp| resp.data); - for i in 10..20 { - assert_eq!( - value!({ "events": {"a": i, "b": i * 10} }), - stream.next().await.unwrap() - ); - } - assert!(stream.next().await.is_none()); -} - -#[tokio::test] -pub async fn test_subscription_error() { - struct Event { - value: i32, - } - - #[Object] - impl Event { - async fn value(&self) -> Result { - if self.value != 5 { - Ok(self.value) - } else { - Err("TestError".into()) - } - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn events(&self) -> impl Stream { - futures_util::stream::iter((0..10).map(|n| Event { value: n })) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - let mut stream = schema - .execute_stream("subscription { events { value } }") - .map(|resp| resp.into_result()) - .map_ok(|resp| resp.data); - - for i in 0i32..5 { - assert_eq!( - value!({ "events": { "value": i } }), - stream.next().await.unwrap().unwrap() - ); - } - - assert_eq!( - stream.next().await, - Some(Err(vec![ServerError { - message: "TestError".to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 25 - }], - path: vec![ - PathSegment::Field("events".to_owned()), - PathSegment::Field("value".to_owned()) - ], - extensions: None, - }])) - ); - - for i in 6i32..10 { - assert_eq!( - value!({ "events": { "value": i } }), - stream.next().await.unwrap().unwrap() - ); - } - - assert!(stream.next().await.is_none()); -} - -#[tokio::test] -pub async fn test_subscription_fieldresult() { - struct Subscription; - - #[Subscription] - impl Subscription { - async fn values(&self) -> impl Stream> { - futures_util::stream::iter(0..5) - .map(Result::Ok) - .chain(futures_util::stream::once(async move { - Err("StreamErr".into()) - })) - .chain(futures_util::stream::iter(5..10).map(Result::Ok)) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - let mut stream = schema.execute_stream("subscription { values }"); - - for i in 0i32..5 { - assert_eq!( - Response::new(value!({ "values": i })), - stream.next().await.unwrap() - ); - } - - let resp = stream.next().await.unwrap(); - assert_eq!( - resp.errors, - vec![ServerError { - message: "StreamErr".to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 16 - }], - path: vec![PathSegment::Field("values".to_owned())], - extensions: None, - }] - ); - - for i in 5i32..10 { - assert_eq!( - Response::new(value!({ "values": i })), - stream.next().await.unwrap() - ); - } - - assert!(stream.next().await.is_none()); -} diff --git a/tests/subscription_websocket_graphql_ws.rs b/tests/subscription_websocket_graphql_ws.rs deleted file mode 100644 index 9f821129c..000000000 --- a/tests/subscription_websocket_graphql_ws.rs +++ /dev/null @@ -1,989 +0,0 @@ -use std::{ - pin::Pin, - sync::{Arc, Mutex}, - time::Duration, -}; - -use async_graphql::{ - http::{WebSocketProtocols, WsMessage}, - *, -}; -use futures_channel::mpsc; -use futures_util::{ - SinkExt, - stream::{BoxStream, Stream, StreamExt}, -}; - -#[tokio::test] -pub async fn test_subscription_ws_transport() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn values(&self) -> impl Stream { - futures_util::stream::iter(0..10) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - let (mut tx, rx) = mpsc::unbounded(); - let mut stream = http::WebSocket::new(schema, rx, WebSocketProtocols::GraphQLWS); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init", - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - serde_json::from_str::(&stream.next().await.unwrap().unwrap_text()) - .unwrap(), - serde_json::json!({ - "type": "connection_ack", - }), - ); - - tx.send( - serde_json::to_string(&value!({ - "type": "start", - "id": "1", - "payload": { - "query": "subscription { values }" - }, - })) - .unwrap(), - ) - .await - .unwrap(); - - for i in 0..10 { - assert_eq!( - serde_json::from_str::(&stream.next().await.unwrap().unwrap_text()) - .unwrap(), - serde_json::json!({ - "type": "next", - "id": "1", - "payload": { "data": { "values": i } }, - }), - ); - } - - assert_eq!( - serde_json::from_str::(&stream.next().await.unwrap().unwrap_text()) - .unwrap(), - serde_json::json!({ - "type": "complete", - "id": "1", - }), - ); -} - -#[tokio::test] -pub async fn test_subscription_ws_transport_with_token() { - struct Token(String); - - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn values(&self, ctx: &Context<'_>) -> Result> { - if ctx.data_unchecked::().0 != "123456" { - return Err("forbidden".into()); - } - Ok(futures_util::stream::iter(0..10)) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - let (mut tx, rx) = mpsc::unbounded(); - let mut stream = http::WebSocket::new(schema.clone(), rx, WebSocketProtocols::GraphQLWS) - .on_connection_init(|value| async { - #[derive(serde::Deserialize)] - struct Payload { - token: String, - } - - let payload: Payload = serde_json::from_value(value).unwrap(); - let mut data = Data::default(); - data.insert(Token(payload.token)); - Ok(data) - }); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init", - "payload": { "token": "123456" } - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - Some(value!({ - "type": "connection_ack", - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); - - tx.send( - serde_json::to_string(&value!({ - "type": "start", - "id": "1", - "payload": { - "query": "subscription { values }" - }, - })) - .unwrap(), - ) - .await - .unwrap(); - - for i in 0..10 { - assert_eq!( - Some(value!({ - "type": "next", - "id": "1", - "payload": { "data": { "values": i } }, - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); - } - - assert_eq!( - Some(value!({ - "type": "complete", - "id": "1", - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); - - let (mut tx, rx) = mpsc::unbounded(); - let mut data = Data::default(); - data.insert(Token("123456".to_string())); - let mut stream = - http::WebSocket::new(schema, rx, WebSocketProtocols::GraphQLWS).connection_data(data); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init", - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - Some(value!({ - "type": "connection_ack", - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); - - tx.send( - serde_json::to_string(&value!({ - "type": "start", - "id": "1", - "payload": { - "query": "subscription { values }" - }, - })) - .unwrap(), - ) - .await - .unwrap(); - - for i in 0..10 { - assert_eq!( - Some(value!({ - "type": "next", - "id": "1", - "payload": { "data": { "values": i } }, - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); - } - - assert_eq!( - Some(value!({ - "type": "complete", - "id": "1", - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); -} - -#[tokio::test] -pub async fn test_subscription_ws_transport_error() { - struct Event { - value: i32, - } - - #[Object] - impl Event { - async fn value(&self) -> Result { - if self.value < 5 { - Ok(self.value) - } else { - Err("TestError".into()) - } - } - } - - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn events(&self) -> impl Stream { - futures_util::stream::iter((0..10).map(|n| Event { value: n })) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - let (mut tx, rx) = mpsc::unbounded(); - let mut stream = http::WebSocket::new(schema, rx, WebSocketProtocols::GraphQLWS); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init" - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - Some(value!({ - "type": "connection_ack", - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); - - tx.send( - serde_json::to_string(&value!({ - "type": "start", - "id": "1", - "payload": { - "query": "subscription { events { value } }" - }, - })) - .unwrap(), - ) - .await - .unwrap(); - - for i in 0i32..5 { - assert_eq!( - Some(value!({ - "type": "next", - "id": "1", - "payload": { "data": { "events": { "value": i } } }, - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); - } - - assert_eq!( - Some(value!({ - "type": "next", - "id": "1", - "payload": { - "data": null, - "errors": [{ - "message": "TestError", - "locations": [{"line": 1, "column": 25}], - "path": ["events", "value"], - }], - }, - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); -} - -#[tokio::test] -pub async fn test_subscription_init_error() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn events(&self) -> impl Stream { - futures_util::stream::once(async move { 10 }) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - let (mut tx, rx) = mpsc::unbounded(); - let mut stream = http::WebSocket::new(schema, rx, WebSocketProtocols::GraphQLWS) - .on_connection_init(|_| async move { Err("Error!".into()) }); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init" - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - (1002, "Error!".to_string()), - dbg!(stream.next().await.unwrap()).unwrap_close() - ); -} - -#[tokio::test] -pub async fn test_subscription_too_many_initialisation_requests_error() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn events(&self) -> impl Stream { - futures_util::stream::once(async move { 10 }) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - let (mut tx, rx) = mpsc::unbounded(); - let mut stream = http::WebSocket::new(schema, rx, WebSocketProtocols::GraphQLWS); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init" - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - Some(value!({ - "type": "connection_ack", - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init" - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - (4429, "Too many initialisation requests.".to_string()), - stream.next().await.unwrap().unwrap_close() - ); -} - -#[tokio::test] -pub async fn test_query_over_websocket() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 999 - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let (mut tx, rx) = mpsc::unbounded(); - let mut stream = http::WebSocket::new(schema, rx, WebSocketProtocols::GraphQLWS); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init", - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - Some(value!({ - "type": "connection_ack", - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); - - tx.send( - serde_json::to_string(&value!({ - "type": "start", - "id": "1", - "payload": { - "query": "query { value }" - }, - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - Some(value!({ - "type": "next", - "id": "1", - "payload": { "data": { "value": 999 } }, - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); - - assert_eq!( - Some(value!({ - "type": "complete", - "id": "1", - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); -} - -#[tokio::test] -pub async fn test_start_before_connection_init() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 999 - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let (mut tx, rx) = mpsc::unbounded(); - let mut stream = http::WebSocket::new(schema, rx, WebSocketProtocols::GraphQLWS); - - tx.send( - serde_json::to_string(&value!({ - "type": "start", - "id": "1", - "payload": { - "query": "query { value }" - }, - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - stream.next().await.unwrap().unwrap_close(), - (1011, "The handshake is not completed.".to_string()) - ); -} - -#[tokio::test] -pub async fn test_stream_drop() { - type Dropped = Arc>; - - struct TestStream { - inner: BoxStream<'static, i32>, - dropped: Dropped, - } - - impl Drop for TestStream { - fn drop(&mut self) { - *self.dropped.lock().unwrap() = true; - } - } - - impl Stream for TestStream { - type Item = i32; - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - self.inner.as_mut().poll_next(cx) - } - } - - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 999 - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn values(&self, ctx: &Context<'_>) -> impl Stream { - TestStream { - inner: Box::pin(async_stream::stream! { - loop { - tokio::time::sleep(Duration::from_millis(10)).await; - yield 10; - } - }), - dropped: ctx.data_unchecked::().clone(), - } - } - } - - let dropped = Dropped::default(); - let schema = Schema::build(Query, EmptyMutation, Subscription) - .data(dropped.clone()) - .finish(); - let (mut tx, rx) = mpsc::unbounded(); - let mut stream = http::WebSocket::new(schema, rx, WebSocketProtocols::GraphQLWS); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init", - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - serde_json::from_str::(&stream.next().await.unwrap().unwrap_text()) - .unwrap(), - serde_json::json!({ - "type": "connection_ack", - }), - ); - - tx.send( - serde_json::to_string(&value!({ - "type": "start", - "id": "1", - "payload": { - "query": "subscription { values }" - }, - })) - .unwrap(), - ) - .await - .unwrap(); - - for _ in 0..5 { - assert_eq!( - Some(value!({ - "type": "next", - "id": "1", - "payload": { "data": { "values": 10 } }, - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); - } - - tx.send( - serde_json::to_string(&value!({ - "type": "stop", - "id": "1", - })) - .unwrap(), - ) - .await - .unwrap(); - - loop { - let value = serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap(); - if value - == Some(value!({ - "type": "next", - "id": "1", - "payload": { "data": { "values": 10 } }, - })) - { - continue; - } else { - assert_eq!( - Some(value!({ - "type": "complete", - "id": "1", - })), - value - ); - break; - } - } - - assert!(*dropped.lock().unwrap()); -} - -#[tokio::test] -pub async fn test_ping_pong() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn values(&self) -> impl Stream { - futures_util::stream::iter(0..10) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - let (mut tx, rx) = mpsc::unbounded(); - let mut stream = http::WebSocket::new(schema, rx, WebSocketProtocols::GraphQLWS); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init", - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - serde_json::from_str::(&stream.next().await.unwrap().unwrap_text()) - .unwrap(), - serde_json::json!({ - "type": "connection_ack", - }), - ); - - for _ in 0..5 { - tx.send( - serde_json::to_string(&value!({ - "type": "ping", - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - serde_json::from_str::(&stream.next().await.unwrap().unwrap_text()) - .unwrap(), - serde_json::json!({ - "type": "pong", - }), - ); - } - - tx.send( - serde_json::to_string(&value!({ - "type": "pong", - })) - .unwrap(), - ) - .await - .unwrap(); - - assert!( - tokio::time::timeout(Duration::from_millis(100), stream.next()) - .await - .is_err() - ); -} - -#[tokio::test] -pub async fn test_connection_terminate() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn values(&self) -> impl Stream { - futures_util::stream::iter(0..10) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - let (mut tx, rx) = mpsc::unbounded(); - let mut stream = http::WebSocket::new(schema, rx, WebSocketProtocols::GraphQLWS); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init", - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - serde_json::from_str::(&stream.next().await.unwrap().unwrap_text()) - .unwrap(), - serde_json::json!({ - "type": "connection_ack", - }), - ); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_terminate", - })) - .unwrap(), - ) - .await - .unwrap(); - - assert!(stream.next().await.is_none()); - assert!(stream.next().await.is_none()); -} - -#[tokio::test] -pub async fn test_keepalive_timeout_1() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn values(&self) -> impl Stream { - futures_util::stream::iter(0..10) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - let (mut tx, rx) = mpsc::unbounded(); - let mut stream = http::WebSocket::new(schema, rx, WebSocketProtocols::GraphQLWS) - .keepalive_timeout(Duration::from_secs(3)); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init", - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - serde_json::from_str::(&stream.next().await.unwrap().unwrap_text()) - .unwrap(), - serde_json::json!({ - "type": "connection_ack", - }), - ); - - assert!( - tokio::time::timeout(Duration::from_secs(2), stream.next()) - .await - .is_err() - ); - - assert_eq!( - tokio::time::timeout(Duration::from_secs(2), stream.next()).await, - Ok(Some(WsMessage::Close(3008, "timeout".to_string()))) - ); - - assert!(stream.next().await.is_none()); -} - -#[tokio::test] -pub async fn test_keepalive_timeout_2() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn values(&self) -> impl Stream { - futures_util::stream::iter(0..10) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - let (mut tx, rx) = mpsc::unbounded(); - let mut stream = http::WebSocket::new(schema, rx, WebSocketProtocols::GraphQLWS) - .keepalive_timeout(Duration::from_secs(3)); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init", - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - serde_json::from_str::(&stream.next().await.unwrap().unwrap_text()) - .unwrap(), - serde_json::json!({ - "type": "connection_ack", - }), - ); - - tokio::time::sleep(Duration::from_secs(2)).await; - - tx.send( - serde_json::to_string(&value!({ - "type": "ping", - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - serde_json::from_str::(&stream.next().await.unwrap().unwrap_text()) - .unwrap(), - serde_json::json!({ - "type": "pong", - }), - ); - - assert!( - tokio::time::timeout(Duration::from_secs(2), stream.next()) - .await - .is_err() - ); - - assert_eq!( - tokio::time::timeout(Duration::from_secs(2), stream.next()).await, - Ok(Some(WsMessage::Close(3008, "timeout".to_string()))) - ); - - assert!(stream.next().await.is_none()); -} - -#[tokio::test] -pub async fn test_on_ping() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn values(&self) -> impl Stream { - futures_util::stream::iter(0..10) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - let (mut tx, rx) = mpsc::unbounded(); - let mut stream = http::WebSocket::new(schema, rx, WebSocketProtocols::GraphQLWS).on_ping( - |_, payload| async move { - assert_eq!(payload, Some(serde_json::json!({"a": 1, "b": "abc"}))); - Ok(Some(serde_json::json!({ "a": "abc", "b": 1 }))) - }, - ); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init", - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - serde_json::from_str::(&stream.next().await.unwrap().unwrap_text()) - .unwrap(), - serde_json::json!({ - "type": "connection_ack", - }), - ); - - tx.send( - serde_json::to_string(&value!({ - "type": "ping", - "payload": { - "a": 1, - "b": "abc" - } - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - serde_json::from_str::(&stream.next().await.unwrap().unwrap_text()) - .unwrap(), - serde_json::json!({ - "type": "pong", - "payload": { - "a": "abc", - "b": 1 - } - }), - ); -} diff --git a/tests/subscription_websocket_subscriptions_transport_ws.rs b/tests/subscription_websocket_subscriptions_transport_ws.rs deleted file mode 100644 index ececa1622..000000000 --- a/tests/subscription_websocket_subscriptions_transport_ws.rs +++ /dev/null @@ -1,424 +0,0 @@ -use async_graphql::{http::WebSocketProtocols, *}; -use futures_channel::mpsc; -use futures_util::{ - SinkExt, - stream::{Stream, StreamExt}, -}; - -#[tokio::test] -pub async fn test_subscription_ws_transport() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn values(&self) -> impl Stream { - futures_util::stream::iter(0..10) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - let (mut tx, rx) = mpsc::unbounded(); - let mut stream = http::WebSocket::new(schema, rx, WebSocketProtocols::SubscriptionsTransportWS); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init", - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - serde_json::from_str::(&stream.next().await.unwrap().unwrap_text()) - .unwrap(), - serde_json::json!({ - "type": "connection_ack", - }), - ); - - tx.send( - serde_json::to_string(&value!({ - "type": "start", - "id": "1", - "payload": { - "query": "subscription { values }" - }, - })) - .unwrap(), - ) - .await - .unwrap(); - - for i in 0..10 { - assert_eq!( - serde_json::from_str::(&stream.next().await.unwrap().unwrap_text()) - .unwrap(), - serde_json::json!({ - "type": "data", - "id": "1", - "payload": { "data": { "values": i } }, - }), - ); - } - - assert_eq!( - serde_json::from_str::(&stream.next().await.unwrap().unwrap_text()) - .unwrap(), - serde_json::json!({ - "type": "complete", - "id": "1", - }), - ); -} - -#[tokio::test] -pub async fn test_subscription_ws_transport_with_token() { - struct Token(String); - - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn values(&self, ctx: &Context<'_>) -> Result> { - if ctx.data_unchecked::().0 != "123456" { - return Err("forbidden".into()); - } - Ok(futures_util::stream::iter(0..10)) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - let (mut tx, rx) = mpsc::unbounded(); - let mut stream = http::WebSocket::new(schema, rx, WebSocketProtocols::SubscriptionsTransportWS) - .on_connection_init(|value| async { - #[derive(serde::Deserialize)] - struct Payload { - token: String, - } - - let payload: Payload = serde_json::from_value(value).unwrap(); - let mut data = Data::default(); - data.insert(Token(payload.token)); - Ok(data) - }); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init", - "payload": { "token": "123456" } - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - Some(value!({ - "type": "connection_ack", - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); - - tx.send( - serde_json::to_string(&value!({ - "type": "start", - "id": "1", - "payload": { - "query": "subscription { values }" - }, - })) - .unwrap(), - ) - .await - .unwrap(); - - for i in 0..10 { - assert_eq!( - Some(value!({ - "type": "data", - "id": "1", - "payload": { "data": { "values": i } }, - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); - } - - assert_eq!( - Some(value!({ - "type": "complete", - "id": "1", - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); -} - -#[tokio::test] -pub async fn test_subscription_ws_transport_error() { - struct Event { - value: i32, - } - - #[Object] - impl Event { - async fn value(&self) -> Result { - if self.value < 5 { - Ok(self.value) - } else { - Err("TestError".into()) - } - } - } - - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn events(&self) -> impl Stream { - futures_util::stream::iter((0..10).map(|n| Event { value: n })) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - let (mut tx, rx) = mpsc::unbounded(); - let mut stream = http::WebSocket::new(schema, rx, WebSocketProtocols::SubscriptionsTransportWS); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init" - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - Some(value!({ - "type": "connection_ack", - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); - - tx.send( - serde_json::to_string(&value!({ - "type": "start", - "id": "1", - "payload": { - "query": "subscription { events { value } }" - }, - })) - .unwrap(), - ) - .await - .unwrap(); - - for i in 0i32..5 { - assert_eq!( - Some(value!({ - "type": "data", - "id": "1", - "payload": { "data": { "events": { "value": i } } }, - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); - } - - assert_eq!( - Some(value!({ - "type": "data", - "id": "1", - "payload": { - "data": null, - "errors": [{ - "message": "TestError", - "locations": [{"line": 1, "column": 25}], - "path": ["events", "value"], - }], - }, - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); -} - -#[tokio::test] -pub async fn test_subscription_too_many_initialisation_requests_error() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 10 - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn events(&self) -> impl Stream { - futures_util::stream::once(async move { 10 }) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - let (mut tx, rx) = mpsc::unbounded(); - let mut stream = http::WebSocket::new(schema, rx, WebSocketProtocols::SubscriptionsTransportWS); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init" - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - Some(value!({ - "type": "connection_ack", - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init" - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - Some(value!({ - "type": "connection_error", - "payload": { - "message": "Too many initialisation requests." - }, - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); -} - -#[tokio::test] -pub async fn test_query_over_websocket() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 999 - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let (mut tx, rx) = mpsc::unbounded(); - let mut stream = http::WebSocket::new(schema, rx, WebSocketProtocols::SubscriptionsTransportWS); - - tx.send( - serde_json::to_string(&value!({ - "type": "connection_init", - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - Some(value!({ - "type": "connection_ack", - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); - - tx.send( - serde_json::to_string(&value!({ - "type": "start", - "id": "1", - "payload": { - "query": "query { value }" - }, - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - Some(value!({ - "type": "data", - "id": "1", - "payload": { "data": { "value": 999 } }, - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); - - assert_eq!( - Some(value!({ - "type": "complete", - "id": "1", - })), - serde_json::from_str(&stream.next().await.unwrap().unwrap_text()).unwrap() - ); -} - -#[tokio::test] -pub async fn test_start_before_connection_init() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 999 - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let (mut tx, rx) = mpsc::unbounded(); - let mut stream = http::WebSocket::new(schema, rx, WebSocketProtocols::SubscriptionsTransportWS); - - tx.send( - serde_json::to_string(&value!({ - "type": "start", - "id": "1", - "payload": { - "query": "query { value }" - }, - })) - .unwrap(), - ) - .await - .unwrap(); - - assert_eq!( - stream.next().await.unwrap().unwrap_close(), - (1011, "The handshake is not completed.".to_string()) - ); -} diff --git a/tests/type_directive.rs b/tests/type_directive.rs deleted file mode 100644 index c50f67e18..000000000 --- a/tests/type_directive.rs +++ /dev/null @@ -1,254 +0,0 @@ -use async_graphql::{EmptyMutation, EmptySubscription, SDLExportOptions, Schema, Subscription}; -use async_graphql_derive::{ - ComplexObject, Enum, InputObject, Interface, Object, OneofObject, SimpleObject, TypeDirective, -}; -use futures_util::{Stream, stream}; - -#[test] -pub fn test_type_directive_1() { - mod test_mod { - #[super::TypeDirective( - location = "FieldDefinition", - location = "Object", - composable = "https://custom.spec.dev/extension/v1.0" - )] - pub fn testDirective(scope: String, input: u32, opt: Option) {} - } - - use test_mod::*; - - #[TypeDirective( - location = "FieldDefinition", - composable = "https://custom.spec.dev/extension/v1.0" - )] - pub fn noArgsDirective() {} - - struct Query; - - #[derive(SimpleObject)] - #[graphql( - directive = testDirective::apply("simple object type".to_string(), 1, Some(3)) - )] - struct SimpleValue { - #[graphql( - directive = testDirective::apply("field and param with \" symbol".to_string(), 2, Some(3)) - )] - some_data: String, - } - - #[Object( - directive = testDirective::apply("object type".to_string(), 3, None), - )] - impl Query { - #[graphql( - directive = test_mod::testDirective::apply("object field".to_string(), 4, None), - directive = noArgsDirective::apply() - )] - async fn value(&self) -> &'static str { - "abc" - } - - async fn another_value(&self) -> SimpleValue { - SimpleValue { - some_data: "data".to_string(), - } - } - } - - struct Subscription; - - #[Subscription( - directive = testDirective::apply("object type".to_string(), 3, None), - )] - impl Subscription { - #[graphql( - directive = testDirective::apply("object field".to_string(), 4, None), - directive = noArgsDirective::apply()) - ] - async fn value(&self) -> impl Stream { - stream::iter(vec!["abc"]) - } - - async fn another_value(&self) -> impl Stream { - stream::iter(vec![SimpleValue { - some_data: "data".to_string(), - }]) - } - } - - let schema = Schema::build(Query, EmptyMutation, Subscription) - .enable_subscription_in_federation() - .finish(); - - let sdl = schema.sdl_with_options(SDLExportOptions::new().federation().compose_directive()); - - let expected = include_str!("schemas/test_fed2_compose.schema.graphql"); - assert_eq!(expected, &sdl) -} - -#[test] -fn test_type_directive_2() { - #[TypeDirective(location = "FieldDefinition")] - fn type_directive_field_definition(description: String) {} - - #[TypeDirective(location = "ArgumentDefinition")] - fn type_directive_argument_definition(description: String) {} - - #[TypeDirective(location = "InputFieldDefinition")] - fn type_directive_input_field_definition(description: String) {} - - #[TypeDirective(location = "Object")] - fn type_directive_object(description: String) {} - - #[TypeDirective(location = "InputObject")] - fn type_directive_input_object(description: String) {} - - #[TypeDirective(location = "Enum")] - fn type_directive_enum(description: String) {} - - #[TypeDirective(location = "EnumValue")] - fn type_directive_enum_value(description: String) {} - - #[TypeDirective(location = "Interface")] - fn type_directive_interface(description: String) {} - - #[derive(InputObject)] - #[graphql(directive = type_directive_input_object::apply("This is INPUT_OBJECT in InputObject".to_string()))] - struct TestInput { - #[graphql(directive = type_directive_input_field_definition::apply("This is INPUT_FIELD_DEFINITION".to_string()))] - field: String, - } - - #[derive(OneofObject)] - #[graphql(directive = type_directive_input_object::apply("This is INPUT_OBJECT in OneofObject".to_string()))] - enum TestOneOfObject { - #[graphql(directive = type_directive_input_field_definition::apply("This is INPUT_FIELD_DEFINITION in OneofObject".to_string()))] - Foo(String), - #[graphql(directive = type_directive_input_field_definition::apply("This is INPUT_FIELD_DEFINITION in OneofObject".to_string()))] - Bar(i32), - } - - #[derive(SimpleObject)] - #[graphql(directive = type_directive_object::apply("This is OBJECT in SimpleObject".to_string()))] - struct TestSimpleObject { - #[graphql(directive = type_directive_field_definition::apply("This is FIELD_DEFINITION in SimpleObject".to_string()))] - field: String, - } - - #[derive(SimpleObject)] - #[graphql(complex, directive = type_directive_object::apply("This is OBJECT in (Complex / Simple)Object".to_string()))] - struct TestComplexObject { - #[graphql(directive = type_directive_field_definition::apply("This is FIELD_DEFINITION in (Complex / Simple)Object".to_string()))] - field: String, - } - - #[ComplexObject] - impl TestComplexObject { - #[graphql(directive = type_directive_field_definition::apply("This is FIELD_DEFINITION in ComplexObject".to_string()))] - async fn test( - &self, - #[graphql(directive = type_directive_argument_definition::apply("This is ARGUMENT_DEFINITION in ComplexObject.arg1".to_string()))] - _arg1: String, - #[graphql(directive = type_directive_argument_definition::apply("This is ARGUMENT_DEFINITION in ComplexObject.arg2".to_string()))] - _arg2: String, - ) -> &'static str { - "test" - } - } - - #[derive(Enum, Copy, Clone, PartialEq, Eq)] - #[graphql(directive = type_directive_enum::apply("This is ENUM in Enum".to_string()))] - enum TestEnum { - #[graphql(directive = type_directive_enum_value::apply("This is ENUM_VALUE in Enum".to_string()))] - Foo, - #[graphql(directive = type_directive_enum_value::apply("This is ENUM_VALUE in Enum".to_string()))] - Bar, - } - - struct TestObjectForInterface; - - #[Object] - impl TestObjectForInterface { - async fn field( - &self, - #[graphql(directive = type_directive_argument_definition::apply("This is ARGUMENT_DEFINITION in Interface.arg1".to_string()))] - _arg1: String, - #[graphql(directive = type_directive_argument_definition::apply("This is ARGUMENT_DEFINITION in Interface.arg2".to_string()))] - _arg2: String, - ) -> &'static str { - "hello" - } - } - #[derive(Interface)] - #[graphql( - field( - name = "field", - ty = "String", - directive = type_directive_field_definition::apply("This is INTERFACE in Interface".to_string()), - arg( - name = "_arg1", - ty = "String", - directive = type_directive_argument_definition::apply("This is ARGUMENT_DEFINITION in Interface.arg1".to_string()) - ), - arg( - name = "_arg2", - ty = "String", - directive = type_directive_argument_definition::apply("This is ARGUMENT_DEFINITION in Interface.arg2".to_string()) - ) - ), - directive = type_directive_interface::apply("This is INTERFACE in Interface".to_string()) - )] - enum TestInterface { - TestSimpleObjectForInterface(TestObjectForInterface), - } - - struct Query; - - #[Object] - impl Query { - pub async fn test_argument( - &self, - #[graphql(directive = type_directive_argument_definition::apply("This is ARGUMENT_DEFINITION in Object.arg1".to_string()))] - _arg1: String, - #[graphql(directive = type_directive_argument_definition::apply("This is ARGUMENT_DEFINITION in Object.arg2".to_string()))] - _arg2: String, - ) -> &'static str { - "hello" - } - - pub async fn test_input_object(&self, _arg: TestInput) -> &'static str { - "hello" - } - - pub async fn test_complex_object(&self) -> TestComplexObject { - TestComplexObject { - field: "hello".to_string(), - } - } - - pub async fn test_simple_object(&self) -> TestSimpleObject { - TestSimpleObject { - field: "hello".to_string(), - } - } - - pub async fn test_one_of_object(&self, _arg: TestOneOfObject) -> &'static str { - "hello" - } - - pub async fn test_enum(&self, _arg: TestEnum) -> &'static str { - "hello" - } - - pub async fn test_interface(&self) -> TestObjectForInterface { - TestObjectForInterface - } - } - - let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .register_output_type::() - .finish(); - let sdl = schema.sdl(); - let expected = include_str!("schemas/test_fed2_compose_2.schema.graphql"); - assert_eq!(expected, sdl); -} diff --git a/tests/union.rs b/tests/union.rs deleted file mode 100644 index 30ba5978b..000000000 --- a/tests/union.rs +++ /dev/null @@ -1,630 +0,0 @@ -use async_graphql::*; - -#[tokio::test] -pub async fn test_union_simple_object() { - #[derive(SimpleObject)] - struct MyObj { - id: i32, - title: String, - } - - #[derive(Union)] - enum Node { - MyObj(MyObj), - } - - struct Query; - - #[Object] - impl Query { - async fn node(&self) -> Node { - MyObj { - id: 33, - title: "haha".to_string(), - } - .into() - } - } - - let query = r#"{ - node { - ... on MyObj { - id - } - } - }"#; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "node": { - "id": 33, - } - }) - ); -} - -#[tokio::test] -pub async fn test_union_simple_object2() { - #[derive(SimpleObject)] - struct MyObj { - id: i32, - title: String, - } - - #[derive(Union)] - enum Node { - MyObj(MyObj), - } - - struct Query; - - #[Object] - impl Query { - async fn node(&self) -> Node { - MyObj { - id: 33, - title: "haha".to_string(), - } - .into() - } - } - - let query = r#"{ - node { - ... on MyObj { - id - } - } - }"#; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "node": { - "id": 33, - } - }) - ); -} - -#[tokio::test] -pub async fn test_multiple_unions() { - struct MyObj; - - #[Object] - impl MyObj { - async fn value_a(&self) -> i32 { - 1 - } - - async fn value_b(&self) -> i32 { - 2 - } - - async fn value_c(&self) -> i32 { - 3 - } - } - - #[derive(Union)] - enum UnionA { - MyObj(MyObj), - } - - #[derive(Union)] - enum UnionB { - MyObj(MyObj), - } - - struct Query; - - #[Object] - impl Query { - async fn union_a(&self) -> UnionA { - MyObj.into() - } - async fn union_b(&self) -> UnionB { - MyObj.into() - } - } - - let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .register_output_type::() // `UnionA` is not directly referenced, so manual registration is required. - .finish(); - let query = r#"{ - unionA { - ... on MyObj { - valueA - valueB - valueC - } - } - unionB { - ... on MyObj { - valueA - valueB - valueC - } - } - }"#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "unionA": { - "valueA": 1, - "valueB": 2, - "valueC": 3, - }, - "unionB": { - "valueA": 1, - "valueB": 2, - "valueC": 3, - } - }) - ); -} - -#[tokio::test] -pub async fn test_multiple_objects_in_multiple_unions() { - struct MyObjOne; - - #[Object] - impl MyObjOne { - async fn value_a(&self) -> i32 { - 1 - } - - async fn value_b(&self) -> i32 { - 2 - } - - async fn value_c(&self) -> i32 { - 3 - } - } - - struct MyObjTwo; - - #[Object] - impl MyObjTwo { - async fn value_a(&self) -> i32 { - 1 - } - } - - #[derive(Union)] - enum UnionA { - MyObjOne(MyObjOne), - MyObjTwo(MyObjTwo), - } - - #[derive(Union)] - enum UnionB { - MyObjOne(MyObjOne), - } - - struct Query; - - #[Object] - impl Query { - async fn my_obj(&self) -> Vec { - vec![MyObjOne.into(), MyObjTwo.into()] - } - } - - let schema = Schema::build(Query, EmptyMutation, EmptySubscription) - .register_output_type::() // `UnionB` is not directly referenced, so manual registration is required. - .finish(); - let query = r#"{ - myObj { - ... on MyObjTwo { - valueA - } - ... on MyObjOne { - valueA - valueB - valueC - } - } - }"#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "myObj": [{ - "valueA": 1, - "valueB": 2, - "valueC": 3, - }, { - "valueA": 1 - }] - }) - ); -} - -#[tokio::test] -pub async fn test_union_field_result() { - struct MyObj; - - #[Object] - impl MyObj { - async fn value(&self) -> Result { - Ok(10) - } - } - - #[derive(Union)] - enum Node { - MyObj(MyObj), - } - - struct Query; - - #[Object] - impl Query { - async fn node(&self) -> Node { - MyObj.into() - } - } - - let query = r#"{ - node { - ... on MyObj { - value - } - } - }"#; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "node": { - "value": 10, - } - }) - ); -} - -#[tokio::test] -pub async fn test_union_flatten() { - #[derive(SimpleObject)] - struct MyObj1 { - value1: i32, - } - - #[derive(SimpleObject)] - struct MyObj2 { - value2: i32, - } - - #[derive(Union)] - enum InnerUnion1 { - A(MyObj1), - } - - #[derive(Union)] - enum InnerUnion2 { - B(MyObj2), - } - - #[derive(Union)] - enum MyUnion { - #[graphql(flatten)] - Inner1(InnerUnion1), - - #[graphql(flatten)] - Inner2(InnerUnion2), - } - - struct Query; - - #[Object] - impl Query { - async fn value1(&self) -> MyUnion { - InnerUnion1::A(MyObj1 { value1: 99 }).into() - } - - async fn value2(&self) -> MyUnion { - InnerUnion2::B(MyObj2 { value2: 88 }).into() - } - - async fn value3(&self) -> InnerUnion1 { - InnerUnion1::A(MyObj1 { value1: 77 }) - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = r#" - { - value1 { - ... on MyObj1 { - value1 - } - } - value2 { - ... on MyObj2 { - value2 - } - } - value3 { - ... on MyObj1 { - value1 - } - } - }"#; - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "value1": { - "value1": 99, - }, - "value2": { - "value2": 88, - }, - "value3": { - "value1": 77, - } - }) - ); -} - -#[tokio::test] -pub async fn test_trait_object_in_union() { - pub trait ProductTrait: Send + Sync { - fn id(&self) -> &str; - } - - #[Object] - impl dyn ProductTrait { - #[graphql(name = "id")] - async fn gql_id(&self, _ctx: &Context<'_>) -> &str { - self.id() - } - } - - struct MyProduct; - - impl ProductTrait for MyProduct { - fn id(&self) -> &str { - "abc" - } - } - - #[derive(Union)] - pub enum Content { - Product(Box), - } - - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> Content { - Content::Product(Box::new(MyProduct)) - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ value { ... on ProductTrait { id } } }") - .await - .into_result() - .unwrap() - .data, - value!({ - "value": { - "id": "abc" - } - }) - ); -} - -macro_rules! generate_union { - ($name:ident, $variant_ty:ty) => { - #[derive(Union)] - pub enum $name { - Val($variant_ty), - } - }; -} - -#[test] -pub fn test_macro_generated_union() { - #[derive(SimpleObject)] - pub struct IntObj { - pub val: i32, - } - - generate_union!(MyEnum, IntObj); - - let _ = MyEnum::Val(IntObj { val: 1 }); -} - -#[tokio::test] -pub async fn test_union_with_oneof_object() { - #[derive(SimpleObject, InputObject)] - #[graphql(input_name = "MyObjInput")] - struct MyObj { - id: i32, - title: String, - } - - #[derive(OneofObject, Union)] - #[graphql(input_name = "NodeInput")] - enum Node { - MyObj(MyObj), - } - - struct Query; - - #[Object] - impl Query { - async fn node(&self, input: Node) -> Node { - input - } - } - - let query = r#"{ - node(input: { myObj: { id: 10, title: "abc" } }) { - ... on MyObj { - id title - } - } - }"#; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "node": { - "id": 10, - "title": "abc", - } - }) - ); -} - -#[tokio::test] -pub async fn test_union_with_generic() { - struct MyObj { - value: T, - } - - #[Object( - concrete(name = "MyObjString", params(String)), - concrete(name = "MyObjInt", params(i64)) - )] - impl MyObj { - async fn id(&self) -> i32 { - 10 - } - - async fn title(&self) -> String { - "abc".to_string() - } - - async fn value(&self) -> &T { - &self.value - } - } - - #[derive(Union)] - #[graphql(concrete(name = "NodeInt", params(i64)))] - #[graphql(concrete(name = "NodeString", params(String)))] - enum Node { - MyObj(MyObj), - } - - struct Query; - - #[Object] - impl Query { - async fn node_int(&self) -> Node { - Node::MyObj(MyObj { value: 10 }) - } - - async fn node_str(&self) -> Node { - Node::MyObj(MyObj { - value: "abc".to_string(), - }) - } - } - - let query = r#"{ - nodeInt { - ... on MyObjInt { - value - } - } - nodeStr { - ... on MyObjString { - value - } - } - }"#; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "nodeInt": { - "value": 10, - }, - "nodeStr": { - "value": "abc", - } - }) - ); - - println!("{}", schema.sdl()); -} - -#[tokio::test] -pub async fn test_union_with_sub_generic() { - struct MyObj { - _marker: std::marker::PhantomData, - } - - #[Object] - impl MyObj { - async fn id(&self) -> i32 { - 10 - } - } - - struct MyObj2 { - _marker: std::marker::PhantomData, - } - - #[Object] - impl MyObj2 { - async fn id(&self) -> i32 { - 10 - } - } - - #[derive(Union)] - #[graphql(concrete(name = "NodeMyObj", params("MyObj"), bounds("G: Send + Sync")))] - enum Node { - Nested(MyObj2), - NotNested(T), - } - - struct Query; - - #[Object] - impl Query { - async fn nested(&self) -> Node> { - Node::Nested(MyObj2 { - _marker: std::marker::PhantomData, - }) - } - - async fn not_nested(&self) -> Node> { - Node::NotNested(MyObj { - _marker: std::marker::PhantomData, - }) - } - } - - let query = r#"{ - nested { - ... on MyObj { - id - } - ... on MyObj2 { - id - } - } - }"#; - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "nested": { - "id": 10, - }, - }) - ); - - println!("{}", schema.sdl()); -} diff --git a/tests/use_type_description.rs b/tests/use_type_description.rs deleted file mode 100644 index 70df509d4..000000000 --- a/tests/use_type_description.rs +++ /dev/null @@ -1,180 +0,0 @@ -#![allow(clippy::diverging_sub_expression)] - -use async_graphql::*; -use chrono::{DateTime, Utc}; -use futures_util::stream::Stream; - -#[tokio::test] -pub async fn test_object() { - /// Haha - #[derive(Description, Default)] - struct MyObj; - - #[Object(use_type_description)] - impl MyObj { - async fn value(&self) -> i32 { - 100 - } - } - - #[derive(SimpleObject, Default)] - struct Query { - obj: MyObj, - } - - let schema = Schema::new(Query::default(), EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute(r#"{ __type(name: "MyObj") { description } }"#) - .await - .data, - value!({ - "__type": { "description": "Haha" } - }) - ); -} - -#[tokio::test] -pub async fn test_object_with_lifetime() { - /// Haha - #[derive(Description, Default)] - struct MyObj<'a>(&'a str); - - #[Object(use_type_description)] - impl MyObj<'_> { - async fn value(&self) -> &str { - self.0 - } - } - - struct Query; - - #[Object] - #[allow(unreachable_code)] - impl Query { - async fn obj(&self) -> MyObj<'_> { - todo!() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute(r#"{ __type(name: "MyObj") { description } }"#) - .await - .data, - value!({ - "__type": { "description": "Haha" } - }) - ); -} - -#[tokio::test] -pub async fn test_scalar() { - /// Haha - #[derive(Description, Default)] - struct MyScalar(i32); - - #[Scalar(use_type_description)] - impl ScalarType for MyScalar { - fn parse(_value: Value) -> InputValueResult { - Ok(MyScalar(42)) - } - - fn to_value(&self) -> Value { - Value::Number(self.0.into()) - } - } - - #[derive(SimpleObject, Default)] - struct Query { - obj: MyScalar, - } - - let schema = Schema::new(Query::default(), EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute(r#"{ __type(name: "MyScalar") { description } }"#) - .await - .data, - value!({ - "__type": { "description": "Haha" } - }) - ); -} - -#[tokio::test] -pub async fn test_subscription() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 100 - } - } - - /// Haha - #[derive(Description, Default)] - struct Subscription; - - #[Subscription(use_type_description)] - impl Subscription { - async fn values(&self) -> impl Stream { - futures_util::stream::once(async move { 100 }) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - assert_eq!( - schema - .execute(r#"{ __type(name: "Subscription") { description } }"#) - .await - .data, - value!({ - "__type": { "description": "Haha" } - }) - ); -} - -#[tokio::test] -pub async fn test_override_description() { - /// Haha - #[derive(SimpleObject)] - struct Query { - value: i32, - value2: DateTime, - } - - let schema = Schema::build( - Query { - value: 100, - value2: Utc::now(), - }, - EmptyMutation, - EmptySubscription, - ) - .override_output_type_description::("Hehe") - .override_output_type_description::>("DT") - .finish(); - - assert_eq!( - schema - .execute(r#"{ __type(name: "Query") { description } }"#) - .await - .data, - value!({ - "__type": { "description": "Hehe" } - }) - ); - - assert_eq!( - schema - .execute(r#"{ __type(name: "DateTime") { description } }"#) - .await - .data, - value!({ - "__type": { "description": "DT" } - }) - ); -} diff --git a/tests/validators.rs b/tests/validators.rs deleted file mode 100644 index 21ea3557c..000000000 --- a/tests/validators.rs +++ /dev/null @@ -1,1015 +0,0 @@ -#![allow(clippy::uninlined_format_args)] -#![allow(clippy::diverging_sub_expression)] - -use std::sync::Arc; - -use async_graphql::*; -use futures_util::{Stream, StreamExt}; - -#[tokio::test] -pub async fn test_all_validator() { - struct Query; - - #[Object] - #[allow(unreachable_code, unused_variables)] - impl Query { - async fn multiple_of(&self, #[graphql(validator(multiple_of = 10))] n: i32) -> i32 { - todo!() - } - - async fn maximum(&self, #[graphql(validator(maximum = 10))] n: i32) -> i32 { - todo!() - } - - async fn minimum(&self, #[graphql(validator(minimum = 10))] n: i32) -> i32 { - todo!() - } - - async fn max_length(&self, #[graphql(validator(max_length = 10))] n: String) -> i32 { - todo!() - } - - async fn min_length(&self, #[graphql(validator(min_length = 10))] n: String) -> i32 { - todo!() - } - - async fn max_items(&self, #[graphql(validator(max_items = 10))] n: Vec) -> i32 { - todo!() - } - - async fn min_items(&self, #[graphql(validator(min_items = 10))] n: Vec) -> i32 { - todo!() - } - - async fn chars_max_length( - &self, - #[graphql(validator(chars_max_length = 10))] n: String, - ) -> i32 { - todo!() - } - - async fn chars_length( - &self, - #[graphql(validator(chars_min_length = 10))] n: String, - ) -> i32 { - todo!() - } - - async fn email(&self, #[graphql(validator(email))] n: String) -> i32 { - todo!() - } - - async fn url(&self, #[graphql(validator(url))] n: String) -> i32 { - todo!() - } - - async fn ip(&self, #[graphql(validator(ip))] n: String) -> i32 { - todo!() - } - - async fn regex(&self, #[graphql(validator(regex = "^[0-9]+$"))] n: String) -> i32 { - todo!() - } - - async fn list_email(&self, #[graphql(validator(list, email))] n: Vec) -> i32 { - todo!() - } - } -} - -#[tokio::test] -pub async fn test_validator_on_object_field_args() { - struct Query; - - #[Object] - impl Query { - async fn value(&self, #[graphql(validator(maximum = 10))] n: i32) -> i32 { - n - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ value(n: 5) }") - .await - .into_result() - .unwrap() - .data, - value!({ "value": 5 }) - ); - - assert_eq!( - schema - .execute("{ value(n: 11) }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "Int": the value is 11, must be less than or equal to 10"# - .to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 12 - }], - path: vec![PathSegment::Field("value".to_string())], - extensions: None - }] - ); -} - -#[tokio::test] -pub async fn test_validator_on_input_object_field() { - #[derive(InputObject)] - struct MyInput { - #[graphql(validator(maximum = 10))] - a: i32, - #[graphql(validator(maximum = 10))] - b: Option, - } - - struct Query; - - #[Object] - impl Query { - async fn value(&self, input: MyInput) -> i32 { - input.a + input.b.unwrap_or_default() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ value(input: {a: 5}) }") - .await - .into_result() - .unwrap() - .data, - value!({ "value": 5 }) - ); - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ value(input: {a: 5, b: 7}) }") - .await - .into_result() - .unwrap() - .data, - value!({ "value": 12 }) - ); - - assert_eq!( - schema - .execute("{ value(input: {a: 11}) }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "Int": the value is 11, must be less than or equal to 10 (occurred while parsing "MyInput")"# - .to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 16 - }], - path: vec![PathSegment::Field("value".to_string())], - extensions: None - }] - ); - - assert_eq!( - schema - .execute("{ value(input: {a: 5, b: 20}) }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "Int": the value is 20, must be less than or equal to 10 (occurred while parsing "MyInput")"# - .to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 16 - }], - path: vec![PathSegment::Field("value".to_string())], - extensions: None - }] - ); -} - -#[tokio::test] -pub async fn test_validator_on_complex_object_field_args() { - #[derive(SimpleObject)] - #[graphql(complex)] - struct Query { - a: i32, - } - - #[ComplexObject] - impl Query { - async fn value(&self, #[graphql(validator(maximum = 10))] n: i32) -> i32 { - n - } - } - - let schema = Schema::new(Query { a: 10 }, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ value(n: 5) }") - .await - .into_result() - .unwrap() - .data, - value!({ "value": 5 }) - ); - - assert_eq!( - schema - .execute("{ value(n: 11) }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "Int": the value is 11, must be less than or equal to 10"# - .to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 12 - }], - path: vec![PathSegment::Field("value".to_string())], - extensions: None - }] - ); -} - -#[tokio::test] -pub async fn test_validator_on_subscription_field_args() { - struct Query; - - #[Object] - impl Query { - async fn value(&self) -> i32 { - 1 - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn value( - &self, - #[graphql(validator(maximum = 10))] n: i32, - ) -> impl Stream { - futures_util::stream::iter(vec![n]) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - assert_eq!( - schema - .execute_stream("subscription { value(n: 5) }") - .collect::>() - .await - .remove(0) - .into_result() - .unwrap() - .data, - value!({ "value": 5 }) - ); - - assert_eq!( - schema - .execute_stream("subscription { value(n: 11) }") - .collect::>() - .await - .remove(0) - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "Int": the value is 11, must be less than or equal to 10"# - .to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 25 - }], - path: vec![PathSegment::Field("value".to_string())], - extensions: None - }] - ); -} - -#[tokio::test] -pub async fn test_custom_validator() { - struct MyValidator { - expect: i32, - } - - impl MyValidator { - pub fn new(n: i32) -> Self { - MyValidator { expect: n } - } - } - - impl CustomValidator for MyValidator { - fn check(&self, value: &i32) -> Result<(), InputValueError> { - if *value == self.expect { - Ok(()) - } else { - Err(InputValueError::custom(format!( - "expect 100, actual {}", - value - ))) - } - } - } - - #[derive(InputObject)] - struct MyInput { - #[graphql(validator(custom = "MyValidator::new(100)"))] - n: i32, - } - - struct Query; - - #[Object] - impl Query { - async fn value( - &self, - #[graphql(validator(custom = "MyValidator::new(100)"))] n: i32, - ) -> i32 { - n - } - - async fn input(&self, input: MyInput) -> i32 { - input.n - } - - async fn value2( - &self, - #[graphql(validator(list, custom = "MyValidator::new(100)"))] values: Vec, - ) -> i32 { - values.into_iter().sum() - } - - async fn value3( - &self, - #[graphql(validator(list, custom = "MyValidator::new(100)"))] values: Option>, - ) -> i32 { - values.into_iter().flatten().sum() - } - } - - struct Subscription; - - #[Subscription] - impl Subscription { - async fn value( - &self, - #[graphql(validator(custom = "MyValidator::new(100)"))] n: i32, - ) -> impl Stream { - futures_util::stream::iter(vec![n]) - } - } - - let schema = Schema::new(Query, EmptyMutation, Subscription); - assert_eq!( - schema - .execute("{ value(n: 100) }") - .await - .into_result() - .unwrap() - .data, - value!({ "value": 100 }) - ); - - assert_eq!( - schema - .execute("{ value(n: 11) }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "Int": expect 100, actual 11"#.to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 12 - }], - path: vec![PathSegment::Field("value".to_string())], - extensions: None - }] - ); - - assert_eq!( - schema - .execute("{ input(input: {n: 100} ) }") - .await - .into_result() - .unwrap() - .data, - value!({ "input": 100 }) - ); - assert_eq!( - schema - .execute("{ input(input: {n: 11} ) }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: - r#"Failed to parse "Int": expect 100, actual 11 (occurred while parsing "MyInput")"# - .to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 16 - }], - path: vec![PathSegment::Field("input".to_string())], - extensions: None - }] - ); - - assert_eq!( - schema - .execute_stream("subscription { value(n: 100 ) }") - .next() - .await - .unwrap() - .into_result() - .unwrap() - .data, - value!({ "value": 100 }) - ); - - assert_eq!( - schema - .execute_stream("subscription { value(n: 11 ) }") - .next() - .await - .unwrap() - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "Int": expect 100, actual 11"#.to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 25 - }], - path: vec![PathSegment::Field("value".to_string())], - extensions: None - }] - ); - - assert_eq!( - schema - .execute("{ value2(values: [77, 88] ) }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "Int": expect 100, actual 77"#.to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 18 - }], - path: vec![PathSegment::Field("value2".to_string())], - extensions: None - }] - ); - - assert_eq!( - schema - .execute("{ value3(values: [77, 88] ) }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "Int": expect 100, actual 77"#.to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 18 - }], - path: vec![PathSegment::Field("value3".to_string())], - extensions: None - }] - ); - - assert_eq!( - schema - .execute("{ value3(values: null ) }") - .await - .into_result() - .unwrap() - .data, - value!({ - "value3": 0 - }) - ); -} - -#[tokio::test] -pub async fn test_custom_validator_with_fn() { - fn check_100(value: &i32) -> Result<(), String> { - if *value == 100 { - Ok(()) - } else { - Err(format!("expect 100, actual {}", value)) - } - } - - struct Query; - - #[Object] - impl Query { - async fn value(&self, #[graphql(validator(custom = "check_100"))] n: i32) -> i32 { - n - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ value(n: 100) }") - .await - .into_result() - .unwrap() - .data, - value!({ "value": 100 }) - ); - - assert_eq!( - schema - .execute("{ value(n: 11) }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "Int": expect 100, actual 11"#.to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 12 - }], - path: vec![PathSegment::Field("value".to_string())], - extensions: None - }] - ); -} - -#[tokio::test] -pub async fn test_custom_validator_with_extensions() { - struct MyValidator { - expect: i32, - } - - impl MyValidator { - pub fn new(n: i32) -> Self { - MyValidator { expect: n } - } - } - - impl CustomValidator for MyValidator { - fn check(&self, value: &i32) -> Result<(), InputValueError> { - if *value == self.expect { - Ok(()) - } else { - Err( - InputValueError::custom(format!("expect 100, actual {}", value)) - .with_extension("code", 99), - ) - } - } - } - - struct Query; - - #[Object] - impl Query { - async fn value( - &self, - #[graphql(validator(custom = "MyValidator::new(100)"))] n: i32, - ) -> i32 { - n - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ value(n: 100) }") - .await - .into_result() - .unwrap() - .data, - value!({ "value": 100 }) - ); - - let mut error_extensions = ErrorExtensionValues::default(); - error_extensions.set("code", 99); - assert_eq!( - schema - .execute("{ value(n: 11) }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "Int": expect 100, actual 11"#.to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 12 - }], - path: vec![PathSegment::Field("value".to_string())], - extensions: Some(error_extensions) - }] - ); -} - -#[tokio::test] -pub async fn test_list_validator() { - struct Query; - - #[Object] - impl Query { - async fn value(&self, #[graphql(validator(maximum = 3, list))] n: Vec) -> i32 { - n.into_iter().sum() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute("{ value(n: [1, 2, 3]) }") - .await - .into_result() - .unwrap() - .data, - value!({ "value": 6 }) - ); - - assert_eq!( - schema - .execute("{ value(n: [1, 2, 3, 4]) }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "Int": the value is 4, must be less than or equal to 3"# - .to_string(), - source: None, - locations: vec![Pos { - line: 1, - column: 12 - }], - path: vec![PathSegment::Field("value".to_string())], - extensions: None - }] - ); -} - -#[tokio::test] -pub async fn test_validate_wrapper_types() { - #[derive(NewType)] - struct Size(i32); - - struct Query; - - #[Object] - impl Query { - async fn a(&self, #[graphql(validator(maximum = 10))] n: Option) -> i32 { - n.unwrap_or_default() - } - - async fn b(&self, #[graphql(validator(maximum = 10))] n: Option>) -> i32 { - n.unwrap_or_default().unwrap_or_default() - } - - async fn c(&self, #[graphql(validator(maximum = 10))] n: MaybeUndefined) -> i32 { - n.take().unwrap_or_default() - } - - async fn d(&self, #[graphql(validator(maximum = 10))] n: Box) -> i32 { - *n - } - - async fn e(&self, #[graphql(validator(maximum = 10))] n: Arc) -> i32 { - *n - } - - async fn f(&self, #[graphql(validator(maximum = 10))] n: Json) -> i32 { - n.0 - } - - async fn g(&self, #[graphql(validator(maximum = 10))] n: Option>) -> i32 { - n.map(|n| n.0).unwrap_or_default() - } - - async fn h(&self, #[graphql(validator(maximum = 10))] n: Size) -> i32 { - n.0 - } - - async fn i(&self, #[graphql(validator(list, maximum = 10))] n: Vec) -> i32 { - n.into_iter().sum() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - let successes = [ - ("{ a(n: 5) }", value!({ "a": 5 })), - ("{ a }", value!({ "a": 0 })), - ("{ b(n: 5) }", value!({ "b": 5 })), - ("{ b }", value!({ "b": 0 })), - ("{ c(n: 5) }", value!({ "c": 5 })), - ("{ c(n: null) }", value!({ "c": 0 })), - ("{ c }", value!({ "c": 0 })), - ("{ d(n: 5) }", value!({ "d": 5 })), - ("{ e(n: 5) }", value!({ "e": 5 })), - ("{ f(n: 5) }", value!({ "f": 5 })), - ("{ g(n: 5) }", value!({ "g": 5 })), - ("{ g }", value!({ "g": 0 })), - ("{ h(n: 5) }", value!({ "h": 5 })), - ("{ i(n: [1, 2, 3]) }", value!({ "i": 6 })), - ]; - - for (query, res) in successes { - assert_eq!(schema.execute(query).await.into_result().unwrap().data, res); - } - - assert_eq!( - schema - .execute("{ a(n:20) }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "Int": the value is 20, must be less than or equal to 10"# - .to_string(), - source: None, - locations: vec![Pos { line: 1, column: 7 }], - path: vec![PathSegment::Field("a".to_string())], - extensions: None - }] - ); - - assert_eq!( - schema - .execute("{ b(n:20) }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "Int": the value is 20, must be less than or equal to 10"# - .to_string(), - source: None, - locations: vec![Pos { line: 1, column: 7 }], - path: vec![PathSegment::Field("b".to_string())], - extensions: None - }] - ); - - assert_eq!( - schema - .execute("{ f(n:20) }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "Int": the value is 20, must be less than or equal to 10"# - .to_string(), - source: None, - locations: vec![Pos { line: 1, column: 7 }], - path: vec![PathSegment::Field("f".to_string())], - extensions: None - }] - ); -} - -#[tokio::test] -pub async fn test_list_both_max_items_and_max_length() { - struct Query; - - #[Object] - impl Query { - async fn value( - &self, - #[graphql(validator(list, max_length = 3, max_items = 2))] values: Vec, - ) -> String { - values.into_iter().collect() - } - - async fn value2( - &self, - #[graphql(validator(list, max_length = 3, max_items = 2))] values: Option>, - ) -> String { - values.into_iter().flatten().collect() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute(r#"{ value(values: ["a", "b", "cdef"])}"#) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "[String!]": the value length is 3, must be less than or equal to 2"#.to_string(), - source: None, - locations: vec![Pos { column: 17, line: 1}], - path: vec![PathSegment::Field("value".to_string())], - extensions: None - }] - ); - - assert_eq!( - schema - .execute(r#"{ value(values: ["a", "cdef"])}"#) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "String": the string length is 4, must be less than or equal to 3"#.to_string(), - source: None, - locations: vec![Pos { column: 17, line: 1}], - path: vec![PathSegment::Field("value".to_string())], - extensions: None - }] - ); - - assert_eq!( - schema - .execute(r#"{ value(values: ["a", "b"])}"#) - .await - .into_result() - .unwrap() - .data, - value!({ - "value": "ab" - }) - ); - - assert_eq!( - schema - .execute(r#"{ value2(values: ["a", "b", "cdef"])}"#) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "[String!]": the value length is 3, must be less than or equal to 2"#.to_string(), - source: None, - locations: vec![Pos { column: 18, line: 1}], - path: vec![PathSegment::Field("value2".to_string())], - extensions: None - }] - ); - - assert_eq!( - schema - .execute(r#"{ value2(values: ["a", "cdef"])}"#) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "String": the string length is 4, must be less than or equal to 3"#.to_string(), - source: None, - locations: vec![Pos { column: 18, line: 1}], - path: vec![PathSegment::Field("value2".to_string())], - extensions: None - }] - ); - - assert_eq!( - schema - .execute(r#"{ value2(values: ["a", "b", "cdef"])}"#) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "[String!]": the value length is 3, must be less than or equal to 2"#.to_string(), - source: None, - locations: vec![Pos { column: 18, line: 1}], - path: vec![PathSegment::Field("value2".to_string())], - extensions: None - }] - ); - - assert_eq!( - schema - .execute(r#"{ value2(values: null)}"#) - .await - .into_result() - .unwrap() - .data, - value!({ - "value2": "" - }) - ); -} - -#[tokio::test] -pub async fn test_issue_1164() { - struct PasswordValidator; - - impl CustomValidator for PasswordValidator { - /// Check if `value` only contains allowed chars - fn check(&self, value: &String) -> Result<(), InputValueError> { - let allowed_chars = ['1', '2', '3', '4', '5', '6']; - - if value - .chars() - .all(|c| allowed_chars.contains(&c.to_ascii_lowercase())) - { - Ok(()) - } else { - Err(InputValueError::custom(format!( - "illegal char in password: `{}`", - value - ))) - } - } - } - - struct Query; - - #[Object] - impl Query { - async fn a( - &self, - #[graphql(validator(min_length = 6, max_length = 16, custom = "PasswordValidator"))] - value: String, - ) -> String { - value - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - assert_eq!( - schema - .execute(r#"{ a(value: "123456")}"#) - .await - .into_result() - .unwrap() - .data, - value!({ - "a": "123456" - }) - ); - - assert_eq!( - schema - .execute(r#"{ a(value: "123") }"#) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "String": the string length is 3, must be greater than or equal to 6"#.to_string(), - source: None, - locations: vec![Pos { column: 12, line: 1}], - path: vec![PathSegment::Field("a".to_string())], - extensions: None - }] - ); - - assert_eq!( - schema - .execute(r#"{ a(value: "123123123123123123") }"#) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "String": the string length is 18, must be less than or equal to 16"#.to_string(), - source: None, - locations: vec![Pos { column: 12, line: 1}], - path: vec![PathSegment::Field("a".to_string())], - extensions: None - }] - ); - - assert_eq!( - schema - .execute(r#"{ a(value: "abcdef") }"#) - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: r#"Failed to parse "String": illegal char in password: `abcdef`"#.to_string(), - source: None, - locations: vec![Pos { - column: 12, - line: 1 - }], - path: vec![PathSegment::Field("a".to_string())], - extensions: None - }] - ); -} diff --git a/tests/variables.rs b/tests/variables.rs deleted file mode 100644 index 43b33a133..000000000 --- a/tests/variables.rs +++ /dev/null @@ -1,411 +0,0 @@ -use std::collections::HashMap; - -use async_graphql::*; - -#[tokio::test] -pub async fn test_variables() { - struct Query; - - #[Object] - impl Query { - pub async fn int_val(&self, value: i32) -> i32 { - value - } - - pub async fn int_list_val(&self, value: Vec) -> Vec { - value - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = Request::new( - r#" - query QueryWithVariables($intVal: Int!, $intListVal: [Int!]!) { - intVal(value: $intVal) - intListVal(value: $intListVal) - } - "#, - ) - .variables(Variables::from_value(value!({ - "intVal": 10, - "intListVal": [1, 2, 3, 4, 5], - }))); - - assert_eq!( - schema.execute(query).await.data, - value!({ - "intVal": 10, - "intListVal": [1, 2, 3, 4, 5], - }) - ); -} - -#[tokio::test] -pub async fn test_variable_default_value() { - struct Query; - - #[Object] - impl Query { - pub async fn int_val(&self, value: i32) -> i32 { - value - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - assert_eq!( - schema - .execute( - r#" - query QueryWithVariables($intVal: Int = 10) { - intVal(value: $intVal) - } - "# - ) - .await - .data, - value!({ - "intVal": 10, - }) - ); -} - -#[tokio::test] -pub async fn test_variable_no_value() { - struct Query; - - #[Object] - impl Query { - pub async fn int_val(&self, value: Option) -> i32 { - value.unwrap_or(10) - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let resp = schema - .execute(Request::new( - r#" - query QueryWithVariables($intVal: Int) { - intVal(value: $intVal) - } - "#, - )) - .await - .into_result() - .unwrap(); - assert_eq!( - resp.data, - value!({ - "intVal": 10, - }) - ); -} - -#[tokio::test] -pub async fn test_variable_null() { - struct Query; - - #[Object] - impl Query { - pub async fn int_val(&self, value: Option) -> i32 { - value.unwrap_or(10) - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = Request::new( - r#" - query QueryWithVariables($intVal: Int) { - intVal(value: $intVal) - } - "#, - ) - .variables(Variables::from_value(value!({ - "intVal": null, - }))); - let resp = schema.execute(query).await; - assert_eq!( - resp.data, - value!({ - "intVal": 10, - }) - ); -} - -#[tokio::test] -pub async fn test_required_variable_with_default() { - struct Query; - - #[Object] - impl Query { - pub async fn int_val(&self, value: i32) -> i32 { - value - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - - // test variable default - { - let query = Request::new( - r#" - query QueryWithVariables($intVal: Int! = 10) { - intVal(value: $intVal) - } - "#, - ); - let resp = schema.execute(query).await; - assert_eq!( - resp.data, - value!({ - "intVal": 10, - }), - "{}", - resp.data - ); - } - - // test variable null - { - let query = Request::new( - r#" - query QueryWithVariables($intVal: Int! = 10) { - intVal(value: $intVal) - } - "#, - ) - .variables(Variables::from_value(value!({ - "intVal": null, - }))); - let resp = schema.execute(query).await; - assert_eq!( - resp.errors.first().map(|v| v.message.as_str()), - Some("Invalid value for argument \"value\", expected type \"Int\"") - ); - } -} - -#[tokio::test] -pub async fn test_variable_in_input_object() { - #[derive(InputObject)] - struct MyInput { - value: i32, - } - - struct Query; - - #[Object] - impl Query { - async fn test(&self, input: MyInput) -> i32 { - input.value - } - - async fn test2(&self, input: Vec) -> i32 { - input.iter().map(|item| item.value).sum() - } - } - - struct Mutation; - - #[Object] - impl Mutation { - async fn test(&self, input: MyInput) -> i32 { - input.value - } - } - - let schema = Schema::new(Query, Mutation, EmptySubscription); - - // test query - { - let query = r#" - query TestQuery($value: Int!) { - test(input: {value: $value }) - }"#; - let resp = schema - .execute(Request::new(query).variables(Variables::from_value(value!({ - "value": 10, - })))) - .await; - assert_eq!( - resp.data, - value!({ - "test": 10, - }) - ); - } - - // test query2 - { - let query = r#" - query TestQuery($value: Int!) { - test2(input: [{value: $value }, {value: $value }]) - }"#; - let resp = schema - .execute(Request::new(query).variables(Variables::from_value(value!({ - "value": 3, - })))) - .await; - assert_eq!( - resp.data, - value!({ - "test2": 6, - }) - ); - } - - // test mutation - { - let query = r#" - mutation TestMutation($value: Int!) { - test(input: {value: $value }) - }"#; - let resp = schema - .execute(Request::new(query).variables(Variables::from_value(value!({ - "value": 10, - })))) - .await; - assert_eq!( - resp.data, - value!({ - "test": 10, - }) - ); - } -} - -#[tokio::test] -pub async fn test_variables_enum() { - #[derive(Enum, Eq, PartialEq, Copy, Clone)] - enum MyEnum { - A, - B, - C, - } - - struct Query; - - #[Object] - impl Query { - pub async fn value(&self, value: MyEnum) -> i32 { - match value { - MyEnum::A => 1, - MyEnum::B => 2, - MyEnum::C => 3, - } - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = Request::new( - r#" - query QueryWithVariables($value1: MyEnum, $value2: MyEnum, $value3: MyEnum) { - a: value(value: $value1) - b: value(value: $value2) - c: value(value: $value3) - } - "#, - ) - .variables(Variables::from_value(value!({ - "value1": "A", - "value2": "B", - "value3": "C", - }))); - - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "a": 1, - "b": 2, - "c": 3, - }) - ); -} - -#[tokio::test] -pub async fn test_variables_json() { - struct Query; - - #[Object] - impl Query { - pub async fn value(&self, value: Json>) -> i32 { - *value.get("a-b").unwrap() - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = Request::new( - r#" - query QueryWithVariables($value: JSON) { - value(value: $value) - } - "#, - ) - .variables(Variables::from_value(value!({ - "value": { "a-b": 123 }, - }))); - - assert_eq!( - schema.execute(query).await.into_result().unwrap().data, - value!({ - "value": 123, - }) - ); -} - -#[tokio::test] -pub async fn test_variables_invalid_type() { - struct Query; - - #[Object] - impl Query { - pub async fn int_val(&self, value: Option) -> i32 { - value.unwrap_or(10) - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = Request::new( - r#" - query QueryWithVariables($intVal: invalid) { - intVal(value: $intVal) - } - "#, - ) - .variables(Variables::from_value(value!({ - "intVal": null, - }))); - let resp = schema.execute(query).await; - assert_eq!( - resp.errors.first().map(|v| v.message.as_str()), - Some("Unknown type \"invalid\"") - ); -} - -#[tokio::test] -pub async fn test_variables_invalid_type_with_value() { - struct Query; - - #[Object] - impl Query { - pub async fn int_val(&self, value: Option) -> i32 { - value.unwrap_or(10) - } - } - - let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - let query = Request::new( - r#" - query QueryWithVariables($intVal: invalid = 2) { - intVal(value: $intVal) - } - "#, - ) - .variables(Variables::from_value(value!({ - "intVal": null, - }))); - let resp = schema.execute(query).await; - assert_eq!( - resp.errors.first().map(|v| v.message.as_str()), - Some("Unknown type \"invalid\"") - ); -} diff --git a/value/Cargo.toml b/value/Cargo.toml deleted file mode 100644 index 0107c1efe..000000000 --- a/value/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "async-graphql-value" -version = "7.0.17" -authors = ["sunli ", "Koxiaet"] -edition = "2024" -description = "GraphQL value for async-graphql" -license = "MIT OR Apache-2.0" -documentation = "https://docs.rs/async-graphql/" -homepage = "https://github.com/async-graphql/async-graphql" -repository = "https://github.com/async-graphql/async-graphql" -keywords = ["futures", "async", "graphql"] -categories = ["network-programming", "asynchronous"] - -[dependencies] -serde_json.workspace = true -serde.workspace = true -bytes.workspace = true -indexmap.workspace = true - -[features] -raw_value = ["serde_json/raw_value"] diff --git a/value/LICENSE-APACHE b/value/LICENSE-APACHE deleted file mode 100644 index f8e5e5ea0..000000000 --- a/value/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file diff --git a/value/LICENSE-MIT b/value/LICENSE-MIT deleted file mode 100644 index 468cd79a8..000000000 --- a/value/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -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. \ No newline at end of file diff --git a/value/src/deserializer.rs b/value/src/deserializer.rs deleted file mode 100644 index d4fc62776..000000000 --- a/value/src/deserializer.rs +++ /dev/null @@ -1,509 +0,0 @@ -use std::{fmt, vec}; - -use indexmap::IndexMap; -use serde::{ - de::{ - self, Deserialize, DeserializeOwned, DeserializeSeed, EnumAccess, Error as DeError, - IntoDeserializer, MapAccess, SeqAccess, Unexpected, VariantAccess, Visitor, - }, - forward_to_deserialize_any, -}; - -use crate::{ConstValue, Name}; - -/// This type represents errors that can occur when deserializing. -#[derive(Debug)] -pub struct DeserializerError(String); - -impl de::Error for DeserializerError { - #[inline] - fn custom(msg: T) -> Self { - DeserializerError(msg.to_string()) - } -} - -impl std::error::Error for DeserializerError { - #[inline] - fn description(&self) -> &str { - "Value deserializer error" - } -} - -impl fmt::Display for DeserializerError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - DeserializerError(msg) => write!(f, "{}", msg), - } - } -} - -impl From for DeserializerError { - #[inline] - fn from(e: de::value::Error) -> DeserializerError { - DeserializerError(e.to_string()) - } -} - -impl ConstValue { - #[inline] - fn unexpected(&self) -> Unexpected { - match self { - ConstValue::Null => Unexpected::Unit, - ConstValue::Number(_) => Unexpected::Other("number"), - ConstValue::String(v) => Unexpected::Str(v), - ConstValue::Boolean(v) => Unexpected::Bool(*v), - ConstValue::Binary(v) => Unexpected::Bytes(v), - ConstValue::Enum(v) => Unexpected::Str(v), - ConstValue::List(_) => Unexpected::Seq, - ConstValue::Object(_) => Unexpected::Map, - } - } -} - -fn visit_array<'de, V>(array: Vec, visitor: V) -> Result -where - V: Visitor<'de>, -{ - let len = array.len(); - let mut deserializer = SeqDeserializer::new(array); - let seq = visitor.visit_seq(&mut deserializer)?; - let remaining = deserializer.iter.len(); - if remaining == 0 { - Ok(seq) - } else { - Err(DeserializerError::invalid_length( - len, - &"fewer elements in array", - )) - } -} - -fn visit_object<'de, V>( - object: IndexMap, - visitor: V, -) -> Result -where - V: Visitor<'de>, -{ - let len = object.len(); - let mut deserializer = MapDeserializer::new(object); - let map = visitor.visit_map(&mut deserializer)?; - let remaining = deserializer.iter.len(); - if remaining == 0 { - Ok(map) - } else { - Err(DeserializerError::invalid_length( - len, - &"fewer elements in map", - )) - } -} - -impl<'de> de::Deserializer<'de> for ConstValue { - type Error = DeserializerError; - - #[inline] - fn deserialize_any(self, visitor: V) -> Result<>::Value, Self::Error> - where - V: Visitor<'de>, - { - match self { - ConstValue::Null => visitor.visit_unit(), - ConstValue::Number(v) => v - .deserialize_any(visitor) - .map_err(|err| DeserializerError(err.to_string())), - ConstValue::String(v) => visitor.visit_str(&v), - ConstValue::Boolean(v) => visitor.visit_bool(v), - ConstValue::Binary(bytes) => visitor.visit_bytes(&bytes), - ConstValue::Enum(v) => visitor.visit_str(v.as_str()), - ConstValue::List(v) => visit_array(v, visitor), - ConstValue::Object(v) => visit_object(v, visitor), - } - } - - forward_to_deserialize_any! { - bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string - bytes byte_buf unit unit_struct seq tuple - tuple_struct map struct identifier ignored_any - } - - #[inline] - fn deserialize_option(self, visitor: V) -> Result<>::Value, Self::Error> - where - V: Visitor<'de>, - { - match self { - ConstValue::Null => visitor.visit_none(), - _ => visitor.visit_some(self), - } - } - - #[inline] - fn deserialize_newtype_struct( - self, - _name: &'static str, - visitor: V, - ) -> Result<>::Value, Self::Error> - where - V: Visitor<'de>, - { - visitor.visit_newtype_struct(self) - } - - fn deserialize_enum( - self, - _name: &'static str, - _variants: &'static [&'static str], - visitor: V, - ) -> Result<>::Value, Self::Error> - where - V: Visitor<'de>, - { - let (variant, value) = match self { - ConstValue::Object(value) => { - let mut iter = value.into_iter(); - let (variant, value) = match iter.next() { - Some(v) => v, - None => { - return Err(serde::de::Error::invalid_value( - Unexpected::Map, - &"map with a single key", - )); - } - }; - // enums are encoded in json as maps with a single key:value pair - if iter.next().is_some() { - return Err(serde::de::Error::invalid_value( - Unexpected::Map, - &"map with a single key", - )); - } - (variant, Some(value)) - } - ConstValue::String(variant) => (Name::new(variant), None), - ConstValue::Enum(variant) => (variant, None), - other => { - return Err(DeserializerError::invalid_type( - other.unexpected(), - &"string or map", - )); - } - }; - - visitor.visit_enum(EnumDeserializer { variant, value }) - } - - #[inline] - fn is_human_readable(&self) -> bool { - true - } -} - -struct EnumDeserializer { - variant: Name, - value: Option, -} - -impl<'de> EnumAccess<'de> for EnumDeserializer { - type Error = DeserializerError; - type Variant = VariantDeserializer; - - #[inline] - fn variant_seed(self, seed: V) -> Result<(V::Value, VariantDeserializer), DeserializerError> - where - V: DeserializeSeed<'de>, - { - let variant = self.variant.into_deserializer(); - let visitor = VariantDeserializer { value: self.value }; - seed.deserialize(variant).map(|v| (v, visitor)) - } -} - -impl IntoDeserializer<'_, DeserializerError> for ConstValue { - type Deserializer = Self; - - fn into_deserializer(self) -> Self::Deserializer { - self - } -} - -struct VariantDeserializer { - value: Option, -} - -impl<'de> VariantAccess<'de> for VariantDeserializer { - type Error = DeserializerError; - - #[inline] - fn unit_variant(self) -> Result<(), DeserializerError> { - match self.value { - Some(value) => Deserialize::deserialize(value), - None => Ok(()), - } - } - - #[inline] - fn newtype_variant_seed(self, seed: T) -> Result - where - T: DeserializeSeed<'de>, - { - match self.value { - Some(value) => seed.deserialize(value), - None => Err(DeserializerError::invalid_type( - Unexpected::UnitVariant, - &"newtype variant", - )), - } - } - - fn tuple_variant(self, _len: usize, visitor: V) -> Result - where - V: Visitor<'de>, - { - match self.value { - Some(ConstValue::List(v)) => { - serde::Deserializer::deserialize_any(SeqDeserializer::new(v), visitor) - } - Some(other) => Err(serde::de::Error::invalid_type( - other.unexpected(), - &"tuple variant", - )), - None => Err(DeserializerError::invalid_type( - Unexpected::UnitVariant, - &"tuple variant", - )), - } - } - - fn struct_variant( - self, - _fields: &'static [&'static str], - visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - match self.value { - Some(ConstValue::Object(v)) => { - serde::Deserializer::deserialize_any(MapDeserializer::new(v), visitor) - } - Some(other) => Err(DeserializerError::invalid_type( - other.unexpected(), - &"struct variant", - )), - None => Err(DeserializerError::invalid_type( - Unexpected::UnitVariant, - &"struct variant", - )), - } - } -} - -struct SeqDeserializer { - iter: vec::IntoIter, -} - -impl SeqDeserializer { - fn new(vec: Vec) -> Self { - SeqDeserializer { - iter: vec.into_iter(), - } - } -} - -impl<'de> serde::Deserializer<'de> for SeqDeserializer { - type Error = DeserializerError; - - #[inline] - fn deserialize_any(mut self, visitor: V) -> Result - where - V: Visitor<'de>, - { - let len = self.iter.len(); - if len == 0 { - visitor.visit_unit() - } else { - let ret = visitor.visit_seq(&mut self)?; - let remaining = self.iter.len(); - if remaining == 0 { - Ok(ret) - } else { - Err(DeserializerError::invalid_length( - len, - &"fewer elements in array", - )) - } - } - } - - forward_to_deserialize_any! { - bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string - bytes byte_buf option unit unit_struct newtype_struct seq tuple - tuple_struct map struct enum identifier ignored_any - } -} - -impl<'de> SeqAccess<'de> for SeqDeserializer { - type Error = DeserializerError; - - fn next_element_seed(&mut self, seed: T) -> Result, DeserializerError> - where - T: DeserializeSeed<'de>, - { - match self.iter.next() { - Some(value) => seed.deserialize(value).map(Some), - None => Ok(None), - } - } - - #[inline] - fn size_hint(&self) -> Option { - match self.iter.size_hint() { - (lower, Some(upper)) if lower == upper => Some(upper), - _ => None, - } - } -} - -struct MapDeserializer { - iter: as IntoIterator>::IntoIter, - value: Option, -} - -impl MapDeserializer { - #[inline] - fn new(map: IndexMap) -> Self { - MapDeserializer { - iter: map.into_iter(), - value: None, - } - } -} - -impl<'de> MapAccess<'de> for MapDeserializer { - type Error = DeserializerError; - - fn next_key_seed(&mut self, seed: T) -> Result, DeserializerError> - where - T: DeserializeSeed<'de>, - { - match self.iter.next() { - Some((key, value)) => { - self.value = Some(value); - let key_de = MapKeyDeserializer { key }; - seed.deserialize(key_de).map(Some) - } - None => Ok(None), - } - } - - #[inline] - fn next_value_seed(&mut self, seed: T) -> Result - where - T: DeserializeSeed<'de>, - { - match self.value.take() { - Some(value) => seed.deserialize(value), - None => Err(serde::de::Error::custom("value is missing")), - } - } - - #[inline] - fn size_hint(&self) -> Option { - match self.iter.size_hint() { - (lower, Some(upper)) if lower == upper => Some(upper), - _ => None, - } - } -} - -impl<'de> serde::Deserializer<'de> for MapDeserializer { - type Error = DeserializerError; - - #[inline] - fn deserialize_any(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - visitor.visit_map(self) - } - - forward_to_deserialize_any! { - bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string - bytes byte_buf option unit unit_struct newtype_struct seq tuple - tuple_struct map struct enum identifier ignored_any - } -} - -struct MapKeyDeserializer { - key: Name, -} - -impl<'de> serde::Deserializer<'de> for MapKeyDeserializer { - type Error = DeserializerError; - - #[inline] - fn deserialize_any(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - NameDeserializer::new(self.key).deserialize_any(visitor) - } - - #[inline] - fn deserialize_enum( - self, - name: &'static str, - variants: &'static [&'static str], - visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - self.key - .into_deserializer() - .deserialize_enum(name, variants, visitor) - } - - forward_to_deserialize_any! { - bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string - bytes byte_buf unit unit_struct seq tuple option newtype_struct - tuple_struct map struct identifier ignored_any - } -} - -struct NameDeserializer { - value: Name, -} - -impl NameDeserializer { - #[inline] - fn new(value: Name) -> Self { - NameDeserializer { value } - } -} - -impl<'de> de::Deserializer<'de> for NameDeserializer { - type Error = DeserializerError; - - #[inline] - fn deserialize_any(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - visitor.visit_string(self.value.to_string()) - } - - forward_to_deserialize_any! { - bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string - bytes byte_buf option unit unit_struct newtype_struct seq tuple enum - tuple_struct map struct identifier ignored_any - } -} - -/// Interpret a `ConstValue` as an instance of type `T`. -#[inline] -pub fn from_value(value: ConstValue) -> Result { - T::deserialize(value) -} diff --git a/value/src/extensions.rs b/value/src/extensions.rs deleted file mode 100644 index 19bcfc20d..000000000 --- a/value/src/extensions.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::{ - collections::HashMap, - ops::{Deref, DerefMut}, -}; - -use serde::{Deserialize, Deserializer, Serialize}; - -/// Extensions of a query. -#[derive(Debug, Clone, Default, Serialize, Eq, PartialEq)] -#[serde(transparent)] -pub struct Extensions(pub HashMap); - -impl<'de> Deserialize<'de> for Extensions { - fn deserialize>(deserializer: D) -> Result { - Ok(Self( - >>::deserialize(deserializer)?.unwrap_or_default(), - )) - } -} - -impl Deref for Extensions { - type Target = HashMap; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Extensions { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} diff --git a/value/src/lib.rs b/value/src/lib.rs deleted file mode 100644 index 73930bc27..000000000 --- a/value/src/lib.rs +++ /dev/null @@ -1,562 +0,0 @@ -//! Value for GraphQL. Used in the [`async-graphql`](https://crates.io/crates/async-graphql) crate. - -#![warn(missing_docs)] -#![allow(clippy::uninlined_format_args)] -#![forbid(unsafe_code)] - -mod deserializer; -mod extensions; -mod macros; -mod serializer; -mod value_serde; -mod variables; - -use std::{ - borrow::{Borrow, Cow}, - fmt::{self, Display, Formatter, Write}, - ops::Deref, - sync::Arc, -}; - -use bytes::Bytes; -pub use deserializer::{DeserializerError, from_value}; -pub use extensions::Extensions; -#[doc(hidden)] -pub use indexmap; -use indexmap::IndexMap; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -pub use serde_json::Number; -pub use serializer::{SerializerError, to_value}; -#[cfg(feature = "raw_value")] -pub use value_serde::RAW_VALUE_TOKEN; -pub use variables::Variables; - -/// A GraphQL name. -/// -/// [Reference](https://spec.graphql.org/June2018/#Name). -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Name(Arc); - -impl Serialize for Name { - fn serialize(&self, serializer: S) -> std::result::Result { - serializer.serialize_str(&self.0) - } -} - -impl Name { - /// Create a new name. - pub fn new(name: impl AsRef) -> Self { - Self(name.as_ref().into()) - } - - /// Get the name as a string. - #[must_use] - pub fn as_str(&self) -> &str { - &self.0 - } -} - -impl AsRef for Name { - fn as_ref(&self) -> &str { - &self.0 - } -} - -impl Borrow for Name { - fn borrow(&self) -> &str { - &self.0 - } -} - -impl Deref for Name { - type Target = str; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Display for Name { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Display::fmt(&self.0, f) - } -} - -impl PartialEq for Name { - fn eq(&self, other: &String) -> bool { - self.as_str() == other - } -} -impl PartialEq for Name { - fn eq(&self, other: &str) -> bool { - self.as_str() == other - } -} -impl PartialEq for String { - fn eq(&self, other: &Name) -> bool { - self == other.as_str() - } -} -impl PartialEq for str { - fn eq(&self, other: &Name) -> bool { - other == self - } -} -impl<'a> PartialEq<&'a str> for Name { - fn eq(&self, other: &&'a str) -> bool { - self == *other - } -} -impl PartialEq for &'_ str { - fn eq(&self, other: &Name) -> bool { - other == self - } -} - -impl<'de> Deserialize<'de> for Name { - fn deserialize>(deserializer: D) -> Result { - Ok(Self( - String::deserialize(deserializer)?.into_boxed_str().into(), - )) - } -} - -/// A resolved GraphQL value, for example `1` or `"Hello World!"`. -/// -/// It can be serialized and deserialized. Enums will be converted to strings. -/// Attempting to serialize `Upload` will fail, and `Enum` and `Upload` cannot -/// be deserialized. -/// -/// [Reference](https://spec.graphql.org/June2018/#Value). -#[derive(Clone, Debug, Eq)] -pub enum ConstValue { - /// `null`. - Null, - /// A number. - Number(Number), - /// A string. - String(String), - /// A boolean. - Boolean(bool), - /// A binary. - Binary(Bytes), - /// An enum. These are typically in `SCREAMING_SNAKE_CASE`. - Enum(Name), - /// A list of values. - List(Vec), - /// An object. This is a map of keys to values. - Object(IndexMap), -} - -impl PartialEq for ConstValue { - fn eq(&self, other: &ConstValue) -> bool { - match (self, other) { - (ConstValue::Null, ConstValue::Null) => true, - (ConstValue::Number(a), ConstValue::Number(b)) => a == b, - (ConstValue::Boolean(a), ConstValue::Boolean(b)) => a == b, - (ConstValue::String(a), ConstValue::String(b)) => a == b, - (ConstValue::Enum(a), ConstValue::String(b)) => a == b, - (ConstValue::String(a), ConstValue::Enum(b)) => a == b, - (ConstValue::Enum(a), ConstValue::Enum(b)) => a == b, - (ConstValue::Binary(a), ConstValue::Binary(b)) => a == b, - (ConstValue::List(a), ConstValue::List(b)) => { - if a.len() != b.len() { - return false; - } - a.iter().zip(b.iter()).all(|(a, b)| a == b) - } - (ConstValue::Object(a), ConstValue::Object(b)) => { - if a.len() != b.len() { - return false; - } - for (a_key, a_value) in a.iter() { - if let Some(b_value) = b.get(a_key.as_str()) { - if b_value != a_value { - return false; - } - } else { - return false; - } - } - - true - } - _ => false, - } - } -} - -impl From<()> for ConstValue { - fn from((): ()) -> Self { - ConstValue::Null - } -} - -macro_rules! from_integer { - ($($ty:ident),*) => { - $( - impl From<$ty> for ConstValue { - #[inline] - fn from(n: $ty) -> Self { - ConstValue::Number(n.into()) - } - } - )* - }; -} - -from_integer!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize); - -impl From for ConstValue { - #[inline] - fn from(f: f32) -> Self { - From::from(f as f64) - } -} - -impl From for ConstValue { - #[inline] - fn from(f: f64) -> Self { - Number::from_f64(f).map_or(ConstValue::Null, ConstValue::Number) - } -} - -impl From for ConstValue { - #[inline] - fn from(value: bool) -> Self { - ConstValue::Boolean(value) - } -} - -impl From for ConstValue { - #[inline] - fn from(value: String) -> Self { - ConstValue::String(value) - } -} - -impl From<&String> for ConstValue { - #[inline] - fn from(value: &String) -> Self { - ConstValue::String(value.clone()) - } -} - -impl From for ConstValue { - #[inline] - fn from(value: Name) -> Self { - ConstValue::Enum(value) - } -} - -impl<'a> From<&'a str> for ConstValue { - #[inline] - fn from(value: &'a str) -> Self { - ConstValue::String(value.into()) - } -} - -impl<'a> From> for ConstValue { - #[inline] - fn from(f: Cow<'a, str>) -> Self { - ConstValue::String(f.into_owned()) - } -} - -impl> FromIterator for ConstValue { - fn from_iter>(iter: I) -> Self { - ConstValue::List(iter.into_iter().map(Into::into).collect()) - } -} - -impl<'a, T: Clone + Into> From<&'a [T]> for ConstValue { - fn from(f: &'a [T]) -> Self { - ConstValue::List(f.iter().cloned().map(Into::into).collect()) - } -} - -impl> From> for ConstValue { - fn from(f: Vec) -> Self { - ConstValue::List(f.into_iter().map(Into::into).collect()) - } -} - -impl From> for ConstValue { - fn from(f: IndexMap) -> Self { - ConstValue::Object(f) - } -} - -impl ConstValue { - /// Convert this `ConstValue` into a `Value`. - #[must_use] - pub fn into_value(self) -> Value { - match self { - Self::Null => Value::Null, - Self::Number(num) => Value::Number(num), - Self::String(s) => Value::String(s), - Self::Boolean(b) => Value::Boolean(b), - Self::Binary(bytes) => Value::Binary(bytes), - Self::Enum(v) => Value::Enum(v), - Self::List(items) => { - Value::List(items.into_iter().map(ConstValue::into_value).collect()) - } - Self::Object(map) => Value::Object( - map.into_iter() - .map(|(key, value)| (key, value.into_value())) - .collect(), - ), - } - } - - /// Attempt to convert the value into JSON. This is equivalent to the - /// `TryFrom` implementation. - /// - /// # Errors - /// - /// Fails if serialization fails (see enum docs for more info). - pub fn into_json(self) -> serde_json::Result { - self.try_into() - } - - /// Attempt to convert JSON into a value. This is equivalent to the - /// `TryFrom` implementation. - /// - /// # Errors - /// - /// Fails if deserialization fails (see enum docs for more info). - pub fn from_json(json: serde_json::Value) -> serde_json::Result { - json.try_into() - } -} - -impl Default for ConstValue { - fn default() -> Self { - Self::Null - } -} - -impl Display for ConstValue { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Self::Number(num) => write!(f, "{}", *num), - Self::String(val) => write_quoted(val, f), - Self::Boolean(true) => f.write_str("true"), - Self::Boolean(false) => f.write_str("false"), - Self::Binary(bytes) => write_binary(bytes, f), - Self::Null => f.write_str("null"), - Self::Enum(name) => f.write_str(name), - Self::List(items) => write_list(items, f), - Self::Object(map) => write_object(map, f), - } - } -} - -impl TryFrom for ConstValue { - type Error = serde_json::Error; - fn try_from(value: serde_json::Value) -> Result { - Self::deserialize(value) - } -} - -impl TryFrom for serde_json::Value { - type Error = serde_json::Error; - fn try_from(value: ConstValue) -> Result { - serde_json::to_value(value) - } -} - -/// A GraphQL value, for example `1`, `$name` or `"Hello World!"`. This is -/// [`ConstValue`](enum.ConstValue.html) with variables. -/// -/// It can be serialized and deserialized. Enums will be converted to strings. -/// Attempting to serialize `Upload` or `Variable` will fail, and `Enum`, -/// `Upload` and `Variable` cannot be deserialized. -/// -/// [Reference](https://spec.graphql.org/June2018/#Value). -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum Value { - /// A variable, without the `$`. - Variable(Name), - /// `null`. - Null, - /// A number. - Number(Number), - /// A string. - String(String), - /// A boolean. - Boolean(bool), - /// A binary. - Binary(Bytes), - /// An enum. These are typically in `SCREAMING_SNAKE_CASE`. - Enum(Name), - /// A list of values. - List(Vec), - /// An object. This is a map of keys to values. - Object(IndexMap), -} - -impl Value { - /// Attempt to convert the value into a const value by using a function to - /// get a variable. - pub fn into_const_with( - self, - mut f: impl FnMut(Name) -> Result, - ) -> Result { - self.into_const_with_mut(&mut f) - } - - fn into_const_with_mut( - self, - f: &mut impl FnMut(Name) -> Result, - ) -> Result { - Ok(match self { - Self::Variable(name) => f(name)?, - Self::Null => ConstValue::Null, - Self::Number(num) => ConstValue::Number(num), - Self::String(s) => ConstValue::String(s), - Self::Boolean(b) => ConstValue::Boolean(b), - Self::Binary(v) => ConstValue::Binary(v), - Self::Enum(v) => ConstValue::Enum(v), - Self::List(items) => ConstValue::List( - items - .into_iter() - .map(|value| value.into_const_with_mut(f)) - .collect::>()?, - ), - Self::Object(map) => ConstValue::Object( - map.into_iter() - .map(|(key, value)| Ok((key, value.into_const_with_mut(f)?))) - .collect::>()?, - ), - }) - } - - /// Attempt to convert the value into a const value. - /// - /// Will fail if the value contains variables. - #[must_use] - pub fn into_const(self) -> Option { - self.into_const_with(|_| Err(())).ok() - } - - /// Attempt to convert the value into JSON. This is equivalent to the - /// `TryFrom` implementation. - /// - /// # Errors - /// - /// Fails if serialization fails (see enum docs for more info). - pub fn into_json(self) -> serde_json::Result { - self.try_into() - } - - /// Attempt to convert JSON into a value. This is equivalent to the - /// `TryFrom` implementation. - /// - /// # Errors - /// - /// Fails if deserialization fails (see enum docs for more info). - pub fn from_json(json: serde_json::Value) -> serde_json::Result { - json.try_into() - } -} - -impl Default for Value { - fn default() -> Self { - Self::Null - } -} - -impl Display for Value { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Self::Variable(name) => write!(f, "${}", name), - Self::Number(num) => write!(f, "{}", *num), - Self::String(val) => write_quoted(val, f), - Self::Boolean(true) => f.write_str("true"), - Self::Boolean(false) => f.write_str("false"), - Self::Binary(bytes) => write_binary(bytes, f), - Self::Null => f.write_str("null"), - Self::Enum(name) => f.write_str(name), - Self::List(items) => write_list(items, f), - Self::Object(map) => write_object(map, f), - } - } -} - -impl From for Value { - fn from(value: ConstValue) -> Self { - value.into_value() - } -} - -impl TryFrom for Value { - type Error = serde_json::Error; - fn try_from(value: serde_json::Value) -> Result { - Self::deserialize(value) - } -} -impl TryFrom for serde_json::Value { - type Error = serde_json::Error; - fn try_from(value: Value) -> Result { - serde_json::to_value(value) - } -} - -fn write_quoted(s: &str, f: &mut Formatter<'_>) -> fmt::Result { - f.write_char('"')?; - for c in s.chars() { - match c { - '\r' => f.write_str("\\r"), - '\n' => f.write_str("\\n"), - '\t' => f.write_str("\\t"), - '"' => f.write_str("\\\""), - '\\' => f.write_str("\\\\"), - c if c.is_control() => write!(f, "\\u{:04}", c as u32), - c => f.write_char(c), - }? - } - f.write_char('"') -} - -fn write_binary(bytes: &[u8], f: &mut Formatter<'_>) -> fmt::Result { - f.write_char('[')?; - let mut iter = bytes.iter().copied(); - if let Some(value) = iter.next() { - value.fmt(f)?; - } - for value in iter { - f.write_str(", ")?; - value.fmt(f)?; - } - f.write_char(']') -} - -fn write_list(list: impl IntoIterator, f: &mut Formatter<'_>) -> fmt::Result { - f.write_char('[')?; - let mut iter = list.into_iter(); - if let Some(item) = iter.next() { - item.fmt(f)?; - } - for item in iter { - f.write_str(", ")?; - item.fmt(f)?; - } - f.write_char(']') -} - -fn write_object( - object: impl IntoIterator, - f: &mut Formatter<'_>, -) -> fmt::Result { - f.write_char('{')?; - let mut iter = object.into_iter(); - if let Some((name, value)) = iter.next() { - write!(f, "{}: {}", name, value)?; - } - for (name, value) in iter { - f.write_str(", ")?; - write!(f, "{}: {}", name, value)?; - } - f.write_char('}') -} diff --git a/value/src/macros.rs b/value/src/macros.rs deleted file mode 100644 index 288d321bf..000000000 --- a/value/src/macros.rs +++ /dev/null @@ -1,254 +0,0 @@ -/// Construct a `ConstValue`. -#[macro_export] -macro_rules! value { - ($($json:tt)+) => { - $crate::value_internal!($($json)+) - }; -} - -#[macro_export] -#[doc(hidden)] -macro_rules! value_internal { - // Done with trailing comma. - (@array [$($elems:expr,)*]) => { - $crate::value_internal_vec![$($elems,)*] - }; - - // Done without trailing comma. - (@array [$($elems:expr),*]) => { - $crate::value_internal_vec![$($elems),*] - }; - - // Next element is `null`. - (@array [$($elems:expr,)*] null $($rest:tt)*) => { - $crate::value_internal!(@array [$($elems,)* $crate::value_internal!(null)] $($rest)*) - }; - - // Next element is `true`. - (@array [$($elems:expr,)*] true $($rest:tt)*) => { - $crate::value_internal!(@array [$($elems,)* $crate::value_internal!(true)] $($rest)*) - }; - - // Next element is `false`. - (@array [$($elems:expr,)*] false $($rest:tt)*) => { - $crate::value_internal!(@array [$($elems,)* $crate::value_internal!(false)] $($rest)*) - }; - - // Next element is an array. - (@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => { - $crate::value_internal!(@array [$($elems,)* $crate::value_internal!([$($array)*])] $($rest)*) - }; - - // Next element is a map. - (@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => { - $crate::value_internal!(@array [$($elems,)* $crate::value_internal!({$($map)*})] $($rest)*) - }; - - // Next element is an expression followed by comma. - (@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => { - $crate::value_internal!(@array [$($elems,)* $crate::value_internal!($next),] $($rest)*) - }; - - // Last element is an expression with no trailing comma. - (@array [$($elems:expr,)*] $last:expr) => { - $crate::value_internal!(@array [$($elems,)* $crate::value_internal!($last)]) - }; - - // Comma after the most recent element. - (@array [$($elems:expr),*] , $($rest:tt)*) => { - $crate::value_internal!(@array [$($elems,)*] $($rest)*) - }; - - // Unexpected token after most recent element. - (@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => { - $crate::value_unexpected!($unexpected) - }; - - // Done. - (@object $object:ident () () ()) => {}; - - // Insert the current entry followed by trailing comma. - (@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => { - let _ = $object.insert($crate::Name::new($($key)+), $value); - $crate::value_internal!(@object $object () ($($rest)*) ($($rest)*)); - }; - - // Current entry followed by unexpected token. - (@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => { - $crate::value_unexpected!($unexpected); - }; - - // Insert the last entry without trailing comma. - (@object $object:ident [$($key:tt)+] ($value:expr)) => { - let _ = $object.insert($crate::Name::new($($key)+), $value); - }; - - // Next value is `null`. - (@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => { - $crate::value_internal!(@object $object [$($key)+] ($crate::value_internal!(null)) $($rest)*); - }; - - // Next value is `true`. - (@object $object:ident ($($key:tt)+) (: true $($rest:tt)*) $copy:tt) => { - $crate::value_internal!(@object $object [$($key)+] ($crate::value_internal!(true)) $($rest)*); - }; - - // Next value is `false`. - (@object $object:ident ($($key:tt)+) (: false $($rest:tt)*) $copy:tt) => { - $crate::value_internal!(@object $object [$($key)+] ($crate::value_internal!(false)) $($rest)*); - }; - - // Next value is an array. - (@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => { - $crate::value_internal!(@object $object [$($key)+] ($crate::value_internal!([$($array)*])) $($rest)*); - }; - - // Next value is a map. - (@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => { - $crate::value_internal!(@object $object [$($key)+] ($crate::value_internal!({$($map)*})) $($rest)*); - }; - - // Next value is an expression followed by comma. - (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => { - $crate::value_internal!(@object $object [$($key)+] ($crate::value_internal!($value)) , $($rest)*); - }; - - // Last value is an expression with no trailing comma. - (@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => { - $crate::value_internal!(@object $object [$($key)+] ($crate::value_internal!($value))); - }; - - // Missing value for last entry. Trigger a reasonable error message. - (@object $object:ident ($($key:tt)+) (:) $copy:tt) => { - // "unexpected end of macro invocation" - $crate::value_internal!(); - }; - - // Missing colon and value for last entry. Trigger a reasonable error - // message. - (@object $object:ident ($($key:tt)+) () $copy:tt) => { - // "unexpected end of macro invocation" - $crate::value_internal!(); - }; - - // Misplaced colon. Trigger a reasonable error message. - (@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => { - // Takes no arguments so "no rules expected the token `:`". - $crate::value_unexpected!($colon); - }; - - // Found a comma inside a key. Trigger a reasonable error message. - (@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => { - // Takes no arguments so "no rules expected the token `,`". - $crate::value_unexpected!($comma); - }; - - // Key is fully parenthesized. This avoids clippy double_parens false - // positives because the parenthesization may be necessary here. - (@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => { - $crate::value_internal!(@object $object ($key) (: $($rest)*) (: $($rest)*)); - }; - - // Refuse to absorb colon token into key expression. - (@object $object:ident ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => { - $crate::value_expect_expr_comma!($($unexpected)+); - }; - - // Munch a token into the current key. - (@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => { - $crate::value_internal!(@object $object ($($key)* $tt) ($($rest)*) ($($rest)*)); - }; - - ////////////////////////////////////////////////////////////////////////// - // The main implementation. - // - // Must be invoked as: value_internal!($($json)+) - ////////////////////////////////////////////////////////////////////////// - - (null) => { - $crate::ConstValue::Null - }; - - (true) => { - $crate::ConstValue::Boolean(true) - }; - - (false) => { - $crate::ConstValue::Boolean(false) - }; - - ([]) => { - $crate::ConstValue::List($crate::value_internal_vec![]) - }; - - ([ $($tt:tt)+ ]) => { - $crate::ConstValue::List($crate::value_internal!(@array [] $($tt)+)) - }; - - ({}) => { - $crate::ConstValue::Object(Default::default()) - }; - - ({ $($tt:tt)+ }) => { - $crate::ConstValue::Object({ - let mut object = $crate::indexmap::IndexMap::new(); - $crate::value_internal!(@object object () ($($tt)+) ($($tt)+)); - object - }) - }; - - // Any Serialize type: numbers, strings, struct literals, variables etc. - // Must be below every other rule. - ($other:expr) => { - $crate::to_value(&$other).unwrap() - }; -} - -#[macro_export] -#[doc(hidden)] -macro_rules! value_internal_vec { - ($($content:tt)*) => { - vec![$($content)*] - }; -} - -#[macro_export] -#[doc(hidden)] -macro_rules! value_unexpected { - () => {}; -} - -#[macro_export] -#[doc(hidden)] -macro_rules! value_expect_expr_comma { - ($e:expr , $($tt:tt)*) => {}; -} - -#[cfg(test)] -mod tests { - use indexmap::IndexMap; - - use crate::{ConstValue, Name}; - - #[test] - fn test_macro() { - assert_eq!(value!(1), ConstValue::Number(1.into())); - assert_eq!(value!(1 + 2), ConstValue::Number(3.into())); - assert_eq!(value!("abc"), ConstValue::String("abc".into())); - assert_eq!(value!(true), ConstValue::Boolean(true)); - assert_eq!( - value!([1, 2, 3]), - ConstValue::List((1..=3).map(|n| ConstValue::Number(n.into())).collect()) - ); - assert_eq!( - value!([1, 2, 3,]), - ConstValue::List((1..=3).map(|n| ConstValue::Number(n.into())).collect()) - ); - assert_eq!(value!({"a": 10, "b": true}), { - let mut map = IndexMap::new(); - map.insert(Name::new("a"), ConstValue::Number(10.into())); - map.insert(Name::new("b"), ConstValue::Boolean(true)); - ConstValue::Object(map) - }); - } -} diff --git a/value/src/serializer.rs b/value/src/serializer.rs deleted file mode 100644 index 3c9208b16..000000000 --- a/value/src/serializer.rs +++ /dev/null @@ -1,632 +0,0 @@ -use std::{error::Error, fmt}; - -use indexmap::IndexMap; -use serde::{ - Serialize, - ser::{self, Impossible}, -}; - -use crate::{ConstValue, Name, Number}; - -/// This type represents errors that can occur when serializing. -#[derive(Debug)] -pub struct SerializerError(String); - -impl fmt::Display for SerializerError { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - match *self { - SerializerError(ref s) => fmt.write_str(s), - } - } -} - -impl Error for SerializerError { - fn description(&self) -> &str { - "ConstValue serializer error" - } -} - -impl ser::Error for SerializerError { - fn custom(msg: T) -> SerializerError { - SerializerError(msg.to_string()) - } -} - -/// Convert a `T` into `ConstValue` which is an enum that can represent any -/// valid GraphQL data. -#[inline] -pub fn to_value(value: T) -> Result { - value.serialize(Serializer) -} - -struct Serializer; - -impl ser::Serializer for Serializer { - type Ok = ConstValue; - type Error = SerializerError; - type SerializeSeq = SerializeSeq; - type SerializeTuple = SerializeTuple; - type SerializeTupleStruct = SerializeTupleStruct; - type SerializeTupleVariant = SerializeTupleVariant; - type SerializeMap = SerializeMap; - type SerializeStruct = SerializeStruct; - type SerializeStructVariant = SerializeStructVariant; - - #[inline] - fn serialize_bool(self, v: bool) -> Result { - Ok(ConstValue::Boolean(v)) - } - - #[inline] - fn serialize_i8(self, v: i8) -> Result { - Ok(ConstValue::Number(v.into())) - } - - #[inline] - fn serialize_i16(self, v: i16) -> Result { - Ok(ConstValue::Number(v.into())) - } - - #[inline] - fn serialize_i32(self, v: i32) -> Result { - Ok(ConstValue::Number(v.into())) - } - - #[inline] - fn serialize_i64(self, v: i64) -> Result { - Ok(ConstValue::Number(v.into())) - } - - #[inline] - fn serialize_u8(self, v: u8) -> Result { - Ok(ConstValue::Number(v.into())) - } - - #[inline] - fn serialize_u16(self, v: u16) -> Result { - Ok(ConstValue::Number(v.into())) - } - - #[inline] - fn serialize_u32(self, v: u32) -> Result { - Ok(ConstValue::Number(v.into())) - } - - #[inline] - fn serialize_u64(self, v: u64) -> Result { - Ok(ConstValue::Number(v.into())) - } - - #[inline] - fn serialize_f32(self, v: f32) -> Result { - self.serialize_f64(v as f64) - } - - #[inline] - fn serialize_f64(self, v: f64) -> Result { - match Number::from_f64(v) { - Some(v) => Ok(ConstValue::Number(v)), - None => Ok(ConstValue::Null), - } - } - - #[inline] - fn serialize_char(self, _v: char) -> Result { - Err(SerializerError("char is not supported.".to_string())) - } - - #[inline] - fn serialize_str(self, v: &str) -> Result { - Ok(ConstValue::String(v.to_string())) - } - - #[inline] - fn serialize_bytes(self, v: &[u8]) -> Result { - Ok(ConstValue::Binary(v.to_vec().into())) - } - - #[inline] - fn serialize_none(self) -> Result { - Ok(ConstValue::Null) - } - - #[inline] - fn serialize_some(self, value: &T) -> Result - where - T: ser::Serialize + ?Sized, - { - value.serialize(self) - } - - #[inline] - fn serialize_unit(self) -> Result { - Ok(ConstValue::Null) - } - - #[inline] - fn serialize_unit_struct(self, _name: &'static str) -> Result { - Ok(ConstValue::Null) - } - - #[inline] - fn serialize_unit_variant( - self, - _name: &'static str, - _variant_index: u32, - variant: &'static str, - ) -> Result { - Ok(ConstValue::String(variant.to_string())) - } - - #[inline] - fn serialize_newtype_struct( - self, - _name: &'static str, - value: &T, - ) -> Result - where - T: ser::Serialize + ?Sized, - { - value.serialize(self) - } - - #[inline] - fn serialize_newtype_variant( - self, - _name: &'static str, - _variant_index: u32, - variant: &'static str, - value: &T, - ) -> Result - where - T: ser::Serialize + ?Sized, - { - value.serialize(self).map(|v| { - let mut map = IndexMap::new(); - map.insert(Name::new(variant), v); - ConstValue::Object(map) - }) - } - - #[inline] - fn serialize_seq(self, _len: Option) -> Result { - Ok(SerializeSeq(vec![])) - } - - #[inline] - fn serialize_tuple(self, _len: usize) -> Result { - Ok(SerializeTuple(vec![])) - } - - #[inline] - fn serialize_tuple_struct( - self, - _name: &'static str, - _len: usize, - ) -> Result { - Ok(SerializeTupleStruct(vec![])) - } - - #[inline] - fn serialize_tuple_variant( - self, - _name: &'static str, - _variant_index: u32, - variant: &'static str, - len: usize, - ) -> Result { - Ok(SerializeTupleVariant( - Name::new(variant), - Vec::with_capacity(len), - )) - } - - #[inline] - fn serialize_map(self, _len: Option) -> Result { - Ok(SerializeMap { - map: IndexMap::new(), - key: None, - }) - } - - #[inline] - fn serialize_struct( - self, - _name: &'static str, - _len: usize, - ) -> Result { - Ok(SerializeStruct(IndexMap::new())) - } - - #[inline] - fn serialize_struct_variant( - self, - _name: &'static str, - _variant_index: u32, - variant: &'static str, - _len: usize, - ) -> Result { - Ok(SerializeStructVariant(Name::new(variant), IndexMap::new())) - } - - #[inline] - fn is_human_readable(&self) -> bool { - true - } -} - -struct SerializeSeq(Vec); - -impl ser::SerializeSeq for SerializeSeq { - type Ok = ConstValue; - type Error = SerializerError; - - #[inline] - fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> - where - T: ser::Serialize + ?Sized, - { - let value = value.serialize(Serializer)?; - self.0.push(value); - Ok(()) - } - - #[inline] - fn end(self) -> Result { - Ok(ConstValue::List(self.0)) - } -} - -struct SerializeTuple(Vec); - -impl ser::SerializeTuple for SerializeTuple { - type Ok = ConstValue; - type Error = SerializerError; - - #[inline] - fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> - where - T: ser::Serialize + ?Sized, - { - let value = value.serialize(Serializer)?; - self.0.push(value); - Ok(()) - } - - #[inline] - fn end(self) -> Result { - Ok(ConstValue::List(self.0)) - } -} - -struct SerializeTupleStruct(Vec); - -impl ser::SerializeTupleStruct for SerializeTupleStruct { - type Ok = ConstValue; - type Error = SerializerError; - - #[inline] - fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> - where - T: ser::Serialize + ?Sized, - { - let value = value.serialize(Serializer)?; - self.0.push(value); - Ok(()) - } - - #[inline] - fn end(self) -> Result { - Ok(ConstValue::List(self.0)) - } -} - -struct SerializeTupleVariant(Name, Vec); - -impl ser::SerializeTupleVariant for SerializeTupleVariant { - type Ok = ConstValue; - type Error = SerializerError; - - #[inline] - fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> - where - T: ser::Serialize + ?Sized, - { - let value = value.serialize(Serializer)?; - self.1.push(value); - Ok(()) - } - - #[inline] - fn end(self) -> Result { - let mut map = IndexMap::new(); - map.insert(self.0, ConstValue::List(self.1)); - Ok(ConstValue::Object(map)) - } -} - -struct SerializeMap { - map: IndexMap, - key: Option, -} - -impl ser::SerializeMap for SerializeMap { - type Ok = ConstValue; - type Error = SerializerError; - - #[inline] - fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> - where - T: ser::Serialize + ?Sized, - { - let key = key.serialize(MapKeySerializer)?; - self.key = Some(key); - Ok(()) - } - - #[inline] - fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> - where - T: ser::Serialize + ?Sized, - { - let value = value.serialize(Serializer)?; - self.map.insert(self.key.take().unwrap(), value); - Ok(()) - } - - #[inline] - fn end(self) -> Result { - Ok(ConstValue::Object(self.map)) - } -} - -struct SerializeStruct(IndexMap); - -impl ser::SerializeStruct for SerializeStruct { - type Ok = ConstValue; - type Error = SerializerError; - - #[inline] - fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> - where - T: ser::Serialize + ?Sized, - { - let key = Name::new(key); - let value = value.serialize(Serializer)?; - self.0.insert(key, value); - Ok(()) - } - - #[inline] - fn end(self) -> Result { - Ok(ConstValue::Object(self.0)) - } -} - -struct SerializeStructVariant(Name, IndexMap); - -impl ser::SerializeStructVariant for SerializeStructVariant { - type Ok = ConstValue; - type Error = SerializerError; - - #[inline] - fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> - where - T: ser::Serialize + ?Sized, - { - let key = Name::new(key); - let value = value.serialize(Serializer)?; - self.1.insert(key, value); - Ok(()) - } - - #[inline] - fn end(self) -> Result { - let mut map = IndexMap::new(); - map.insert(self.0, ConstValue::Object(self.1)); - Ok(ConstValue::Object(map)) - } -} - -#[inline] -fn key_must_be_a_string() -> SerializerError { - SerializerError("Key must be a string".to_string()) -} - -struct MapKeySerializer; - -impl serde::Serializer for MapKeySerializer { - type Ok = Name; - type Error = SerializerError; - type SerializeSeq = Impossible; - type SerializeTuple = Impossible; - type SerializeTupleStruct = Impossible; - type SerializeTupleVariant = Impossible; - type SerializeMap = Impossible; - type SerializeStruct = Impossible; - type SerializeStructVariant = Impossible; - - #[inline] - fn serialize_bool(self, _v: bool) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_i8(self, _v: i8) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_i16(self, _v: i16) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_i32(self, _v: i32) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_i64(self, _v: i64) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_u8(self, _v: u8) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_u16(self, _v: u16) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_u32(self, _v: u32) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_u64(self, _v: u64) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_f32(self, _v: f32) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_f64(self, _v: f64) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_char(self, _v: char) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_str(self, v: &str) -> Result { - Ok(Name::new(v)) - } - - #[inline] - fn serialize_bytes(self, _v: &[u8]) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_none(self) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_some(self, _value: &T) -> Result - where - T: Serialize + ?Sized, - { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_unit(self) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_unit_struct(self, _name: &'static str) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_unit_variant( - self, - _name: &'static str, - _variant_index: u32, - variant: &'static str, - ) -> Result { - Ok(Name::new(variant)) - } - - #[inline] - fn serialize_newtype_struct( - self, - _name: &'static str, - _value: &T, - ) -> Result - where - T: Serialize + ?Sized, - { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_newtype_variant( - self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _value: &T, - ) -> Result - where - T: Serialize + ?Sized, - { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_seq(self, _len: Option) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_tuple(self, _len: usize) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_tuple_struct( - self, - _name: &'static str, - _len: usize, - ) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_tuple_variant( - self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _len: usize, - ) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_map(self, _len: Option) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_struct( - self, - _name: &'static str, - _len: usize, - ) -> Result { - Err(key_must_be_a_string()) - } - - #[inline] - fn serialize_struct_variant( - self, - _name: &'static str, - _variant_index: u32, - _variant: &'static str, - _len: usize, - ) -> Result { - Err(key_must_be_a_string()) - } -} diff --git a/value/src/value_serde.rs b/value/src/value_serde.rs deleted file mode 100644 index a66f589df..000000000 --- a/value/src/value_serde.rs +++ /dev/null @@ -1,343 +0,0 @@ -use std::fmt::{self, Formatter}; - -use indexmap::IndexMap; -use serde::{ - Deserialize, Deserializer, Serialize, Serializer, - de::{Error as DeError, MapAccess, SeqAccess, Visitor}, - ser::SerializeMap, -}; - -use crate::{ConstValue, Name, Number, Value}; - -/// The token used by `serde_json` to represent raw values. -/// -/// It should be kept in sync with the following original until made public: -/// https://github.com/serde-rs/json/blob/b48b9a3a0c09952579e98c8940fe0d1ee4aae588/src/raw.rs#L292 -#[cfg(feature = "raw_value")] -pub const RAW_VALUE_TOKEN: &str = "$serde_json::private::RawValue"; - -impl Serialize for ConstValue { - fn serialize(&self, serializer: S) -> Result { - match self { - ConstValue::Null => serializer.serialize_none(), - ConstValue::Number(v) => v.serialize(serializer), - ConstValue::String(v) => serializer.serialize_str(v), - ConstValue::Boolean(v) => serializer.serialize_bool(*v), - ConstValue::Binary(v) => serializer.serialize_bytes(v), - ConstValue::Enum(v) => serializer.serialize_str(v), - ConstValue::List(v) => v.serialize(serializer), - ConstValue::Object(v) => { - #[cfg(feature = "raw_value")] - if v.len() == 1 { - if let Some(ConstValue::String(v)) = v.get(RAW_VALUE_TOKEN) { - if let Ok(v) = serde_json::value::RawValue::from_string(v.clone()) { - return v.serialize(serializer); - } - } - } - v.serialize(serializer) - } - } - } -} - -impl<'de> Deserialize<'de> for ConstValue { - fn deserialize>(deserializer: D) -> Result { - struct ValueVisitor; - - impl<'de> Visitor<'de> for ValueVisitor { - type Value = ConstValue; - - #[inline] - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("any valid value") - } - - #[inline] - fn visit_bool(self, v: bool) -> Result - where - E: DeError, - { - Ok(ConstValue::Boolean(v)) - } - - #[inline] - fn visit_i64(self, v: i64) -> Result - where - E: DeError, - { - Ok(ConstValue::Number(v.into())) - } - - #[inline] - fn visit_u64(self, v: u64) -> Result - where - E: DeError, - { - Ok(ConstValue::Number(v.into())) - } - - #[inline] - fn visit_f64(self, v: f64) -> Result - where - E: DeError, - { - Ok(Number::from_f64(v).map_or(ConstValue::Null, ConstValue::Number)) - } - - #[inline] - fn visit_str(self, v: &str) -> Result - where - E: DeError, - { - Ok(ConstValue::String(v.to_string())) - } - - #[inline] - fn visit_string(self, v: String) -> Result - where - E: DeError, - { - Ok(ConstValue::String(v)) - } - - #[inline] - fn visit_bytes(self, v: &[u8]) -> Result - where - E: DeError, - { - Ok(ConstValue::Binary(v.to_vec().into())) - } - - #[inline] - fn visit_byte_buf(self, v: Vec) -> Result - where - E: DeError, - { - Ok(ConstValue::Binary(v.into())) - } - - #[inline] - fn visit_none(self) -> Result - where - E: DeError, - { - Ok(ConstValue::Null) - } - - #[inline] - fn visit_some(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Deserialize::deserialize(deserializer) - } - - #[inline] - fn visit_unit(self) -> Result - where - E: DeError, - { - Ok(ConstValue::Null) - } - - fn visit_seq(self, mut visitor: A) -> Result - where - A: SeqAccess<'de>, - { - let mut vec = Vec::new(); - while let Some(elem) = visitor.next_element()? { - vec.push(elem); - } - Ok(ConstValue::List(vec)) - } - - fn visit_map(self, mut visitor: A) -> Result - where - A: MapAccess<'de>, - { - let mut map = IndexMap::new(); - while let Some((name, value)) = visitor.next_entry()? { - map.insert(name, value); - } - Ok(ConstValue::Object(map)) - } - } - - deserializer.deserialize_any(ValueVisitor) - } -} - -#[derive(Debug)] -struct SerdeVariable(Name); - -impl Serialize for SerdeVariable { - fn serialize(&self, serializer: S) -> Result { - let mut s = serializer.serialize_map(Some(1))?; - s.serialize_entry("$var", &self.0)?; - s.end() - } -} - -impl Serialize for Value { - fn serialize(&self, serializer: S) -> Result { - match self { - Value::Variable(name) => SerdeVariable(name.clone()).serialize(serializer), - Value::Null => serializer.serialize_none(), - Value::Number(v) => v.serialize(serializer), - Value::String(v) => serializer.serialize_str(v), - Value::Boolean(v) => serializer.serialize_bool(*v), - Value::Binary(v) => serializer.serialize_bytes(v), - Value::Enum(v) => serializer.serialize_str(v), - Value::List(v) => v.serialize(serializer), - Value::Object(v) => v.serialize(serializer), - } - } -} - -impl<'de> Deserialize<'de> for Value { - fn deserialize>(deserializer: D) -> Result { - struct ValueVisitor; - - impl<'de> Visitor<'de> for ValueVisitor { - type Value = Value; - - #[inline] - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("any valid value") - } - - #[inline] - fn visit_bool(self, v: bool) -> Result - where - E: DeError, - { - Ok(Value::Boolean(v)) - } - - #[inline] - fn visit_i64(self, v: i64) -> Result - where - E: DeError, - { - Ok(Value::Number(v.into())) - } - - #[inline] - fn visit_u64(self, v: u64) -> Result - where - E: DeError, - { - Ok(Value::Number(v.into())) - } - - #[inline] - fn visit_f64(self, v: f64) -> Result - where - E: DeError, - { - Ok(Number::from_f64(v).map_or(Value::Null, Value::Number)) - } - - #[inline] - fn visit_str(self, v: &str) -> Result - where - E: DeError, - { - Ok(Value::String(v.to_string())) - } - - #[inline] - fn visit_string(self, v: String) -> Result - where - E: DeError, - { - Ok(Value::String(v)) - } - - #[inline] - fn visit_bytes(self, v: &[u8]) -> Result - where - E: DeError, - { - Ok(Value::Binary(v.to_vec().into())) - } - - #[inline] - fn visit_byte_buf(self, v: Vec) -> Result - where - E: DeError, - { - Ok(Value::Binary(v.into())) - } - - #[inline] - fn visit_none(self) -> Result - where - E: DeError, - { - Ok(Value::Null) - } - - #[inline] - fn visit_some(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Deserialize::deserialize(deserializer) - } - - #[inline] - fn visit_unit(self) -> Result - where - E: DeError, - { - Ok(Value::Null) - } - - fn visit_seq(self, mut visitor: A) -> Result - where - A: SeqAccess<'de>, - { - let mut vec = Vec::new(); - while let Some(elem) = visitor.next_element()? { - vec.push(elem); - } - Ok(Value::List(vec)) - } - - fn visit_map(self, mut visitor: A) -> Result - where - A: MapAccess<'de>, - { - let mut map = IndexMap::new(); - while let Some((name, value)) = visitor.next_entry()? { - match &value { - Value::String(value) if name == "$var" => { - return Ok(Value::Variable(Name::new(value))); - } - _ => { - map.insert(name, value); - } - } - } - Ok(Value::Object(map)) - } - } - - deserializer.deserialize_any(ValueVisitor) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn var_serde() { - let var = Value::Variable(Name::new("abc")); - let s = serde_json::to_string(&var).unwrap(); - assert_eq!(s, r#"{"$var":"abc"}"#); - assert_eq!(var, serde_json::from_str(&s).unwrap()); - } -} diff --git a/value/src/variables.rs b/value/src/variables.rs deleted file mode 100644 index bfb84698b..000000000 --- a/value/src/variables.rs +++ /dev/null @@ -1,82 +0,0 @@ -use std::{ - collections::BTreeMap, - fmt::{self, Display, Formatter}, - ops::{Deref, DerefMut}, -}; - -use serde::{Deserialize, Deserializer, Serialize}; - -use crate::{ConstValue, Name}; - -/// Variables of a query. -#[derive(Debug, Clone, Default, Serialize, Eq, PartialEq)] -#[serde(transparent)] -pub struct Variables(BTreeMap); - -impl Display for Variables { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str("{")?; - for (i, (name, value)) in self.0.iter().enumerate() { - write!(f, "{}{}: {}", if i == 0 { "" } else { ", " }, name, value)?; - } - f.write_str("}") - } -} - -impl<'de> Deserialize<'de> for Variables { - fn deserialize>(deserializer: D) -> Result { - Ok(Self( - >>::deserialize(deserializer)?.unwrap_or_default(), - )) - } -} - -impl Variables { - /// Get the variables from a GraphQL value. - /// - /// If the value is not a map, then no variables will be returned. - #[must_use] - pub fn from_value(value: ConstValue) -> Self { - match value { - ConstValue::Object(obj) => Self(obj.into_iter().collect()), - _ => Self::default(), - } - } - - /// Get the values from a JSON value. - /// - /// If the value is not a map or the keys of a map are not valid GraphQL - /// names, then no variables will be returned. - #[must_use] - pub fn from_json(value: serde_json::Value) -> Self { - ConstValue::from_json(value) - .map(Self::from_value) - .unwrap_or_default() - } - - /// Get the variables as a GraphQL value. - #[must_use] - pub fn into_value(self) -> ConstValue { - ConstValue::Object(self.0.into_iter().collect()) - } -} - -impl From for ConstValue { - fn from(variables: Variables) -> Self { - variables.into_value() - } -} - -impl Deref for Variables { - type Target = BTreeMap; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Variables { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} diff --git a/value/tests/test_serde.rs b/value/tests/test_serde.rs deleted file mode 100644 index c5c9e81f0..000000000 --- a/value/tests/test_serde.rs +++ /dev/null @@ -1,107 +0,0 @@ -use std::{collections::BTreeMap, fmt::Debug}; - -use async_graphql_value::*; -use bytes::Bytes; -use serde::{Deserialize, Serialize, de::DeserializeOwned}; - -fn test_value(value: T) { - assert_eq!( - from_value::(to_value(value.clone()).unwrap()).unwrap(), - value - ) -} - -#[test] -fn test_serde() { - test_value(true); - test_value(100i32); - test_value(1.123f64); - test_value(Some(100i32)); - test_value(ConstValue::Null); - test_value(vec![0i32, 1, 2, 3, 4, 5, 6, 7, 8, 9]); - test_value(b"123456".to_vec()); - - #[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)] - struct NewType(i32); - test_value(NewType(100i32)); - - #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash, Copy, Clone, Ord, PartialOrd)] - enum Enum { - A, - B, - } - test_value(Enum::A); - test_value(Enum::B); - - let mut obj = BTreeMap::::new(); - obj.insert(Name::new("A"), ConstValue::Number(10.into())); - obj.insert(Name::new("B"), ConstValue::Number(20.into())); - test_value(obj); - - let mut obj = BTreeMap::::new(); - obj.insert(Enum::A, ConstValue::Number(10.into())); - obj.insert(Enum::B, ConstValue::Number(20.into())); - test_value(obj); - - #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] - struct Struct { - a: i32, - b: Option, - } - test_value(Struct { - a: 100, - b: Some(Enum::B), - }); -} - -#[test] -fn test_binary() { - assert_eq!( - to_value(Bytes::from_static(b"123456")).unwrap(), - ConstValue::Binary(Bytes::from_static(b"123456")) - ); - - assert_eq!( - from_value::(ConstValue::Binary(Bytes::from_static(b"123456"))).unwrap(), - Bytes::from_static(b"123456") - ); -} - -#[cfg(feature = "raw_value")] -#[test] -fn test_raw_value() { - use indexmap::IndexMap; - use serde_json::value::RawValue; - - #[derive(Serialize)] - struct Struct { - field: Box, - } - - let object = Struct { - field: RawValue::from_string("[0, 1, 2]".into()).unwrap(), - }; - - let value = to_value(&object).unwrap(); - assert_eq!( - value, - ConstValue::Object({ - let mut map = IndexMap::default(); - map.insert( - Name::new("field"), - ConstValue::Object({ - let mut map = IndexMap::default(); - map.insert( - Name::new(RAW_VALUE_TOKEN), - ConstValue::String("[0, 1, 2]".into()), - ); - map - }), - ); - map - }) - ); - - let value = serde_json::to_string(&value).unwrap(); - assert_eq!(value, r#"{"field":[0, 1, 2]}"#); -} diff --git a/zh-CN/.nojekyll b/zh-CN/.nojekyll new file mode 100644 index 000000000..f17311098 --- /dev/null +++ b/zh-CN/.nojekyll @@ -0,0 +1 @@ +This file makes sure that Github Pages doesn't process mdBook's output. diff --git a/zh-CN/404.html b/zh-CN/404.html new file mode 100644 index 000000000..aa70cd773 --- /dev/null +++ b/zh-CN/404.html @@ -0,0 +1,211 @@ + + + + + + Page not found - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Document not found (404)

+

This URL is invalid, sorry. Please use the navigation bar or search to continue.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/FontAwesome/css/font-awesome.css b/zh-CN/FontAwesome/css/font-awesome.css new file mode 100644 index 000000000..540440ce8 --- /dev/null +++ b/zh-CN/FontAwesome/css/font-awesome.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/zh-CN/FontAwesome/fonts/FontAwesome.ttf b/zh-CN/FontAwesome/fonts/FontAwesome.ttf new file mode 100644 index 000000000..35acda2fa Binary files /dev/null and b/zh-CN/FontAwesome/fonts/FontAwesome.ttf differ diff --git a/zh-CN/FontAwesome/fonts/fontawesome-webfont.eot b/zh-CN/FontAwesome/fonts/fontawesome-webfont.eot new file mode 100644 index 000000000..e9f60ca95 Binary files /dev/null and b/zh-CN/FontAwesome/fonts/fontawesome-webfont.eot differ diff --git a/zh-CN/FontAwesome/fonts/fontawesome-webfont.svg b/zh-CN/FontAwesome/fonts/fontawesome-webfont.svg new file mode 100644 index 000000000..855c845e5 --- /dev/null +++ b/zh-CN/FontAwesome/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/zh-CN/FontAwesome/fonts/fontawesome-webfont.ttf b/zh-CN/FontAwesome/fonts/fontawesome-webfont.ttf new file mode 100644 index 000000000..35acda2fa Binary files /dev/null and b/zh-CN/FontAwesome/fonts/fontawesome-webfont.ttf differ diff --git a/zh-CN/FontAwesome/fonts/fontawesome-webfont.woff b/zh-CN/FontAwesome/fonts/fontawesome-webfont.woff new file mode 100644 index 000000000..400014a4b Binary files /dev/null and b/zh-CN/FontAwesome/fonts/fontawesome-webfont.woff differ diff --git a/zh-CN/FontAwesome/fonts/fontawesome-webfont.woff2 b/zh-CN/FontAwesome/fonts/fontawesome-webfont.woff2 new file mode 100644 index 000000000..4d13fc604 Binary files /dev/null and b/zh-CN/FontAwesome/fonts/fontawesome-webfont.woff2 differ diff --git a/zh-CN/advanced_topics.html b/zh-CN/advanced_topics.html new file mode 100644 index 000000000..fba3f9606 --- /dev/null +++ b/zh-CN/advanced_topics.html @@ -0,0 +1,221 @@ + + + + + + 高级主题 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

高级主题

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/apollo_federation.html b/zh-CN/apollo_federation.html new file mode 100644 index 000000000..77b774139 --- /dev/null +++ b/zh-CN/apollo_federation.html @@ -0,0 +1,299 @@ + + + + + + Apollo Federation 集成 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Apollo Federation 集成

+

Apollo Federation是一个GraphQL网关,它可以组合多个 GraphQL 服务,允许每服务仅实现它负责的那一部分数据,参考官方文档

+

Async-graphql可以完全支持Apollo Federation的所有功能,但需要对Schema定义做一些小小的改造。

+
    +
  • +

    async_graphql::Objectasync_graphql::Interfaceextends属性声明这个类别是一个已有类型的扩充。

    +
  • +
  • +

    字段的external属性声明这个字段定义来自其它服务。

    +
  • +
  • +

    字段的provides属性用于要求网关提供的字段集。

    +
  • +
  • +

    字段的requires属性表示解析该字段值需要依赖该类型的字段集。

    +
  • +
+

实体查找函数

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct User { id: ID }
+struct Query;
+
+#[Object]
+impl Query {
+    #[graphql(entity)]
+    async fn find_user_by_id(&self, id: ID) -> User {
+        User { id }
+    }
+
+    #[graphql(entity)]
+    async fn find_user_by_id_with_username(&self, #[graphql(key)] id: ID, username: String) -> User {
+        User { id }
+    }
+
+    #[graphql(entity)]
+    async fn find_user_by_id_and_username(&self, id: ID, username: String) -> User {
+        User { id }
+    }
+}
+}
+

注意这三个查找函数的不同,他们都是查找 User 对象。

+
    +
  • +

    find_user_by_id

    +

    使用id查找User对象,User对象的 key 是id

    +
  • +
  • +

    find_user_by_id_with_username

    +

    使用id查找User对象,User对象的 key 是id,并且请求User对象的username字段值。

    +
  • +
  • +

    find_user_by_id_and_username

    +

    使用idusername查找User对象,User对象的 key 是idusername

    +
  • +
+

完整的例子请参考 https://github.com/async-graphql/examples/tree/master/federation

+

定义复合主键

+

一个主键可以包含多个字段,什么包含嵌套字段,你可以用InputObject来实现一个嵌套字段的 Key 类型。

+

下面的例子中User对象的主键是key { a b }

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct User { id: i32 }
+#[derive(InputObject)]
+struct NestedKey {
+  a: i32,
+  b: i32,
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+  #[graphql(entity)]
+  async fn find_user_by_key(&self, key: NestedKey) -> User {
+    User { id: key.a }
+  }
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/apollo_tracing.html b/zh-CN/apollo_tracing.html new file mode 100644 index 000000000..19104eb1b --- /dev/null +++ b/zh-CN/apollo_tracing.html @@ -0,0 +1,238 @@ + + + + + + Apollo Tracing 支持 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Apollo Tracing 支持

+

Apollo Tracing提供了查询每个步骤的性能分析结果,它是一个Schema扩展,性能分析结果保存在QueryResponse中。

+

启用Apollo Tracing扩展需要在创建Schema的时候添加该扩展。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::ApolloTracing;
+
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+
+let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
+    .extension(ApolloTracing) // 启用 ApolloTracing 扩展
+    .finish();
+
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/ayu-highlight.css b/zh-CN/ayu-highlight.css new file mode 100644 index 000000000..32c943222 --- /dev/null +++ b/zh-CN/ayu-highlight.css @@ -0,0 +1,78 @@ +/* +Based off of the Ayu theme +Original by Dempfi (https://github.com/dempfi/ayu) +*/ + +.hljs { + display: block; + overflow-x: auto; + background: #191f26; + color: #e6e1cf; +} + +.hljs-comment, +.hljs-quote { + color: #5c6773; + font-style: italic; +} + +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-attr, +.hljs-regexp, +.hljs-link, +.hljs-selector-id, +.hljs-selector-class { + color: #ff7733; +} + +.hljs-number, +.hljs-meta, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #ffee99; +} + +.hljs-string, +.hljs-bullet { + color: #b8cc52; +} + +.hljs-title, +.hljs-built_in, +.hljs-section { + color: #ffb454; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-symbol { + color: #ff7733; +} + +.hljs-name { + color: #36a3d9; +} + +.hljs-tag { + color: #00568d; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-addition { + color: #91b362; +} + +.hljs-deletion { + color: #d96c75; +} diff --git a/zh-CN/book.js b/zh-CN/book.js new file mode 100644 index 000000000..5df2096f7 --- /dev/null +++ b/zh-CN/book.js @@ -0,0 +1,818 @@ +'use strict'; + +/* global default_theme, default_dark_theme, default_light_theme, hljs, ClipboardJS */ + +// Fix back button cache problem +window.onunload = function() { }; + +// Global variable, shared between modules +function playground_text(playground, hidden = true) { + const code_block = playground.querySelector('code'); + + if (window.ace && code_block.classList.contains('editable')) { + const editor = window.ace.edit(code_block); + return editor.getValue(); + } else if (hidden) { + return code_block.textContent; + } else { + return code_block.innerText; + } +} + +(function codeSnippets() { + function fetch_with_timeout(url, options, timeout = 6000) { + return Promise.race([ + fetch(url, options), + new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout)), + ]); + } + + const playgrounds = Array.from(document.querySelectorAll('.playground')); + if (playgrounds.length > 0) { + fetch_with_timeout('https://play.rust-lang.org/meta/crates', { + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + mode: 'cors', + }) + .then(response => response.json()) + .then(response => { + // get list of crates available in the rust playground + const playground_crates = response.crates.map(item => item['id']); + playgrounds.forEach(block => handle_crate_list_update(block, playground_crates)); + }); + } + + function handle_crate_list_update(playground_block, playground_crates) { + // update the play buttons after receiving the response + update_play_button(playground_block, playground_crates); + + // and install on change listener to dynamically update ACE editors + if (window.ace) { + const code_block = playground_block.querySelector('code'); + if (code_block.classList.contains('editable')) { + const editor = window.ace.edit(code_block); + editor.addEventListener('change', () => { + update_play_button(playground_block, playground_crates); + }); + // add Ctrl-Enter command to execute rust code + editor.commands.addCommand({ + name: 'run', + bindKey: { + win: 'Ctrl-Enter', + mac: 'Ctrl-Enter', + }, + exec: _editor => run_rust_code(playground_block), + }); + } + } + } + + // updates the visibility of play button based on `no_run` class and + // used crates vs ones available on https://play.rust-lang.org + function update_play_button(pre_block, playground_crates) { + const play_button = pre_block.querySelector('.play-button'); + + // skip if code is `no_run` + if (pre_block.querySelector('code').classList.contains('no_run')) { + play_button.classList.add('hidden'); + return; + } + + // get list of `extern crate`'s from snippet + const txt = playground_text(pre_block); + const re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g; + const snippet_crates = []; + let item; + // eslint-disable-next-line no-cond-assign + while (item = re.exec(txt)) { + snippet_crates.push(item[1]); + } + + // check if all used crates are available on play.rust-lang.org + const all_available = snippet_crates.every(function(elem) { + return playground_crates.indexOf(elem) > -1; + }); + + if (all_available) { + play_button.classList.remove('hidden'); + } else { + play_button.classList.add('hidden'); + } + } + + function run_rust_code(code_block) { + let result_block = code_block.querySelector('.result'); + if (!result_block) { + result_block = document.createElement('code'); + result_block.className = 'result hljs language-bash'; + + code_block.append(result_block); + } + + const text = playground_text(code_block); + const classes = code_block.querySelector('code').classList; + let edition = '2015'; + classes.forEach(className => { + if (className.startsWith('edition')) { + edition = className.slice(7); + } + }); + const params = { + version: 'stable', + optimize: '0', + code: text, + edition: edition, + }; + + if (text.indexOf('#![feature') !== -1) { + params.version = 'nightly'; + } + + result_block.innerText = 'Running...'; + + fetch_with_timeout('https://play.rust-lang.org/evaluate.json', { + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + mode: 'cors', + body: JSON.stringify(params), + }) + .then(response => response.json()) + .then(response => { + if (response.result.trim() === '') { + result_block.innerText = 'No output'; + result_block.classList.add('result-no-output'); + } else { + result_block.innerText = response.result; + result_block.classList.remove('result-no-output'); + } + }) + .catch(error => result_block.innerText = 'Playground Communication: ' + error.message); + } + + // Syntax highlighting Configuration + hljs.configure({ + tabReplace: ' ', // 4 spaces + languages: [], // Languages used for auto-detection + }); + + const code_nodes = Array + .from(document.querySelectorAll('code')) + // Don't highlight `inline code` blocks in headers. + .filter(function(node) { + return !node.parentElement.classList.contains('header'); + }); + + if (window.ace) { + // language-rust class needs to be removed for editable + // blocks or highlightjs will capture events + code_nodes + .filter(function(node) { + return node.classList.contains('editable'); + }) + .forEach(function(block) { + block.classList.remove('language-rust'); + }); + + code_nodes + .filter(function(node) { + return !node.classList.contains('editable'); + }) + .forEach(function(block) { + hljs.highlightBlock(block); + }); + } else { + code_nodes.forEach(function(block) { + hljs.highlightBlock(block); + }); + } + + // Adding the hljs class gives code blocks the color css + // even if highlighting doesn't apply + code_nodes.forEach(function(block) { + block.classList.add('hljs'); + }); + + Array.from(document.querySelectorAll('code.hljs')).forEach(function(block) { + + const lines = Array.from(block.querySelectorAll('.boring')); + // If no lines were hidden, return + if (!lines.length) { + return; + } + block.classList.add('hide-boring'); + + const buttons = document.createElement('div'); + buttons.className = 'buttons'; + buttons.innerHTML = ''; + + // add expand button + const pre_block = block.parentNode; + pre_block.insertBefore(buttons, pre_block.firstChild); + + pre_block.querySelector('.buttons').addEventListener('click', function(e) { + if (e.target.classList.contains('fa-eye')) { + e.target.classList.remove('fa-eye'); + e.target.classList.add('fa-eye-slash'); + e.target.title = 'Hide lines'; + e.target.setAttribute('aria-label', e.target.title); + + block.classList.remove('hide-boring'); + } else if (e.target.classList.contains('fa-eye-slash')) { + e.target.classList.remove('fa-eye-slash'); + e.target.classList.add('fa-eye'); + e.target.title = 'Show hidden lines'; + e.target.setAttribute('aria-label', e.target.title); + + block.classList.add('hide-boring'); + } + }); + }); + + if (window.playground_copyable) { + Array.from(document.querySelectorAll('pre code')).forEach(function(block) { + const pre_block = block.parentNode; + if (!pre_block.classList.contains('playground')) { + let buttons = pre_block.querySelector('.buttons'); + if (!buttons) { + buttons = document.createElement('div'); + buttons.className = 'buttons'; + pre_block.insertBefore(buttons, pre_block.firstChild); + } + + const clipButton = document.createElement('button'); + clipButton.className = 'clip-button'; + clipButton.title = 'Copy to clipboard'; + clipButton.setAttribute('aria-label', clipButton.title); + clipButton.innerHTML = ''; + + buttons.insertBefore(clipButton, buttons.firstChild); + } + }); + } + + // Process playground code blocks + Array.from(document.querySelectorAll('.playground')).forEach(function(pre_block) { + // Add play button + let buttons = pre_block.querySelector('.buttons'); + if (!buttons) { + buttons = document.createElement('div'); + buttons.className = 'buttons'; + pre_block.insertBefore(buttons, pre_block.firstChild); + } + + const runCodeButton = document.createElement('button'); + runCodeButton.className = 'fa fa-play play-button'; + runCodeButton.hidden = true; + runCodeButton.title = 'Run this code'; + runCodeButton.setAttribute('aria-label', runCodeButton.title); + + buttons.insertBefore(runCodeButton, buttons.firstChild); + runCodeButton.addEventListener('click', () => { + run_rust_code(pre_block); + }); + + if (window.playground_copyable) { + const copyCodeClipboardButton = document.createElement('button'); + copyCodeClipboardButton.className = 'clip-button'; + copyCodeClipboardButton.innerHTML = ''; + copyCodeClipboardButton.title = 'Copy to clipboard'; + copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title); + + buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild); + } + + const code_block = pre_block.querySelector('code'); + if (window.ace && code_block.classList.contains('editable')) { + const undoChangesButton = document.createElement('button'); + undoChangesButton.className = 'fa fa-history reset-button'; + undoChangesButton.title = 'Undo changes'; + undoChangesButton.setAttribute('aria-label', undoChangesButton.title); + + buttons.insertBefore(undoChangesButton, buttons.firstChild); + + undoChangesButton.addEventListener('click', function() { + const editor = window.ace.edit(code_block); + editor.setValue(editor.originalCode); + editor.clearSelection(); + }); + } + }); +})(); + +(function themes() { + const html = document.querySelector('html'); + const themeToggleButton = document.getElementById('theme-toggle'); + const themePopup = document.getElementById('theme-list'); + const themeColorMetaTag = document.querySelector('meta[name="theme-color"]'); + const themeIds = []; + themePopup.querySelectorAll('button.theme').forEach(function(el) { + themeIds.push(el.id); + }); + const stylesheets = { + ayuHighlight: document.querySelector('#ayu-highlight-css'), + tomorrowNight: document.querySelector('#tomorrow-night-css'), + highlight: document.querySelector('#highlight-css'), + }; + + function showThemes() { + themePopup.style.display = 'block'; + themeToggleButton.setAttribute('aria-expanded', true); + themePopup.querySelector('button#' + get_theme()).focus(); + } + + function updateThemeSelected() { + themePopup.querySelectorAll('.theme-selected').forEach(function(el) { + el.classList.remove('theme-selected'); + }); + const selected = get_saved_theme() ?? 'default_theme'; + let element = themePopup.querySelector('button#' + selected); + if (element === null) { + // Fall back in case there is no "Default" item. + element = themePopup.querySelector('button#' + get_theme()); + } + element.classList.add('theme-selected'); + } + + function hideThemes() { + themePopup.style.display = 'none'; + themeToggleButton.setAttribute('aria-expanded', false); + themeToggleButton.focus(); + } + + function get_saved_theme() { + let theme = null; + try { + theme = localStorage.getItem('mdbook-theme'); + } catch (e) { + // ignore error. + } + return theme; + } + + function delete_saved_theme() { + localStorage.removeItem('mdbook-theme'); + } + + function get_theme() { + const theme = get_saved_theme(); + if (theme === null || theme === undefined || !themeIds.includes(theme)) { + if (typeof default_dark_theme === 'undefined') { + // A customized index.hbs might not define this, so fall back to + // old behavior of determining the default on page load. + return default_theme; + } + return window.matchMedia('(prefers-color-scheme: dark)').matches + ? default_dark_theme + : default_light_theme; + } else { + return theme; + } + } + + let previousTheme = default_theme; + function set_theme(theme, store = true) { + let ace_theme; + + if (theme === 'coal' || theme === 'navy') { + stylesheets.ayuHighlight.disabled = true; + stylesheets.tomorrowNight.disabled = false; + stylesheets.highlight.disabled = true; + + ace_theme = 'ace/theme/tomorrow_night'; + } else if (theme === 'ayu') { + stylesheets.ayuHighlight.disabled = false; + stylesheets.tomorrowNight.disabled = true; + stylesheets.highlight.disabled = true; + ace_theme = 'ace/theme/tomorrow_night'; + } else { + stylesheets.ayuHighlight.disabled = true; + stylesheets.tomorrowNight.disabled = true; + stylesheets.highlight.disabled = false; + ace_theme = 'ace/theme/dawn'; + } + + setTimeout(function() { + themeColorMetaTag.content = getComputedStyle(document.documentElement).backgroundColor; + }, 1); + + if (window.ace && window.editors) { + window.editors.forEach(function(editor) { + editor.setTheme(ace_theme); + }); + } + + if (store) { + try { + localStorage.setItem('mdbook-theme', theme); + } catch (e) { + // ignore error. + } + } + + html.classList.remove(previousTheme); + html.classList.add(theme); + previousTheme = theme; + updateThemeSelected(); + } + + const query = window.matchMedia('(prefers-color-scheme: dark)'); + query.onchange = function() { + set_theme(get_theme(), false); + }; + + // Set theme. + set_theme(get_theme(), false); + + themeToggleButton.addEventListener('click', function() { + if (themePopup.style.display === 'block') { + hideThemes(); + } else { + showThemes(); + } + }); + + themePopup.addEventListener('click', function(e) { + let theme; + if (e.target.className === 'theme') { + theme = e.target.id; + } else if (e.target.parentElement.className === 'theme') { + theme = e.target.parentElement.id; + } else { + return; + } + if (theme === 'default_theme' || theme === null) { + delete_saved_theme(); + set_theme(get_theme(), false); + } else { + set_theme(theme); + } + }); + + themePopup.addEventListener('focusout', function(e) { + // e.relatedTarget is null in Safari and Firefox on macOS (see workaround below) + if (!!e.relatedTarget && + !themeToggleButton.contains(e.relatedTarget) && + !themePopup.contains(e.relatedTarget) + ) { + hideThemes(); + } + }); + + // Should not be needed, but it works around an issue on macOS & iOS: + // https://github.com/rust-lang/mdBook/issues/628 + document.addEventListener('click', function(e) { + if (themePopup.style.display === 'block' && + !themeToggleButton.contains(e.target) && + !themePopup.contains(e.target) + ) { + hideThemes(); + } + }); + + document.addEventListener('keydown', function(e) { + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { + return; + } + if (!themePopup.contains(e.target)) { + return; + } + + let li; + switch (e.key) { + case 'Escape': + e.preventDefault(); + hideThemes(); + break; + case 'ArrowUp': + e.preventDefault(); + li = document.activeElement.parentElement; + if (li && li.previousElementSibling) { + li.previousElementSibling.querySelector('button').focus(); + } + break; + case 'ArrowDown': + e.preventDefault(); + li = document.activeElement.parentElement; + if (li && li.nextElementSibling) { + li.nextElementSibling.querySelector('button').focus(); + } + break; + case 'Home': + e.preventDefault(); + themePopup.querySelector('li:first-child button').focus(); + break; + case 'End': + e.preventDefault(); + themePopup.querySelector('li:last-child button').focus(); + break; + } + }); +})(); + +(function sidebar() { + const body = document.querySelector('body'); + const sidebar = document.getElementById('sidebar'); + const sidebarLinks = document.querySelectorAll('#sidebar a'); + const sidebarToggleButton = document.getElementById('sidebar-toggle'); + const sidebarToggleAnchor = document.getElementById('sidebar-toggle-anchor'); + const sidebarResizeHandle = document.getElementById('sidebar-resize-handle'); + let firstContact = null; + + function showSidebar() { + body.classList.remove('sidebar-hidden'); + body.classList.add('sidebar-visible'); + Array.from(sidebarLinks).forEach(function(link) { + link.setAttribute('tabIndex', 0); + }); + sidebarToggleButton.setAttribute('aria-expanded', true); + sidebar.setAttribute('aria-hidden', false); + try { + localStorage.setItem('mdbook-sidebar', 'visible'); + } catch (e) { + // Ignore error. + } + } + + function hideSidebar() { + body.classList.remove('sidebar-visible'); + body.classList.add('sidebar-hidden'); + Array.from(sidebarLinks).forEach(function(link) { + link.setAttribute('tabIndex', -1); + }); + sidebarToggleButton.setAttribute('aria-expanded', false); + sidebar.setAttribute('aria-hidden', true); + try { + localStorage.setItem('mdbook-sidebar', 'hidden'); + } catch (e) { + // Ignore error. + } + } + + // Toggle sidebar + sidebarToggleAnchor.addEventListener('change', function sidebarToggle() { + if (sidebarToggleAnchor.checked) { + const current_width = parseInt( + document.documentElement.style.getPropertyValue('--sidebar-target-width'), 10); + if (current_width < 150) { + document.documentElement.style.setProperty('--sidebar-target-width', '150px'); + } + showSidebar(); + } else { + hideSidebar(); + } + }); + + sidebarResizeHandle.addEventListener('mousedown', initResize, false); + + function initResize() { + window.addEventListener('mousemove', resize, false); + window.addEventListener('mouseup', stopResize, false); + body.classList.add('sidebar-resizing'); + } + function resize(e) { + let pos = e.clientX - sidebar.offsetLeft; + if (pos < 20) { + hideSidebar(); + } else { + if (body.classList.contains('sidebar-hidden')) { + showSidebar(); + } + pos = Math.min(pos, window.innerWidth - 100); + document.documentElement.style.setProperty('--sidebar-target-width', pos + 'px'); + } + } + //on mouseup remove windows functions mousemove & mouseup + function stopResize() { + body.classList.remove('sidebar-resizing'); + window.removeEventListener('mousemove', resize, false); + window.removeEventListener('mouseup', stopResize, false); + } + + document.addEventListener('touchstart', function(e) { + firstContact = { + x: e.touches[0].clientX, + time: Date.now(), + }; + }, { passive: true }); + + document.addEventListener('touchmove', function(e) { + if (!firstContact) { + return; + } + + const curX = e.touches[0].clientX; + const xDiff = curX - firstContact.x, + tDiff = Date.now() - firstContact.time; + + if (tDiff < 250 && Math.abs(xDiff) >= 150) { + if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300)) { + showSidebar(); + } else if (xDiff < 0 && curX < 300) { + hideSidebar(); + } + + firstContact = null; + } + }, { passive: true }); +})(); + +(function chapterNavigation() { + document.addEventListener('keydown', function(e) { + if (e.altKey || e.ctrlKey || e.metaKey) { + return; + } + if (window.search && window.search.hasFocus()) { + return; + } + const html = document.querySelector('html'); + + function next() { + const nextButton = document.querySelector('.nav-chapters.next'); + if (nextButton) { + window.location.href = nextButton.href; + } + } + function prev() { + const previousButton = document.querySelector('.nav-chapters.previous'); + if (previousButton) { + window.location.href = previousButton.href; + } + } + function showHelp() { + const container = document.getElementById('mdbook-help-container'); + const overlay = document.getElementById('mdbook-help-popup'); + container.style.display = 'flex'; + + // Clicking outside the popup will dismiss it. + const mouseHandler = event => { + if (overlay.contains(event.target)) { + return; + } + if (event.button !== 0) { + return; + } + event.preventDefault(); + event.stopPropagation(); + document.removeEventListener('mousedown', mouseHandler); + hideHelp(); + }; + + // Pressing esc will dismiss the popup. + const escapeKeyHandler = event => { + if (event.key === 'Escape') { + event.preventDefault(); + event.stopPropagation(); + document.removeEventListener('keydown', escapeKeyHandler, true); + hideHelp(); + } + }; + document.addEventListener('keydown', escapeKeyHandler, true); + document.getElementById('mdbook-help-container') + .addEventListener('mousedown', mouseHandler); + } + function hideHelp() { + document.getElementById('mdbook-help-container').style.display = 'none'; + } + + // Usually needs the Shift key to be pressed + switch (e.key) { + case '?': + e.preventDefault(); + showHelp(); + break; + } + + // Rest of the keys are only active when the Shift key is not pressed + if (e.shiftKey) { + return; + } + + switch (e.key) { + case 'ArrowRight': + e.preventDefault(); + if (html.dir === 'rtl') { + prev(); + } else { + next(); + } + break; + case 'ArrowLeft': + e.preventDefault(); + if (html.dir === 'rtl') { + next(); + } else { + prev(); + } + break; + } + }); +})(); + +(function clipboard() { + const clipButtons = document.querySelectorAll('.clip-button'); + + function hideTooltip(elem) { + elem.firstChild.innerText = ''; + elem.className = 'clip-button'; + } + + function showTooltip(elem, msg) { + elem.firstChild.innerText = msg; + elem.className = 'clip-button tooltipped'; + } + + const clipboardSnippets = new ClipboardJS('.clip-button', { + text: function(trigger) { + hideTooltip(trigger); + const playground = trigger.closest('pre'); + return playground_text(playground, false); + }, + }); + + Array.from(clipButtons).forEach(function(clipButton) { + clipButton.addEventListener('mouseout', function(e) { + hideTooltip(e.currentTarget); + }); + }); + + clipboardSnippets.on('success', function(e) { + e.clearSelection(); + showTooltip(e.trigger, 'Copied!'); + }); + + clipboardSnippets.on('error', function(e) { + showTooltip(e.trigger, 'Clipboard error!'); + }); +})(); + +(function scrollToTop() { + const menuTitle = document.querySelector('.menu-title'); + + menuTitle.addEventListener('click', function() { + document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' }); + }); +})(); + +(function controllMenu() { + const menu = document.getElementById('menu-bar'); + + (function controllPosition() { + let scrollTop = document.scrollingElement.scrollTop; + let prevScrollTop = scrollTop; + const minMenuY = -menu.clientHeight - 50; + // When the script loads, the page can be at any scroll (e.g. if you reforesh it). + menu.style.top = scrollTop + 'px'; + // Same as parseInt(menu.style.top.slice(0, -2), but faster + let topCache = menu.style.top.slice(0, -2); + menu.classList.remove('sticky'); + let stickyCache = false; // Same as menu.classList.contains('sticky'), but faster + document.addEventListener('scroll', function() { + scrollTop = Math.max(document.scrollingElement.scrollTop, 0); + // `null` means that it doesn't need to be updated + let nextSticky = null; + let nextTop = null; + const scrollDown = scrollTop > prevScrollTop; + const menuPosAbsoluteY = topCache - scrollTop; + if (scrollDown) { + nextSticky = false; + if (menuPosAbsoluteY > 0) { + nextTop = prevScrollTop; + } + } else { + if (menuPosAbsoluteY > 0) { + nextSticky = true; + } else if (menuPosAbsoluteY < minMenuY) { + nextTop = prevScrollTop + minMenuY; + } + } + if (nextSticky === true && stickyCache === false) { + menu.classList.add('sticky'); + stickyCache = true; + } else if (nextSticky === false && stickyCache === true) { + menu.classList.remove('sticky'); + stickyCache = false; + } + if (nextTop !== null) { + menu.style.top = nextTop + 'px'; + topCache = nextTop; + } + prevScrollTop = scrollTop; + }, { passive: true }); + })(); + (function controllBorder() { + function updateBorder() { + if (menu.offsetTop === 0) { + menu.classList.remove('bordered'); + } else { + menu.classList.add('bordered'); + } + } + updateBorder(); + document.addEventListener('scroll', updateBorder, { passive: true }); + })(); +})(); diff --git a/zh-CN/cache_control.html b/zh-CN/cache_control.html new file mode 100644 index 000000000..a9c64d78e --- /dev/null +++ b/zh-CN/cache_control.html @@ -0,0 +1,260 @@ + + + + + + 查询缓存控制 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

查询缓存控制

+

生产环境下通常依赖缓存来提高性能。

+

一个 GraphQL 查询会调用多个 Resolver 函数,每个 Resolver 函数都能够具有不同的缓存定义。有的可能缓存几秒钟,有的可能缓存几个小时,有的可能所有用户都相同,有的可能每个会话都不同。

+

Async-graphql提供一种机制允许定义结果的缓存时间和作用域。

+

你可以在对象上定义缓存参数,也可以在字段上定义,下面的例子展示了缓存控制参数的两种用法。

+

你可以用max_age参数来控制缓存时长(单位是秒),也可以用publicprivate来控制缓存的作用域,当你不指定时,作用域默认是public

+

Async-graphql查询时会合并所有缓存控制指令的结果,max_age取最小值。如果任何对象或者字段的作用域为private,则其结果的作用域为private,否则为public

+

我们可以从查询结果QueryResponse中获取缓存控制合并结果,并且调用CacheControl::value来获取对应的 HTTP 头。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object(cache_control(max_age = 60))]
+impl Query {
+    #[graphql(cache_control(max_age = 30))]
+    async fn value1(&self) -> i32 {
+        1
+    }
+
+    #[graphql(cache_control(private))]
+    async fn value2(&self) -> i32 {
+        2
+    }
+
+    async fn value3(&self) -> i32 {
+        3
+    }
+}
+}
+

下面是不同的查询对应不同缓存控制结果:

+
# max_age=30
+{ value1 }
+
+
# max_age=30, private
+{ value1 value2 }
+
+
# max_age=60
+{ value3 }
+
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/clipboard.min.js b/zh-CN/clipboard.min.js new file mode 100644 index 000000000..02c549e35 --- /dev/null +++ b/zh-CN/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.4 + * https://zenorocha.github.io/clipboard.js + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return function(n){var o={};function r(t){if(o[t])return o[t].exports;var e=o[t]={i:t,l:!1,exports:{}};return n[t].call(e.exports,e,e.exports,r),e.l=!0,e.exports}return r.m=n,r.c=o,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=0)}([function(t,e,n){"use strict";var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function o(t,e){for(var n=0;n + + + + + 查询上下文 (Context) - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

查询上下文 (Context)

+

Context的主要目标是获取附加到Schema的全局数据或者与正在处理的实际查询相关的数据。

+

存储数据

+

Context中你可以存放全局数据,例如环境变量、数据库连接池,以及你在每个查询中可能需要的任何内容。

+

数据必须实现SendSync

+

你可以通过调用ctx.data::<TypeOfYourData>()来获取查询中的数据。

+

主意:如果 Resolver 函数的返回值是从Context中借用的,则需要明确说明参数的生命周期。

+

下面的例子展示了如何从Context中借用数据。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn borrow_from_context_data<'ctx>(
+        &self,
+        ctx: &Context<'ctx>
+    ) -> Result<&'ctx String> {
+        ctx.data::<String>()
+    }
+}
+}
+

Schema 数据

+

你可以在创建Schema时将数据放入上下文中,这对于不会更改的数据非常有用,例如连接池。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Default,SimpleObject)]
+struct Query { version: i32}
+struct EnvStruct;
+let env_struct = EnvStruct;
+struct S3Object;
+let s3_storage = S3Object;
+struct DBConnection;
+let db_core = DBConnection;
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription)
+    .data(env_struct)
+    .data(s3_storage)
+    .data(db_core)
+    .finish();
+}
+

请求数据

+

你可以在执行请求时将数据放入上下文中,它对于身份验证数据很有用。

+

一个使用warp的小例子:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate async_graphql_warp;
+extern crate warp;
+use async_graphql::*;
+use warp::{Filter, Reply};
+use std::convert::Infallible;
+#[derive(Default, SimpleObject)]
+struct Query { name: String }
+struct AuthInfo { pub token: Option<String> }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+let schema_filter = async_graphql_warp::graphql(schema);
+let graphql_post = warp::post()
+  .and(warp::path("graphql"))
+  .and(warp::header::optional("Authorization"))
+  .and(schema_filter)
+  .and_then( |auth: Option<String>, (schema, mut request): (Schema<Query, EmptyMutation, EmptySubscription>, async_graphql::Request)| async move {
+    // Do something to get auth data from the header
+    let your_auth_data = AuthInfo { token: auth };
+    let response = schema
+      .execute(
+        request
+         .data(your_auth_data)
+      ).await;
+      
+    Ok::<_, Infallible>(async_graphql_warp::GraphQLResponse::from(response))
+  });
+}
+

HTTP 头

+

使用Context你还可以插入或添加 HTTP 头。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate http;
+use ::http::header::ACCESS_CONTROL_ALLOW_ORIGIN;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query {
+    async fn greet(&self, ctx: &Context<'_>) -> String {
+        // Headers can be inserted using the `http` constants
+        let was_in_headers = ctx.insert_http_header(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
+
+        // They can also be inserted using &str
+        let was_in_headers = ctx.insert_http_header("Custom-Header", "1234");
+
+        // If multiple headers with the same key are `inserted` then the most recent
+        // one overwrites the previous. If you want multiple headers for the same key, use
+        // `append_http_header` for subsequent headers
+        let was_in_headers = ctx.append_http_header("Custom-Header", "Hello World");
+
+        String::from("Hello world")
+    }
+}
+}
+

Selection / LookAhead

+

有时你想知道子查询中请求了哪些字段用于优化数据处理,则可以使用ctx.field()读取查询中的字段,它将提供一个SelectionField,允许你在当前字段和子字段之间导航。

+

如果要跨查询或子查询执行搜索,则不必使用 SelectionField 手动执行此操作,可以使用 ctx.look_ahead() 来执行选择。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+#[derive(SimpleObject)]
+struct Detail {
+    c: i32,
+    d: i32,
+}
+
+#[derive(SimpleObject)]
+struct MyObj {
+    a: i32,
+    b: i32,
+    detail: Detail,
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn obj(&self, ctx: &Context<'_>) -> MyObj {
+        if ctx.look_ahead().field("a").exists() {
+            // This is a query like `obj { a }`
+        } else if ctx.look_ahead().field("detail").field("c").exists() {
+            // This is a query like `obj { detail { c } }`
+        } else {
+            // This query doesn't have `a`
+        }
+        unimplemented!()
+    }
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/css/chrome.css b/zh-CN/css/chrome.css new file mode 100644 index 000000000..360a65372 --- /dev/null +++ b/zh-CN/css/chrome.css @@ -0,0 +1,701 @@ +/* CSS for UI elements (a.k.a. chrome) */ + +html { + scrollbar-color: var(--scrollbar) var(--bg); +} +#searchresults a, +.content a:link, +a:visited, +a > .hljs { + color: var(--links); +} + +/* + body-container is necessary because mobile browsers don't seem to like + overflow-x on the body tag when there is a tag. +*/ +#body-container { + /* + This is used when the sidebar pushes the body content off the side of + the screen on small screens. Without it, dragging on mobile Safari + will want to reposition the viewport in a weird way. + */ + overflow-x: clip; +} + +/* Menu Bar */ + +#menu-bar, +#menu-bar-hover-placeholder { + z-index: 101; + margin: auto calc(0px - var(--page-padding)); +} +#menu-bar { + position: relative; + display: flex; + flex-wrap: wrap; + background-color: var(--bg); + border-block-end-color: var(--bg); + border-block-end-width: 1px; + border-block-end-style: solid; +} +#menu-bar.sticky, +#menu-bar-hover-placeholder:hover + #menu-bar, +#menu-bar:hover, +html.sidebar-visible #menu-bar { + position: -webkit-sticky; + position: sticky; + top: 0 !important; +} +#menu-bar-hover-placeholder { + position: sticky; + position: -webkit-sticky; + top: 0; + height: var(--menu-bar-height); +} +#menu-bar.bordered { + border-block-end-color: var(--table-border-color); +} +#menu-bar i, #menu-bar .icon-button { + position: relative; + padding: 0 8px; + z-index: 10; + line-height: var(--menu-bar-height); + cursor: pointer; + transition: color 0.5s; +} +@media only screen and (max-width: 420px) { + #menu-bar i, #menu-bar .icon-button { + padding: 0 5px; + } +} + +.icon-button { + border: none; + background: none; + padding: 0; + color: inherit; +} +.icon-button i { + margin: 0; +} + +.right-buttons { + margin: 0 15px; +} +.right-buttons a { + text-decoration: none; +} + +.left-buttons { + display: flex; + margin: 0 5px; +} +html:not(.js) .left-buttons button { + display: none; +} + +.menu-title { + display: inline-block; + font-weight: 200; + font-size: 2.4rem; + line-height: var(--menu-bar-height); + text-align: center; + margin: 0; + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.menu-title { + cursor: pointer; +} + +.menu-bar, +.menu-bar:visited, +.nav-chapters, +.nav-chapters:visited, +.mobile-nav-chapters, +.mobile-nav-chapters:visited, +.menu-bar .icon-button, +.menu-bar a i { + color: var(--icons); +} + +.menu-bar i:hover, +.menu-bar .icon-button:hover, +.nav-chapters:hover, +.mobile-nav-chapters i:hover { + color: var(--icons-hover); +} + +/* Nav Icons */ + +.nav-chapters { + font-size: 2.5em; + text-align: center; + text-decoration: none; + + position: fixed; + top: 0; + bottom: 0; + margin: 0; + max-width: 150px; + min-width: 90px; + + display: flex; + justify-content: center; + align-content: center; + flex-direction: column; + + transition: color 0.5s, background-color 0.5s; +} + +.nav-chapters:hover { + text-decoration: none; + background-color: var(--theme-hover); + transition: background-color 0.15s, color 0.15s; +} + +.nav-wrapper { + margin-block-start: 50px; + display: none; +} + +.mobile-nav-chapters { + font-size: 2.5em; + text-align: center; + text-decoration: none; + width: 90px; + border-radius: 5px; + background-color: var(--sidebar-bg); +} + +/* Only Firefox supports flow-relative values */ +.previous { float: left; } +[dir=rtl] .previous { float: right; } + +/* Only Firefox supports flow-relative values */ +.next { + float: right; + right: var(--page-padding); +} +[dir=rtl] .next { + float: left; + right: unset; + left: var(--page-padding); +} + +/* Use the correct buttons for RTL layouts*/ +[dir=rtl] .previous i.fa-angle-left:before {content:"\f105";} +[dir=rtl] .next i.fa-angle-right:before { content:"\f104"; } + +@media only screen and (max-width: 1080px) { + .nav-wide-wrapper { display: none; } + .nav-wrapper { display: block; } +} + +/* sidebar-visible */ +@media only screen and (max-width: 1380px) { + #sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wide-wrapper { display: none; } + #sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wrapper { display: block; } +} + +/* Inline code */ + +:not(pre) > .hljs { + display: inline; + padding: 0.1em 0.3em; + border-radius: 3px; +} + +:not(pre):not(a) > .hljs { + color: var(--inline-code-color); + overflow-x: initial; +} + +a:hover > .hljs { + text-decoration: underline; +} + +pre { + position: relative; +} +pre > .buttons { + position: absolute; + z-index: 100; + right: 0px; + top: 2px; + margin: 0px; + padding: 2px 0px; + + color: var(--sidebar-fg); + cursor: pointer; + visibility: hidden; + opacity: 0; + transition: visibility 0.1s linear, opacity 0.1s linear; +} +pre:hover > .buttons { + visibility: visible; + opacity: 1 +} +pre > .buttons :hover { + color: var(--sidebar-active); + border-color: var(--icons-hover); + background-color: var(--theme-hover); +} +pre > .buttons i { + margin-inline-start: 8px; +} +pre > .buttons button { + cursor: inherit; + margin: 0px 5px; + padding: 4px 4px 3px 5px; + font-size: 23px; + + border-style: solid; + border-width: 1px; + border-radius: 4px; + border-color: var(--icons); + background-color: var(--theme-popup-bg); + transition: 100ms; + transition-property: color,border-color,background-color; + color: var(--icons); +} + +pre > .buttons button.clip-button { + padding: 2px 4px 0px 6px; +} +pre > .buttons button.clip-button::before { + /* clipboard image from octicons (https://github.com/primer/octicons/tree/v2.0.0) MIT license + */ + content: url('data:image/svg+xml,\ +\ +\ +'); + filter: var(--copy-button-filter); +} +pre > .buttons button.clip-button:hover::before { + filter: var(--copy-button-filter-hover); +} + +@media (pointer: coarse) { + pre > .buttons button { + /* On mobile, make it easier to tap buttons. */ + padding: 0.3rem 1rem; + } + + .sidebar-resize-indicator { + /* Hide resize indicator on devices with limited accuracy */ + display: none; + } +} +pre > code { + display: block; + padding: 1rem; +} + +/* FIXME: ACE editors overlap their buttons because ACE does absolute + positioning within the code block which breaks padding. The only solution I + can think of is to move the padding to the outer pre tag (or insert a div + wrapper), but that would require fixing a whole bunch of CSS rules. +*/ +.hljs.ace_editor { + padding: 0rem 0rem; +} + +pre > .result { + margin-block-start: 10px; +} + +/* Search */ + +#searchresults a { + text-decoration: none; +} + +mark { + border-radius: 2px; + padding-block-start: 0; + padding-block-end: 1px; + padding-inline-start: 3px; + padding-inline-end: 3px; + margin-block-start: 0; + margin-block-end: -1px; + margin-inline-start: -3px; + margin-inline-end: -3px; + background-color: var(--search-mark-bg); + transition: background-color 300ms linear; + cursor: pointer; +} + +mark.fade-out { + background-color: rgba(0,0,0,0) !important; + cursor: auto; +} + +.searchbar-outer { + margin-inline-start: auto; + margin-inline-end: auto; + max-width: var(--content-max-width); +} + +#searchbar { + width: 100%; + margin-block-start: 5px; + margin-block-end: 0; + margin-inline-start: auto; + margin-inline-end: auto; + padding: 10px 16px; + transition: box-shadow 300ms ease-in-out; + border: 1px solid var(--searchbar-border-color); + border-radius: 3px; + background-color: var(--searchbar-bg); + color: var(--searchbar-fg); +} +#searchbar:focus, +#searchbar.active { + box-shadow: 0 0 3px var(--searchbar-shadow-color); +} + +.searchresults-header { + font-weight: bold; + font-size: 1em; + padding-block-start: 18px; + padding-block-end: 0; + padding-inline-start: 5px; + padding-inline-end: 0; + color: var(--searchresults-header-fg); +} + +.searchresults-outer { + margin-inline-start: auto; + margin-inline-end: auto; + max-width: var(--content-max-width); + border-block-end: 1px dashed var(--searchresults-border-color); +} + +ul#searchresults { + list-style: none; + padding-inline-start: 20px; +} +ul#searchresults li { + margin: 10px 0px; + padding: 2px; + border-radius: 2px; +} +ul#searchresults li.focus { + background-color: var(--searchresults-li-bg); +} +ul#searchresults span.teaser { + display: block; + clear: both; + margin-block-start: 5px; + margin-block-end: 0; + margin-inline-start: 20px; + margin-inline-end: 0; + font-size: 0.8em; +} +ul#searchresults span.teaser em { + font-weight: bold; + font-style: normal; +} + +/* Sidebar */ + +.sidebar { + position: fixed; + left: 0; + top: 0; + bottom: 0; + width: var(--sidebar-width); + font-size: 0.875em; + box-sizing: border-box; + -webkit-overflow-scrolling: touch; + overscroll-behavior-y: contain; + background-color: var(--sidebar-bg); + color: var(--sidebar-fg); +} +.sidebar-iframe-inner { + --padding: 10px; + + background-color: var(--sidebar-bg); + padding: var(--padding); + margin: 0; + font-size: 1.4rem; + color: var(--sidebar-fg); + min-height: calc(100vh - var(--padding) * 2); +} +.sidebar-iframe-outer { + border: none; + height: 100%; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; +} +[dir=rtl] .sidebar { left: unset; right: 0; } +.sidebar-resizing { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} +html:not(.sidebar-resizing) .sidebar { + transition: transform 0.3s; /* Animation: slide away */ +} +.sidebar code { + line-height: 2em; +} +.sidebar .sidebar-scrollbox { + overflow-y: auto; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + padding: 10px 10px; +} +.sidebar .sidebar-resize-handle { + position: absolute; + cursor: col-resize; + width: 0; + right: calc(var(--sidebar-resize-indicator-width) * -1); + top: 0; + bottom: 0; + display: flex; + align-items: center; +} + +.sidebar-resize-handle .sidebar-resize-indicator { + width: 100%; + height: 16px; + color: var(--icons); + margin-inline-start: var(--sidebar-resize-indicator-space); + display: flex; + align-items: center; + justify-content: flex-start; +} +.sidebar-resize-handle .sidebar-resize-indicator::before { + content: ""; + width: 2px; + height: 12px; + border-left: dotted 2px currentColor; +} +.sidebar-resize-handle .sidebar-resize-indicator::after { + content: ""; + width: 2px; + height: 16px; + border-left: dotted 2px currentColor; +} + +[dir=rtl] .sidebar .sidebar-resize-handle { + left: calc(var(--sidebar-resize-indicator-width) * -1); + right: unset; +} +.js .sidebar .sidebar-resize-handle { + cursor: col-resize; + width: calc(var(--sidebar-resize-indicator-width) - var(--sidebar-resize-indicator-space)); +} +/* sidebar-hidden */ +#sidebar-toggle-anchor:not(:checked) ~ .sidebar { + transform: translateX(calc(0px - var(--sidebar-width) - var(--sidebar-resize-indicator-width))); + z-index: -1; +} +[dir=rtl] #sidebar-toggle-anchor:not(:checked) ~ .sidebar { + transform: translateX(calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width))); +} +.sidebar::-webkit-scrollbar { + background: var(--sidebar-bg); +} +.sidebar::-webkit-scrollbar-thumb { + background: var(--scrollbar); +} + +/* sidebar-visible */ +#sidebar-toggle-anchor:checked ~ .page-wrapper { + transform: translateX(calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width))); +} +[dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper { + transform: translateX(calc(0px - var(--sidebar-width) - var(--sidebar-resize-indicator-width))); +} +@media only screen and (min-width: 620px) { + #sidebar-toggle-anchor:checked ~ .page-wrapper { + transform: none; + margin-inline-start: calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width)); + } + [dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper { + transform: none; + } +} + +.chapter { + list-style: none outside none; + padding-inline-start: 0; + line-height: 2.2em; +} + +.chapter ol { + width: 100%; +} + +.chapter li { + display: flex; + color: var(--sidebar-non-existant); +} +.chapter li a { + display: block; + padding: 0; + text-decoration: none; + color: var(--sidebar-fg); +} + +.chapter li a:hover { + color: var(--sidebar-active); +} + +.chapter li a.active { + color: var(--sidebar-active); +} + +.chapter li > a.toggle { + cursor: pointer; + display: block; + margin-inline-start: auto; + padding: 0 10px; + user-select: none; + opacity: 0.68; +} + +.chapter li > a.toggle div { + transition: transform 0.5s; +} + +/* collapse the section */ +.chapter li:not(.expanded) + li > ol { + display: none; +} + +.chapter li.chapter-item { + line-height: 1.5em; + margin-block-start: 0.6em; +} + +.chapter li.expanded > a.toggle div { + transform: rotate(90deg); +} + +.spacer { + width: 100%; + height: 3px; + margin: 5px 0px; +} +.chapter .spacer { + background-color: var(--sidebar-spacer); +} + +@media (-moz-touch-enabled: 1), (pointer: coarse) { + .chapter li a { padding: 5px 0; } + .spacer { margin: 10px 0; } +} + +.section { + list-style: none outside none; + padding-inline-start: 20px; + line-height: 1.9em; +} + +/* Theme Menu Popup */ + +.theme-popup { + position: absolute; + left: 10px; + top: var(--menu-bar-height); + z-index: 1000; + border-radius: 4px; + font-size: 0.7em; + color: var(--fg); + background: var(--theme-popup-bg); + border: 1px solid var(--theme-popup-border); + margin: 0; + padding: 0; + list-style: none; + display: none; + /* Don't let the children's background extend past the rounded corners. */ + overflow: hidden; +} +[dir=rtl] .theme-popup { left: unset; right: 10px; } +.theme-popup .default { + color: var(--icons); +} +.theme-popup .theme { + width: 100%; + border: 0; + margin: 0; + padding: 2px 20px; + line-height: 25px; + white-space: nowrap; + text-align: start; + cursor: pointer; + color: inherit; + background: inherit; + font-size: inherit; +} +.theme-popup .theme:hover { + background-color: var(--theme-hover); +} + +.theme-selected::before { + display: inline-block; + content: "✓"; + margin-inline-start: -14px; + width: 14px; +} + +/* The container for the help popup that covers the whole window. */ +#mdbook-help-container { + /* Position and size for the whole window. */ + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + /* This uses flex layout (which is set in book.js), and centers the popup + in the window.*/ + display: none; + align-items: center; + justify-content: center; + z-index: 1000; + /* Dim out the book while the popup is visible. */ + background: var(--overlay-bg); +} + +/* The popup help box. */ +#mdbook-help-popup { + box-shadow: 0 4px 24px rgba(0,0,0,0.15); + min-width: 300px; + max-width: 500px; + width: 100%; + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: center; + background-color: var(--bg); + color: var(--fg); + border-width: 1px; + border-color: var(--theme-popup-border); + border-style: solid; + border-radius: 8px; + padding: 10px; +} + +.mdbook-help-title { + text-align: center; + /* mdbook's margin for h2 is way too large. */ + margin: 10px; +} diff --git a/zh-CN/css/general.css b/zh-CN/css/general.css new file mode 100644 index 000000000..9946cfc01 --- /dev/null +++ b/zh-CN/css/general.css @@ -0,0 +1,279 @@ +/* Base styles and content styles */ + +:root { + /* Browser default font-size is 16px, this way 1 rem = 10px */ + font-size: 62.5%; + color-scheme: var(--color-scheme); +} + +html { + font-family: "Open Sans", sans-serif; + color: var(--fg); + background-color: var(--bg); + text-size-adjust: none; + -webkit-text-size-adjust: none; +} + +body { + margin: 0; + font-size: 1.6rem; + overflow-x: hidden; +} + +code { + font-family: var(--mono-font) !important; + font-size: var(--code-font-size); + direction: ltr !important; +} + +/* make long words/inline code not x overflow */ +main { + overflow-wrap: break-word; +} + +/* make wide tables scroll if they overflow */ +.table-wrapper { + overflow-x: auto; +} + +/* Don't change font size in headers. */ +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + font-size: unset; +} + +.left { float: left; } +.right { float: right; } +.boring { opacity: 0.6; } +.hide-boring .boring { display: none; } +.hidden { display: none !important; } + +h2, h3 { margin-block-start: 2.5em; } +h4, h5 { margin-block-start: 2em; } + +.header + .header h3, +.header + .header h4, +.header + .header h5 { + margin-block-start: 1em; +} + +h1:target::before, +h2:target::before, +h3:target::before, +h4:target::before, +h5:target::before, +h6:target::before { + display: inline-block; + content: "»"; + margin-inline-start: -30px; + width: 30px; +} + +/* This is broken on Safari as of version 14, but is fixed + in Safari Technology Preview 117 which I think will be Safari 14.2. + https://bugs.webkit.org/show_bug.cgi?id=218076 +*/ +:target { + /* Safari does not support logical properties */ + scroll-margin-top: calc(var(--menu-bar-height) + 0.5em); +} + +.page { + outline: 0; + padding: 0 var(--page-padding); + margin-block-start: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ +} +.page-wrapper { + box-sizing: border-box; + background-color: var(--bg); +} +.no-js .page-wrapper, +.js:not(.sidebar-resizing) .page-wrapper { + transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ +} +[dir=rtl] .js:not(.sidebar-resizing) .page-wrapper { + transition: margin-right 0.3s ease, transform 0.3s ease; /* Animation: slide away */ +} + +.content { + overflow-y: auto; + padding: 0 5px 50px 5px; +} +.content main { + margin-inline-start: auto; + margin-inline-end: auto; + max-width: var(--content-max-width); +} +.content p { line-height: 1.45em; } +.content ol { line-height: 1.45em; } +.content ul { line-height: 1.45em; } +.content a { text-decoration: none; } +.content a:hover { text-decoration: underline; } +.content img, .content video { max-width: 100%; } +.content .header:link, +.content .header:visited { + color: var(--fg); +} +.content .header:link, +.content .header:visited:hover { + text-decoration: none; +} + +table { + margin: 0 auto; + border-collapse: collapse; +} +table td { + padding: 3px 20px; + border: 1px var(--table-border-color) solid; +} +table thead { + background: var(--table-header-bg); +} +table thead td { + font-weight: 700; + border: none; +} +table thead th { + padding: 3px 20px; +} +table thead tr { + border: 1px var(--table-header-bg) solid; +} +/* Alternate background colors for rows */ +table tbody tr:nth-child(2n) { + background: var(--table-alternate-bg); +} + + +blockquote { + margin: 20px 0; + padding: 0 20px; + color: var(--fg); + background-color: var(--quote-bg); + border-block-start: .1em solid var(--quote-border); + border-block-end: .1em solid var(--quote-border); +} + +.warning { + margin: 20px; + padding: 0 20px; + border-inline-start: 2px solid var(--warning-border); +} + +.warning:before { + position: absolute; + width: 3rem; + height: 3rem; + margin-inline-start: calc(-1.5rem - 21px); + content: "ⓘ"; + text-align: center; + background-color: var(--bg); + color: var(--warning-border); + font-weight: bold; + font-size: 2rem; +} + +blockquote .warning:before { + background-color: var(--quote-bg); +} + +kbd { + background-color: var(--table-border-color); + border-radius: 4px; + border: solid 1px var(--theme-popup-border); + box-shadow: inset 0 -1px 0 var(--theme-hover); + display: inline-block; + font-size: var(--code-font-size); + font-family: var(--mono-font); + line-height: 10px; + padding: 4px 5px; + vertical-align: middle; +} + +sup { + /* Set the line-height for superscript and footnote references so that there + isn't an awkward space appearing above lines that contain the footnote. + + See https://github.com/rust-lang/mdBook/pull/2443#discussion_r1813773583 + for an explanation. + */ + line-height: 0; +} + +.footnote-definition { + font-size: 0.9em; +} +/* The default spacing for a list is a little too large. */ +.footnote-definition ul, +.footnote-definition ol { + padding-left: 20px; +} +.footnote-definition > li { + /* Required to position the ::before target */ + position: relative; +} +.footnote-definition > li:target { + scroll-margin-top: 50vh; +} +.footnote-reference:target { + scroll-margin-top: 50vh; +} +/* Draws a border around the footnote (including the marker) when it is selected. + TODO: If there are multiple linkbacks, highlight which one you just came + from so you know which one to click. +*/ +.footnote-definition > li:target::before { + border: 2px solid var(--footnote-highlight); + border-radius: 6px; + position: absolute; + top: -8px; + right: -8px; + bottom: -8px; + left: -32px; + pointer-events: none; + content: ""; +} +/* Pulses the footnote reference so you can quickly see where you left off reading. + This could use some improvement. +*/ +@media not (prefers-reduced-motion) { + .footnote-reference:target { + animation: fn-highlight 0.8s; + border-radius: 2px; + } + + @keyframes fn-highlight { + from { + background-color: var(--footnote-highlight); + } + } +} + +.tooltiptext { + position: absolute; + visibility: hidden; + color: #fff; + background-color: #333; + transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ + left: -8px; /* Half of the width of the icon */ + top: -35px; + font-size: 0.8em; + text-align: center; + border-radius: 6px; + padding: 5px 8px; + margin: 5px; + z-index: 1000; +} +.tooltipped .tooltiptext { + visibility: visible; +} + +.chapter li.part-title { + color: var(--sidebar-fg); + margin: 5px 0px; + font-weight: bold; +} + +.result-no-output { + font-style: italic; +} diff --git a/zh-CN/css/print.css b/zh-CN/css/print.css new file mode 100644 index 000000000..80ec3a544 --- /dev/null +++ b/zh-CN/css/print.css @@ -0,0 +1,50 @@ + +#sidebar, +#menu-bar, +.nav-chapters, +.mobile-nav-chapters { + display: none; +} + +#page-wrapper.page-wrapper { + transform: none !important; + margin-inline-start: 0px; + overflow-y: initial; +} + +#content { + max-width: none; + margin: 0; + padding: 0; +} + +.page { + overflow-y: initial; +} + +code { + direction: ltr !important; +} + +pre > .buttons { + z-index: 2; +} + +a, a:visited, a:active, a:hover { + color: #4183c4; + text-decoration: none; +} + +h1, h2, h3, h4, h5, h6 { + page-break-inside: avoid; + page-break-after: avoid; +} + +pre, code { + page-break-inside: avoid; + white-space: pre-wrap; +} + +.fa { + display: none !important; +} diff --git a/zh-CN/css/variables.css b/zh-CN/css/variables.css new file mode 100644 index 000000000..5742d2414 --- /dev/null +++ b/zh-CN/css/variables.css @@ -0,0 +1,330 @@ + +/* Globals */ + +:root { + --sidebar-target-width: 300px; + --sidebar-width: min(var(--sidebar-target-width), 80vw); + --sidebar-resize-indicator-width: 8px; + --sidebar-resize-indicator-space: 2px; + --page-padding: 15px; + --content-max-width: 750px; + --menu-bar-height: 50px; + --mono-font: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace; + --code-font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */ +} + +/* Themes */ + +.ayu { + --bg: hsl(210, 25%, 8%); + --fg: #c5c5c5; + + --sidebar-bg: #14191f; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #5c6773; + --sidebar-active: #ffb454; + --sidebar-spacer: #2d334f; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #b7b9cc; + + --links: #0096cf; + + --inline-code-color: #ffb454; + + --theme-popup-bg: #14191f; + --theme-popup-border: #5c6773; + --theme-hover: #191f26; + + --quote-bg: hsl(226, 15%, 17%); + --quote-border: hsl(226, 15%, 22%); + + --warning-border: #ff8e00; + + --table-border-color: hsl(210, 25%, 13%); + --table-header-bg: hsl(210, 25%, 28%); + --table-alternate-bg: hsl(210, 25%, 11%); + + --searchbar-border-color: #848484; + --searchbar-bg: #424242; + --searchbar-fg: #fff; + --searchbar-shadow-color: #d4c89f; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #252932; + --search-mark-bg: #e3b171; + + --color-scheme: dark; + + /* Same as `--icons` */ + --copy-button-filter: invert(45%) sepia(6%) saturate(621%) hue-rotate(198deg) brightness(99%) contrast(85%); + /* Same as `--sidebar-active` */ + --copy-button-filter-hover: invert(68%) sepia(55%) saturate(531%) hue-rotate(341deg) brightness(104%) contrast(101%); + + --footnote-highlight: #2668a6; + + --overlay-bg: rgba(33, 40, 48, 0.4); +} + +.coal { + --bg: hsl(200, 7%, 8%); + --fg: #98a3ad; + + --sidebar-bg: #292c2f; + --sidebar-fg: #a1adb8; + --sidebar-non-existant: #505254; + --sidebar-active: #3473ad; + --sidebar-spacer: #393939; + + --scrollbar: var(--sidebar-fg); + + --icons: #43484d; + --icons-hover: #b3c0cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6; + + --theme-popup-bg: #141617; + --theme-popup-border: #43484d; + --theme-hover: #1f2124; + + --quote-bg: hsl(234, 21%, 18%); + --quote-border: hsl(234, 21%, 23%); + + --warning-border: #ff8e00; + + --table-border-color: hsl(200, 7%, 13%); + --table-header-bg: hsl(200, 7%, 28%); + --table-alternate-bg: hsl(200, 7%, 11%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #b7b7b7; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #98a3ad; + --searchresults-li-bg: #2b2b2f; + --search-mark-bg: #355c7d; + + --color-scheme: dark; + + /* Same as `--icons` */ + --copy-button-filter: invert(26%) sepia(8%) saturate(575%) hue-rotate(169deg) brightness(87%) contrast(82%); + /* Same as `--sidebar-active` */ + --copy-button-filter-hover: invert(36%) sepia(70%) saturate(503%) hue-rotate(167deg) brightness(98%) contrast(89%); + + --footnote-highlight: #4079ae; + + --overlay-bg: rgba(33, 40, 48, 0.4); +} + +.light, html:not(.js) { + --bg: hsl(0, 0%, 100%); + --fg: hsl(0, 0%, 0%); + + --sidebar-bg: #fafafa; + --sidebar-fg: hsl(0, 0%, 0%); + --sidebar-non-existant: #aaaaaa; + --sidebar-active: #1f1fff; + --sidebar-spacer: #f4f4f4; + + --scrollbar: #8F8F8F; + + --icons: #747474; + --icons-hover: #000000; + + --links: #20609f; + + --inline-code-color: #301900; + + --theme-popup-bg: #fafafa; + --theme-popup-border: #cccccc; + --theme-hover: #e6e6e6; + + --quote-bg: hsl(197, 37%, 96%); + --quote-border: hsl(197, 37%, 91%); + + --warning-border: #ff8e00; + + --table-border-color: hsl(0, 0%, 95%); + --table-header-bg: hsl(0, 0%, 80%); + --table-alternate-bg: hsl(0, 0%, 97%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #fafafa; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #e4f2fe; + --search-mark-bg: #a2cff5; + + --color-scheme: light; + + /* Same as `--icons` */ + --copy-button-filter: invert(45.49%); + /* Same as `--sidebar-active` */ + --copy-button-filter-hover: invert(14%) sepia(93%) saturate(4250%) hue-rotate(243deg) brightness(99%) contrast(130%); + + --footnote-highlight: #7e7eff; + + --overlay-bg: rgba(200, 200, 205, 0.4); +} + +.navy { + --bg: hsl(226, 23%, 11%); + --fg: #bcbdd0; + + --sidebar-bg: #282d3f; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #505274; + --sidebar-active: #2b79a2; + --sidebar-spacer: #2d334f; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #b7b9cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6; + + --theme-popup-bg: #161923; + --theme-popup-border: #737480; + --theme-hover: #282e40; + + --quote-bg: hsl(226, 15%, 17%); + --quote-border: hsl(226, 15%, 22%); + + --warning-border: #ff8e00; + + --table-border-color: hsl(226, 23%, 16%); + --table-header-bg: hsl(226, 23%, 31%); + --table-alternate-bg: hsl(226, 23%, 14%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #aeaec6; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #5f5f71; + --searchresults-border-color: #5c5c68; + --searchresults-li-bg: #242430; + --search-mark-bg: #a2cff5; + + --color-scheme: dark; + + /* Same as `--icons` */ + --copy-button-filter: invert(51%) sepia(10%) saturate(393%) hue-rotate(198deg) brightness(86%) contrast(87%); + /* Same as `--sidebar-active` */ + --copy-button-filter-hover: invert(46%) sepia(20%) saturate(1537%) hue-rotate(156deg) brightness(85%) contrast(90%); + + --footnote-highlight: #4079ae; + + --overlay-bg: rgba(33, 40, 48, 0.4); +} + +.rust { + --bg: hsl(60, 9%, 87%); + --fg: #262625; + + --sidebar-bg: #3b2e2a; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #505254; + --sidebar-active: #e69f67; + --sidebar-spacer: #45373a; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #262625; + + --links: #2b79a2; + + --inline-code-color: #6e6b5e; + + --theme-popup-bg: #e1e1db; + --theme-popup-border: #b38f6b; + --theme-hover: #99908a; + + --quote-bg: hsl(60, 5%, 75%); + --quote-border: hsl(60, 5%, 70%); + + --warning-border: #ff8e00; + + --table-border-color: hsl(60, 9%, 82%); + --table-header-bg: #b3a497; + --table-alternate-bg: hsl(60, 9%, 84%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #fafafa; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #dec2a2; + --search-mark-bg: #e69f67; + + /* Same as `--icons` */ + --copy-button-filter: invert(51%) sepia(10%) saturate(393%) hue-rotate(198deg) brightness(86%) contrast(87%); + /* Same as `--sidebar-active` */ + --copy-button-filter-hover: invert(77%) sepia(16%) saturate(1798%) hue-rotate(328deg) brightness(98%) contrast(83%); + + --footnote-highlight: #d3a17a; + + --overlay-bg: rgba(150, 150, 150, 0.25); +} + +@media (prefers-color-scheme: dark) { + html:not(.js) { + --bg: hsl(200, 7%, 8%); + --fg: #98a3ad; + + --sidebar-bg: #292c2f; + --sidebar-fg: #a1adb8; + --sidebar-non-existant: #505254; + --sidebar-active: #3473ad; + --sidebar-spacer: #393939; + + --scrollbar: var(--sidebar-fg); + + --icons: #43484d; + --icons-hover: #b3c0cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6; + + --theme-popup-bg: #141617; + --theme-popup-border: #43484d; + --theme-hover: #1f2124; + + --quote-bg: hsl(234, 21%, 18%); + --quote-border: hsl(234, 21%, 23%); + + --warning-border: #ff8e00; + + --table-border-color: hsl(200, 7%, 13%); + --table-header-bg: hsl(200, 7%, 28%); + --table-alternate-bg: hsl(200, 7%, 11%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #b7b7b7; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #98a3ad; + --searchresults-li-bg: #2b2b2f; + --search-mark-bg: #355c7d; + + --color-scheme: dark; + + /* Same as `--icons` */ + --copy-button-filter: invert(26%) sepia(8%) saturate(575%) hue-rotate(169deg) brightness(87%) contrast(82%); + /* Same as `--sidebar-active` */ + --copy-button-filter-hover: invert(36%) sepia(70%) saturate(503%) hue-rotate(167deg) brightness(98%) contrast(89%); + } +} diff --git a/zh-CN/cursor_connections.html b/zh-CN/cursor_connections.html new file mode 100644 index 000000000..79c942ac6 --- /dev/null +++ b/zh-CN/cursor_connections.html @@ -0,0 +1,264 @@ + + + + + + 游标连接 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

游标连接 (Cursor Connections)

+

Relay 定义了一套游标连接规范,以提供一致性的分页查询方式,具体的规范文档请参考GraphQL Cursor Connections Specification

+

Async-graphql中定义一个游标连接非常简单,你只需要调用 connection::query 函数,并在闭包中查询数据。

+

下面是一个简单的获取连续整数的数据源:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::types::connection::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn numbers(&self,
+        after: Option<String>,
+        before: Option<String>,
+        first: Option<i32>,
+        last: Option<i32>,
+    ) -> Result<Connection<usize, i32, EmptyFields, EmptyFields>> {
+        query(after, before, first, last, |after, before, first, last| async move {
+            let mut start = after.map(|after| after + 1).unwrap_or(0);
+            let mut end = before.unwrap_or(10000);
+            if let Some(first) = first {
+                end = (start + first).min(end);
+            }
+            if let Some(last) = last {
+                start = if last > end - start {
+                     end
+                } else {
+                    end - last
+                };
+            }
+            let mut connection = Connection::new(start > 0, end < 10000);
+            connection.edges.extend(
+                (start..end).into_iter().map(|n|
+                    Edge::with_additional_fields(n, n as i32, EmptyFields)
+            ));
+            Ok::<_, async_graphql::Error>(connection)
+        }).await
+    }
+}
+
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/custom_directive.html b/zh-CN/custom_directive.html new file mode 100644 index 000000000..974baf33a --- /dev/null +++ b/zh-CN/custom_directive.html @@ -0,0 +1,268 @@ + + + + + + 自定义指令 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

自定义指令

+

Async-graphql可以很方便的自定义指令,这可以扩展 GraphQL 的行为。

+

创建一个自定义指令,需要实现 CustomDirective trait,然后用Directive宏生成一个工厂函数,该函数接收指令的参数并返回指令的实例。

+

目前Async-graphql仅支持添加FIELD位置的指令。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct ConcatDirective {
+    value: String,
+}
+
+#[async_trait::async_trait]
+impl CustomDirective for ConcatDirective {
+    async fn resolve_field(&self, _ctx: &Context<'_>, resolve: ResolveFut<'_>) -> ServerResult<Option<Value>> {
+        resolve.await.map(|value| {
+            value.map(|value| match value {
+                Value::String(str) => Value::String(str + &self.value),
+                _ => value,
+            })
+        })
+    }
+}
+
+#[Directive(location = "Field")]
+fn concat(value: String) -> impl CustomDirective {
+    ConcatDirective { value }
+}
+}
+

创建模式时注册指令:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+struct ConcatDirective { value: String, }
+#[async_trait::async_trait]
+impl CustomDirective for ConcatDirective {
+  async fn resolve_field(&self, _ctx: &Context<'_>, resolve: ResolveFut<'_>) -> ServerResult<Option<Value>> { todo!() }
+}
+#[Directive(location = "Field")]
+fn concat(value: String) -> impl CustomDirective { ConcatDirective { value } }
+let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
+    .directive(concat)
+    .finish();
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/custom_scalars.html b/zh-CN/custom_scalars.html new file mode 100644 index 000000000..16aa8ebcd --- /dev/null +++ b/zh-CN/custom_scalars.html @@ -0,0 +1,273 @@ + + + + + + 自定义标量 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

自定义标量

+

Async-graphql已经内置了绝大部分常用的标量类型,同时你也能自定义标量。

+

实现Async-graphql::Scalar即可自定义一个标量,你只需要实现一个解析函数和输出函数。

+

下面的例子定义一个 64 位整数标量,但它的输入输出都是字符串。 (Async-graphql已经内置了对 64 位整数的支持,正是采用字符串作为输入输出)

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+
+struct StringNumber(i64);
+
+#[Scalar]
+impl ScalarType for StringNumber {
+    fn parse(value: Value) -> InputValueResult<Self> {
+        if let Value::String(value) = &value {
+            // 解析整数
+            Ok(value.parse().map(StringNumber)?)
+        } else {
+            // 类型不匹配
+            Err(InputValueError::expected_type(value))
+        }
+    }
+
+    fn to_value(&self) -> Value {
+        Value::String(self.0.to_string())
+    }
+}
+
+}
+

使用scalar!宏定义标量

+

如果你的类型实现了serde :: Serializeserde :: Deserialize,那么可以使用此宏更简单地定义标量。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate serde;
+use async_graphql::*;
+use serde::{Serialize, Deserialize};
+use std::collections::HashMap;
+#[derive(Serialize, Deserialize)]
+struct MyValue {
+    a: i32,
+    b: HashMap<String, i32>,     
+}
+
+scalar!(MyValue);
+
+// 重命名为 `MV`.
+// scalar!(MyValue, "MV");
+
+// 重命名为 `MV` 并且添加描述。
+// scalar!(MyValue, "MV", "This is my value");
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/dataloader.html b/zh-CN/dataloader.html new file mode 100644 index 000000000..f72c1a5b8 --- /dev/null +++ b/zh-CN/dataloader.html @@ -0,0 +1,336 @@ + + + + + + 优化查询(解决 N+1 问题) - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

优化查询(解决 N+1 问题)

+

您是否注意到某些 GraphQL 查询需要执行数百个数据库查询,这些查询通常包含重复的数据,让我们来看看为什么以及如何修复它。

+

查询解析

+

想象一下,如果您有一个简单的查询,例如:

+
query { todos { users { name } } }
+
+

实现User的 resolver 代码如下:

+
struct User {
+    id: u64,
+}
+
+#[Object]
+impl User {
+    async fn name(&self, ctx: &Context<'_>) -> Result<String> {
+        let pool = ctx.data_unchecked::<Pool<Postgres>>();
+        let (name,): (String,) = sqlx::query_as("SELECT name FROM user WHERE id = $1")
+            .bind(self.id)
+            .fetch_one(pool)
+            .await?;
+        Ok(name)
+    }
+}
+

执行查询将调用Todos的 resolver,该 resolver 执行SELECT * FROM todo并返回 N 个Todo对象。然后对每个Todo对象同时调用User的 +resolver 执行SELECT name FROM user where id = $1

+

例如:

+
SELECT id, todo, user_id FROM todo
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+
+

执行了多次SELECT name FROM user WHERE id = $1,并且,大多数Todo对象都属于同一个用户,我们需要优化这些代码!

+

Dataloader

+

我们需要对查询分组,并且排除重复的查询。Dataloader就能完成这个工作,facebook 给出了一个请求范围的批处理和缓存解决方案。

+

下面是使用DataLoader来优化查询请求的例子:

+
use async_graphql::*;
+use async_graphql::dataloader::*;
+use itertools::Itertools;
+use std::sync::Arc;
+
+struct UserNameLoader {
+    pool: sqlx::Pool<Postgres>,
+}
+
+impl Loader<u64> for UserNameLoader {
+    type Value = String;
+    type Error = Arc<sqlx::Error>;
+
+    async fn load(&self, keys: &[u64]) -> Result<HashMap<u64, Self::Value>, Self::Error> {
+        let query = format!("SELECT name FROM user WHERE id IN ({})", keys.iter().join(","));
+        Ok(sqlx::query_as(query)
+            .fetch(&self.pool)
+            .map_ok(|name: String| name)
+            .map_err(Arc::new)
+            .try_collect().await?)
+    }
+}
+
+struct User {
+    id: u64,
+}
+
+#[Object]
+impl User {
+    async fn name(&self, ctx: &Context<'_>) -> Result<String> {
+        let loader = ctx.data_unchecked::<DataLoader<UserNameLoader>>();
+        let name: Option<String> = loader.load_one(self.id).await?;
+        name.ok_or_else(|| "Not found".into())
+    }
+}
+

要在 ctx 中获取 UserNameLoader,您必须将其和任务生成器(例如 async_std::task::spawn)注册到 Schema 中:

+
let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
+    .data(DataLoader::new(
+        UserNameLoader,
+        async_std::task::spawn, // 或者 `tokio::spawn`
+    ))
+    .finish();
+

最终只需要两个查询语句,就查询出了我们想要的结果!

+
SELECT id, todo, user_id FROM todo
+SELECT name FROM user WHERE id IN (1, 2, 3, 4)
+
+

同一个 Loader 支持多种数据类型

+

你可以为同一个Loader实现多种数据类型,就像下面这样:

+
struct PostgresLoader {
+    pool: sqlx::Pool<Postgres>,
+}
+
+impl Loader<UserId> for PostgresLoader {
+    type Value = User;
+    type Error = Arc<sqlx::Error>;
+
+    async fn load(&self, keys: &[UserId]) -> Result<HashMap<UserId, Self::Value>, Self::Error> {
+        // 从数据库中加载 User
+    }
+}
+
+impl Loader<TodoId> for PostgresLoader {
+    type Value = Todo;
+    type Error = sqlx::Error;
+
+    async fn load(&self, keys: &[TodoId]) -> Result<HashMap<TodoId, Self::Value>, Self::Error> {
+        // 从数据库中加载 Todo
+    }
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/default_value.html b/zh-CN/default_value.html new file mode 100644 index 000000000..3f7e3a18e --- /dev/null +++ b/zh-CN/default_value.html @@ -0,0 +1,289 @@ + + + + + + 默认值 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

默认值

+

你可以为输入值类型定义默认值,下面展示了在不同类型上默认值的定义方法。

+

对象字段参数

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+fn my_default() -> i32 {
+    30
+}
+
+#[Object]
+impl Query {
+    // value 参数的默认值为 0,它会调用 i32::default()
+    async fn test1(&self, #[graphql(default)] value: i32) -> i32 { todo!() }
+
+    // value 参数的默认值为 10
+    async fn test2(&self, #[graphql(default = 10)] value: i32) -> i32 { todo!() }
+
+    // value 参数的默认值使用 my_default 函数的返回结果,值为 30
+    async fn test3(&self, #[graphql(default_with = "my_default()")] value: i32) -> i32 { todo!() }
+}
+}
+

接口字段参数

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+fn my_default() -> i32 { 5 }
+struct MyObj;
+#[Object]
+impl MyObj {
+   async fn test1(&self, value: i32) -> i32 { todo!() }
+   async fn test2(&self, value: i32) -> i32 { todo!() }
+   async fn test3(&self, value: i32) -> i32 { todo!() }
+}
+use async_graphql::*;
+
+#[derive(Interface)]
+#[graphql(
+    field(name = "test1", ty = "i32", arg(name = "value", ty = "i32", default)),
+    field(name = "test2", ty = "i32", arg(name = "value", ty = "i32", default = 10)),
+    field(name = "test3", ty = "i32", arg(name = "value", ty = "i32", default_with = "my_default()")),
+)]
+enum MyInterface {
+    MyObj(MyObj),
+}
+}
+

输入对象 (InputObject)

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+fn my_default() -> i32 { 5 }
+use async_graphql::*;
+
+#[derive(InputObject)]
+struct MyInputObject {
+    #[graphql(default)]
+    value1: i32,
+
+    #[graphql(default = 10)]
+    value2: i32,
+
+    #[graphql(default_with = "my_default()")]
+    value3: i32,
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/define_complex_object.html b/zh-CN/define_complex_object.html new file mode 100644 index 000000000..ffc39fe4a --- /dev/null +++ b/zh-CN/define_complex_object.html @@ -0,0 +1,259 @@ + + + + + + 对象 (Object) - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

对象 (Object)

+

和简单对象不同,对象必须为所有的字段定义 Resolver 函数,Resolver 函数定义在 impl 块中。

+

一个 Resolver 函数必须是异步的,它的第一个参数必须是&self,第二个参数是可选的Context,接下来是字段的参数。

+

Resolver 函数用于计算字段的值,你可以执行一个数据库查询,并返回查询结果。函数的返回值是字段的类型,你也可以返回一个async_graphql::Result类型,这样能够返回一个错误,这个错误信息将输出到查询结果中。

+

在查询数据库时,你可能需要一个数据库连接池对象,这个对象是个全局的,你可以在创建 Schema 的时候,用SchemaBuilder::data函数设置Schema数据,用Context::data函数设置Context数据。下面的value_from_db字段展示了如何从Context中获取一个数据库连接。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+struct Data { pub name: String }
+struct DbConn {}
+impl DbConn {
+  fn query_something(&self, id: i64) -> std::result::Result<Data, String> { Ok(Data {name:"".into()})}
+}
+struct DbPool {}
+impl DbPool {
+  fn take(&self) -> DbConn { DbConn {} }    
+}
+use async_graphql::*;
+
+struct MyObject {
+    value: i32,
+}
+
+#[Object]
+impl MyObject {
+    async fn value(&self) -> String {
+        self.value.to_string()
+    }
+
+    async fn value_from_db(
+        &self,
+        ctx: &Context<'_>,
+        #[graphql(desc = "Id of object")] id: i64
+    ) -> Result<String> {
+        let conn = ctx.data::<DbPool>()?.take();
+        Ok(conn.query_something(id)?.name)
+    }
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/define_enum.html b/zh-CN/define_enum.html new file mode 100644 index 000000000..72e885e93 --- /dev/null +++ b/zh-CN/define_enum.html @@ -0,0 +1,283 @@ + + + + + + 枚举 (Enum) - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

枚举 (Enum)

+

定义枚举相当简单,直接给出一个例子。

+

Async-graphql 会自动把枚举项的名称转换为 GraphQL 标准的大写加下划线形式,你也可以用name属性自已定义名称。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+/// One of the films in the Star Wars Trilogy
+#[derive(Enum, Copy, Clone, Eq, PartialEq)]
+pub enum Episode {
+    /// Released in 1977.
+    NewHope,
+
+    /// Released in 1980.
+    Empire,
+
+    /// Released in 1983.
+    #[graphql(name="AAA")]
+    Jedi,
+}
+}
+

封装外部枚举类型

+

Rust 的 孤儿规则 要求特质或您要实现特质的类型必须在相同的板条箱中定义,因此你不能向 GraphQL 公开外部枚举类型。为了创建Enum类型,一种常见的解决方法是创建一个新的与现有远程枚举类型同等的枚举。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+mod remote_crate { pub enum RemoteEnum { A, B, C } }
+use async_graphql::*;
+
+/// Provides parity with a remote enum type
+#[derive(Enum, Copy, Clone, Eq, PartialEq)]
+pub enum LocalEnum {
+    A,
+    B,
+    C,
+}
+
+/// Conversion interface from remote type to our local GraphQL enum type
+impl From<remote_crate::RemoteEnum> for LocalEnum {
+    fn from(e: remote_crate::RemoteEnum) -> Self {
+        match e {
+            remote_crate::RemoteEnum::A => Self::A,
+            remote_crate::RemoteEnum::B => Self::B,
+            remote_crate::RemoteEnum::C => Self::C,
+        }
+    }
+}
+}
+

该过程很繁琐,需要多个步骤才能使本地枚举和远程枚举保持同步。Async_graphql提供了一个方便的功能,可在派生Enum之后通过附加属性生成 LocalEnum 的From <remote_crate::RemoteEnum>以及相反的From<LocalEnum> for remote_crate::RemoteEnum:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+mod remote_crate { pub enum RemoteEnum { A, B, C } }
+#[derive(Enum, Copy, Clone, Eq, PartialEq)]
+#[graphql(remote = "remote_crate::RemoteEnum")]
+enum LocalEnum {
+    A,
+    B,
+    C,
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/define_input_object.html b/zh-CN/define_input_object.html new file mode 100644 index 000000000..bce115d30 --- /dev/null +++ b/zh-CN/define_input_object.html @@ -0,0 +1,301 @@ + + + + + + 输入对象 (InputObject) - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

输入对象 (InputObject)

+

你可以定义一个对象作为参数类型,GraphQL 称之为Input Object,输入对象的定义方式和简单对象很像,不同的是,简单对象只能用于输出,而输入对象只能用于输入。

+

你也通过可选的#[graphql]属性来给字段添加描述,重命名。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+#[derive(SimpleObject)]
+struct User { a: i32 }
+use async_graphql::*;
+
+#[derive(InputObject)]
+struct Coordinate {
+    latitude: f64,
+    longitude: f64,
+}
+
+struct Mutation;
+
+#[Object]
+impl Mutation {
+    async fn users_at_location(&self, coordinate: Coordinate, radius: f64) -> Vec<User> {
+        // 将坐标写入数据库
+        // ...
+      todo!()
+    }
+}
+}
+

泛型

+

如果你希望其它类型能够重用InputObject,则可以定义泛型的InputObject,并指定具体的类型。

+

在下面的示例中,创建了两种InputObject类型:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(InputObject)]
+struct SomeType { a: i32 }
+#[derive(InputObject)]
+struct SomeOtherType { a: i32 }
+#[derive(InputObject)]
+#[graphql(concrete(name = "SomeName", params(SomeType)))]
+#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))]
+pub struct SomeGenericInput<T: InputType> {
+    field1: Option<T>,
+    field2: String
+}
+}
+

注意:每个泛型参数必须实现InputType,如上所示。

+

生成的 SDL 如下:

+
input SomeName {
+  field1: SomeType
+  field2: String!
+}
+
+input SomeOtherName {
+  field1: SomeOtherType
+  field2: String!
+}
+
+

在其它InputObject中使用具体的泛型类型:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(InputObject)]
+struct SomeType { a: i32 }
+#[derive(InputObject)]
+struct SomeOtherType { a: i32 }
+#[derive(InputObject)]
+#[graphql(concrete(name = "SomeName", params(SomeType)))]
+#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))]
+pub struct SomeGenericInput<T: InputType> {
+    field1: Option<T>,
+    field2: String
+}
+#[derive(InputObject)]
+pub struct YetAnotherInput {
+    a: SomeGenericInput<SomeType>,
+    b: SomeGenericInput<SomeOtherType>,
+}
+}
+

你可以将多个通用类型传递给params(),并用逗号分隔。

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/define_interface.html b/zh-CN/define_interface.html new file mode 100644 index 000000000..cae371552 --- /dev/null +++ b/zh-CN/define_interface.html @@ -0,0 +1,334 @@ + + + + + + 接口 (Interface) - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

接口 (Interface)

+

接口用于抽象具有特定字段集合的对象,Async-graphql内部实现实际上是一个包装器,包装器转发接口上定义的 Resolver 函数到实现该接口的对象,所以接口类型所包含的字段类型,参数都必须和实现该接口的对象完全匹配。

+

Async-graphql自动实现了对象到接口的转换,把一个对象类型转换为接口类型只需要调用Into::into

+

接口字段的name属性表示转发的 Resolver 函数,并且它将被转换为驼峰命名作为字段名称。 +如果你需要自定义 GraphQL 接口字段名称,可以同时使用namemethod属性。

+
    +
  • namemethod属性同时存在时,name是 GraphQL 接口字段名,而method是 Resolver 函数名。
  • +
  • 当只有name存在时,转换为驼峰命名后的name是 GraphQL 接口字段名,而name是 Resolver 函数名。
  • +
+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Circle {
+    radius: f32,
+}
+
+#[Object]
+impl Circle {
+    async fn area(&self) -> f32 {
+        std::f32::consts::PI * self.radius * self.radius
+    }
+
+    async fn scale(&self, s: f32) -> Shape {
+        Circle { radius: self.radius * s }.into()
+    }
+
+    #[graphql(name = "short_description")]
+    async fn short_description(&self) -> String {
+        "Circle".to_string()
+    }
+}
+
+struct Square {
+    width: f32,
+}
+
+#[Object]
+impl Square {
+    async fn area(&self) -> f32 {
+        self.width * self.width
+    }
+
+    async fn scale(&self, s: f32) -> Shape {
+        Square { width: self.width * s }.into()
+    }
+
+    #[graphql(name = "short_description")]
+    async fn short_description(&self) -> String {
+        "Square".to_string()
+    }
+}
+
+#[derive(Interface)]
+#[graphql(
+    field(name = "area", ty = "f32"),
+    field(name = "scale", ty = "Shape", arg(name = "s", ty = "f32")),
+    field(name = "short_description", method = "short_description", ty = "String")
+)]
+enum Shape {
+    Circle(Circle),
+    Square(Square),
+}
+}
+

手工注册接口类型

+

Async-graphql在初始化阶段从Schema开始遍历并注册所有被直接或者间接引用的类型,如果某个接口没有被引用到,那么它将不会存在于注册表中,就像下面的例子, +即使MyObject实现了MyInterface,但由于Schema中并没有引用MyInterface,类型注册表中将不会存在MyInterface类型的信息。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Interface)]
+#[graphql(
+    field(name = "name", ty = "String"),
+)]
+enum MyInterface {
+    MyObject(MyObject),
+}
+
+#[derive(SimpleObject)]
+struct MyObject {
+    name: String,
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn obj(&self) -> MyObject {
+        todo!()
+    }
+}
+
+type MySchema = Schema<Query, EmptyMutation, EmptySubscription>;
+}
+

你需要在构造 Schema 时手工注册MyInterface类型:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Interface)]
+#[graphql(field(name = "name", ty = "String"))]
+enum MyInterface { MyObject(MyObject) }
+#[derive(SimpleObject)]
+struct MyObject { name: String, }
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+
+Schema::build(Query, EmptyMutation, EmptySubscription)
+    .register_output_type::<MyInterface>()
+    .finish();
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/define_schema.html b/zh-CN/define_schema.html new file mode 100644 index 000000000..87c7c397f --- /dev/null +++ b/zh-CN/define_schema.html @@ -0,0 +1,223 @@ + + + + + + 定义模式 (Schema) - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

定义模式 (Schema)

+

在定义了基本的类型之后,需要定义一个模式把他们组合起来,模式由三种类型组成,查询对象,变更对象和订阅对象,其中变更对象和订阅对象是可选的。

+

当模式创建时,Async-graphql会遍历所有对象图,并注册所有类型。这意味着,如果定义了 GraphQL 对象但从未引用,那么此对象就不会暴露在模式中。

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/define_simple_object.html b/zh-CN/define_simple_object.html new file mode 100644 index 000000000..f605dc482 --- /dev/null +++ b/zh-CN/define_simple_object.html @@ -0,0 +1,328 @@ + + + + + + 简单对象 (SimpleObject) - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

简单对象 (SimpleObject)

+

简单对象是把 Rust 结构的所有字段都直接映射到 GraphQL 对象,不支持定义单独的 Resolver 函数。

+

下面的例子定义了一个名称为 MyObject 的对象,包含字段abc由于标记为#[graphql(skip)],所以不会映射到 GraphQL。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+#[derive(SimpleObject)]
+struct MyObject {
+    /// Value a
+    a: i32,
+    
+    /// Value b
+    b: i32,
+
+    #[graphql(skip)]
+    c: i32,
+}
+}
+

泛型

+

如果你希望其它类型能够重用SimpleObject,则可以定义泛型的SimpleObject,并指定具体的类型。

+

在下面的示例中,创建了两种SimpleObject类型:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct SomeType { a: i32 }
+#[derive(SimpleObject)]
+struct SomeOtherType { a: i32 }
+#[derive(SimpleObject)]
+#[graphql(concrete(name = "SomeName", params(SomeType)))]
+#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))]
+pub struct SomeGenericObject<T: OutputType> {
+    field1: Option<T>,
+    field2: String
+}
+}
+

注意:每个泛型参数必须实现OutputType,如上所示。

+

生成的 SDL 如下:

+
type SomeName {
+  field1: SomeType
+  field2: String!
+}
+
+type SomeOtherName {
+  field1: SomeOtherType
+  field2: String!
+}
+
+

在其它Object中使用具体的泛型类型:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct SomeType { a: i32 }
+#[derive(SimpleObject)]
+struct SomeOtherType { a: i32 }
+#[derive(SimpleObject)]
+#[graphql(concrete(name = "SomeName", params(SomeType)))]
+#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))]
+pub struct SomeGenericObject<T: OutputType> {
+    field1: Option<T>,
+    field2: String,
+}
+#[derive(SimpleObject)]
+pub struct YetAnotherObject {
+    a: SomeGenericObject<SomeType>,
+    b: SomeGenericObject<SomeOtherType>,
+}
+}
+

你可以将多个通用类型传递给params(),并用逗号分隔。

+

复杂字段

+

有时 GraphQL 对象的大多数字段仅返回结构成员的值,但是少数字段需要计算。通常我们使用Object宏来定义这样一个 GraphQL 对象。

+

ComplexObject宏可以更漂亮的完成这件事,我们可以使用SimpleObject宏来定义 +一些简单的字段,并使用ComplexObject宏来定义其他一些需要计算的字段。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+#[graphql(complex)] // 注意:如果你希望 ComplexObject 宏生效,complex 属性是必须的
+struct MyObj {
+    a: i32,
+    b: i32,
+}
+
+#[ComplexObject]
+impl MyObj {
+    async fn c(&self) -> i32 {
+        self.a + self.b     
+    }
+}
+}
+

同时用于输入和输出

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject, InputObject)]
+#[graphql(input_name = "MyObjInput")] // 注意:你必须用 input_name 属性为输入类型定义一个新的名称,否则将产生一个运行时错误。
+struct MyObj {
+    a: i32,
+    b: i32,
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/define_union.html b/zh-CN/define_union.html new file mode 100644 index 000000000..db2b7b35d --- /dev/null +++ b/zh-CN/define_union.html @@ -0,0 +1,320 @@ + + + + + + 联合 (Union) - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

联合 (Union)

+

联合的定义和接口非常像,但不允许定义字段。这两个类型的实现原理也差不多,对于Async-graphql来说,联合类型是接口类型的子集。

+

下面把接口定义的例子做一个小小的修改,去掉字段的定义。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Circle {
+    radius: f32,
+}
+
+#[Object]
+impl Circle {
+    async fn area(&self) -> f32 {
+        std::f32::consts::PI * self.radius * self.radius
+    }
+
+    async fn scale(&self, s: f32) -> Shape {
+        Circle { radius: self.radius * s }.into()
+    }
+}
+
+struct Square {
+    width: f32,
+}
+
+#[Object]
+impl Square {
+    async fn area(&self) -> f32 {
+        self.width * self.width
+    }
+
+    async fn scale(&self, s: f32) -> Shape {
+        Square { width: self.width * s }.into()
+    }
+}
+
+#[derive(Union)]
+enum Shape {
+    Circle(Circle),
+    Square(Square),
+}
+}
+

展平嵌套联合

+

GraphQL 的有个限制是Union类型内不能包含其它联合类型。所有成员必须为Object。 +位置支持嵌套Union,我们可以用#graphql(flatten),是它们合并到上级Union类型。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+#[derive(async_graphql::Union)]
+pub enum TopLevelUnion {
+    A(A),
+
+    // 除非我们使用 `flatten` 属性,否则将无法编译
+    #[graphql(flatten)]
+    B(B),
+}
+
+#[derive(async_graphql::SimpleObject)]
+pub struct A {
+    a: i32,
+    // ...
+}
+
+#[derive(async_graphql::Union)]
+pub enum B {
+    C(C),
+    D(D),
+}
+
+#[derive(async_graphql::SimpleObject)]
+pub struct C {
+    c: i32,
+    // ...
+}
+
+#[derive(async_graphql::SimpleObject)]
+pub struct D {
+    d: i32,
+    // ...
+}
+}
+

上面的示例将顶级Union转换为以下等效形式:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+#[derive(async_graphql::SimpleObject)]
+struct A { a: i32 }
+#[derive(async_graphql::SimpleObject)]
+struct C { c: i32 }
+#[derive(async_graphql::SimpleObject)]
+struct D { d: i32 }
+#[derive(async_graphql::Union)]
+pub enum TopLevelUnion {
+    A(A),
+    C(C),
+    D(D),
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/depth_and_complexity.html b/zh-CN/depth_and_complexity.html new file mode 100644 index 000000000..94d0b22bc --- /dev/null +++ b/zh-CN/depth_and_complexity.html @@ -0,0 +1,316 @@ + + + + + + 查询的深度和复杂度 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

查询的深度和复杂度

+

⚠️GraphQL 提供了非常灵活的查询方法,但在客户端上滥用复杂的查询可能造成风险,限制查询语句的深度和复杂度可以减轻这种风险。

+

昂贵的查询

+

考虑一种允许列出博客文章的架构。每个博客帖子也与其他帖子相关。

+
type Query {
+	posts(count: Int = 10): [Post!]!
+}
+
+type Post {
+	title: String!
+	text: String!
+	related(count: Int = 10): [Post!]!
+}
+
+

创建一个会引起很大响应的查询不是很困难:

+
{
+    posts(count: 100) {
+        related(count: 100) {
+            related(count: 100) {
+                related(count: 100) {
+                    title
+                }
+            }
+        }
+    }
+}
+
+

响应的大小随related字段的每个其他级别呈指数增长。幸运的是,Async-graphql提供了一种防止此类查询的方法。

+

限制查询的深度

+

查询的深度是字段嵌套的层数,下面是一个深度为3的查询。

+
{
+    a {
+        b {
+            c
+        }
+    }
+}
+
+

在创建Schema的时候可以限制深度,如果查询语句超过这个限制,则会出错并且返回Query is nested too deep.消息。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
+    .limit_depth(5) // 限制最大深度为 5
+    .finish();
+}
+

限制查询的复杂度

+

复杂度是查询语句中字段的数量,每个字段的复杂度默认为1,下面是一个复杂度为6的查询。

+
{
+    a b c {
+        d {
+            e f
+        }
+    }
+}
+
+

在创建Schema的时候可以限制复杂度,如果查询语句超过这个限制,则会出错并且返回Query is too complex.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
+    .limit_complexity(5) // 限制复杂度为 5
+    .finish();
+}
+

自定义字段的复杂度

+

针对非列表类型和列表类型的字段,有两种自定义复杂度的方法。 +下面的代码中,value字段的复杂度为5。而values字段的复杂度为count * child_complexitychild_complexity是一个特殊的变量,表示子查询的复杂度, +count是字段的参数,这个表达式用于计算values字段的复杂度,并且返回值的类型必须是usize

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+
+#[Object]
+impl Query {
+    #[graphql(complexity = 5)]
+    async fn value(&self) -> i32 {
+        todo!()
+    }
+
+    #[graphql(complexity = "count * child_complexity")]
+    async fn values(&self, count: usize) -> i32 {
+        todo!()
+    }
+}
+}
+

注意:计算复杂度是在验证阶段完成而不是在执行阶段,所以你不用担心超限的查询语句会导致查询只执行一部分。

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/derived_fields.html b/zh-CN/derived_fields.html new file mode 100644 index 000000000..6558aee04 --- /dev/null +++ b/zh-CN/derived_fields.html @@ -0,0 +1,312 @@ + + + + + + 派生字段 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

派生字段

+

有时两个字段有一样的查询逻辑,仅仅是输出的类型不同,在 async-graphql 中,你可以为它创建派生字段。

+

在以下例子中,你已经有一个duration_rfc2822字段输出RFC2822格式的时间格式,然后复用它派生一个新的date_rfc3339字段。

+
#![allow(unused)]
+fn main() {
+extern crate chrono;
+use chrono::Utc;
+extern crate async_graphql;
+use async_graphql::*;
+struct DateRFC3339(chrono::DateTime<Utc>);
+struct DateRFC2822(chrono::DateTime<Utc>);
+
+#[Scalar]
+impl ScalarType for DateRFC3339 {
+  fn parse(value: Value) -> InputValueResult<Self> { todo!() } 
+
+  fn to_value(&self) -> Value {
+    Value::String(self.0.to_rfc3339())
+  }
+}
+
+#[Scalar]
+impl ScalarType for DateRFC2822 {
+  fn parse(value: Value) -> InputValueResult<Self> { todo!() } 
+
+  fn to_value(&self) -> Value {
+    Value::String(self.0.to_rfc2822())
+  }
+}
+
+impl From<DateRFC2822> for DateRFC3339 {
+    fn from(value: DateRFC2822) -> Self {
+      DateRFC3339(value.0)
+    }
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    #[graphql(derived(name = "date_rfc3339", into = "DateRFC3339"))]
+    async fn duration_rfc2822(&self, arg: String) -> DateRFC2822 {
+        todo!()
+    }
+}
+}
+

它将呈现为如下 GraphQL:

+
type Query {
+	duration_rfc2822(arg: String): DateRFC2822!
+	duration_rfc3339(arg: String): DateRFC3339!
+}
+
+

包装类型

+

因为 孤儿规则,以下代码无法通过编译:

+
impl From<Vec<U>> for Vec<T> {
+  ...
+}
+

因此,你将无法为现有的包装类型结构(如VecOption)生成派生字段。 +但是当你为 T 实现了 From<U> 后,你可以为 Vec<T> 实现 From<Vec<U>>,为 Option<T> 实现 From<Option<U>>. +使用 with 参数来定义一个转换函数,而不是用 Into::into

+

Example

+
#![allow(unused)]
+fn main() {
+extern crate serde;
+use serde::{Serialize, Deserialize};
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Serialize, Deserialize, Clone)]
+struct ValueDerived(String);
+
+#[derive(Serialize, Deserialize, Clone)]
+struct ValueDerived2(String);
+
+scalar!(ValueDerived);
+scalar!(ValueDerived2);
+
+impl From<ValueDerived> for ValueDerived2 {
+    fn from(value: ValueDerived) -> Self {
+        ValueDerived2(value.0)
+    }
+}
+
+fn option_to_option<T, U: From<T>>(value: Option<T>) -> Option<U> {
+    value.map(|x| x.into())
+}
+
+#[derive(SimpleObject)]
+struct TestObj {
+    #[graphql(derived(owned, name = "value2", into = "Option<ValueDerived2>", with = "option_to_option"))]
+    pub value1: Option<ValueDerived>,
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/elasticlunr.min.js b/zh-CN/elasticlunr.min.js new file mode 100644 index 000000000..94b20dd2e --- /dev/null +++ b/zh-CN/elasticlunr.min.js @@ -0,0 +1,10 @@ +/** + * elasticlunr - http://weixsong.github.io + * Lightweight full-text search engine in Javascript for browser search and offline search. - 0.9.5 + * + * Copyright (C) 2017 Oliver Nightingale + * Copyright (C) 2017 Wei Song + * MIT Licensed + * @license + */ +!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o + + + + + 错误扩展 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

# 错误扩展

+

引用 graphql-spec

+
+

GraphQL 服务可以通过扩展提供错误的附加条目。 +该条目(如果设置)必须是一个映射作为其值,用于附加错误的其它信息。

+
+

示例

+

我建议您查看此 错误扩展示例 作为快速入门。

+

一般概念

+

Async-graphql中,所有面向用户的错误都强制转换为Error类型,默认情况下会提供 +由std:::fmt::Display暴露的错误消息。但是,Error实际上提供了一个额外的可以扩展错误的信息。

+

Resolver 函数类似这样:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query {
+async fn parse_with_extensions(&self) -> Result<i32, Error> {
+    Err(Error::new("MyMessage").extend_with(|_, e| e.set("details", "CAN_NOT_FETCH")))
+}
+}
+}
+

然后可以返回如下响应:

+
{
+  "errors": [
+    {
+      "message": "MyMessage",
+      "locations": [ ... ],
+      "path": [ ... ],
+      "extensions": {
+        "details": "CAN_NOT_FETCH",
+      }
+    }
+  ]
+}
+
+

ErrorExtensions

+

手动构造新的Error很麻烦。这就是为什么Async-graphql提供 +两个方便特性,可将您的错误转换为适当的Error扩展。

+

扩展任何错误的最简单方法是对错误调用extend_with。 +这将把任何错误转换为具有给定扩展信息的Error

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+use std::num::ParseIntError;
+#[Object]
+impl Query {
+async fn parse_with_extensions(&self) -> Result<i32> {
+     Ok("234a"
+         .parse()
+         .map_err(|err: ParseIntError| err.extend_with(|_err, e| e.set("code", 404)))?)
+}
+}
+}
+

为自定义错误实现 ErrorExtensions

+

你也可以给自己的错误类型实现ErrorExtensions:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate thiserror;
+use async_graphql::*;
+#[derive(Debug, thiserror::Error)]
+pub enum MyError {
+    #[error("Could not find resource")]
+    NotFound,
+
+    #[error("ServerError")]
+    ServerError(String),
+
+    #[error("No Extensions")]
+    ErrorWithoutExtensions,
+}
+
+impl ErrorExtensions for MyError {
+    // lets define our base extensions
+    fn extend(&self) -> Error {
+        Error::new(format!("{}", self)).extend_with(|err, e| 
+            match self {
+              MyError::NotFound => e.set("code", "NOT_FOUND"),
+              MyError::ServerError(reason) => e.set("reason", reason.clone()),
+              MyError::ErrorWithoutExtensions => {}
+          })
+    }
+}
+}
+

您只需要对错误调用extend即可将错误与其提供的扩展信息一起传递,或者通过extend_with进一步扩展错误信息。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate thiserror;
+use async_graphql::*;
+#[derive(Debug, thiserror::Error)]
+pub enum MyError {
+    #[error("Could not find resource")]
+    NotFound,
+
+    #[error("ServerError")]
+    ServerError(String),
+
+    #[error("No Extensions")]
+    ErrorWithoutExtensions,
+}
+struct Query;
+#[Object]
+impl Query {
+async fn parse_with_extensions_result(&self) -> Result<i32> {
+    // Err(MyError::NotFound.extend())
+    // OR
+    Err(MyError::NotFound.extend_with(|_, e| e.set("on_the_fly", "some_more_info")))
+}
+}
+}
+
{
+  "errors": [
+    {
+      "message": "NotFound",
+      "locations": [ ... ],
+      "path": [ ... ],
+      "extensions": {
+        "code": "NOT_FOUND",
+        "on_the_fly": "some_more_info"
+      }
+    }
+  ]
+}
+
+

ResultExt

+

这个特质使您可以直接在结果上调用extend_err。因此上面的代码不再那么冗长。

+
// @todo figure out why this example does not compile!
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query {
+async fn parse_with_extensions(&self) -> Result<i32> {
+     Ok("234a"
+         .parse()
+         .extend_err(|_, e| e.set("code", 404))?)
+}
+}
+

链式调用

+

由于对所有&E where E: std::fmt::Display实现了ErrorExtensionsResultsExt,我们可以将扩展链接在一起。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query {
+async fn parse_with_extensions(&self) -> Result<i32> {
+    match "234a".parse() {
+        Ok(n) => Ok(n),
+        Err(e) => Err(e
+            .extend_with(|_, e| e.set("code", 404))
+            .extend_with(|_, e| e.set("details", "some more info.."))
+            // keys may also overwrite previous keys...
+            .extend_with(|_, e| e.set("code", 500))),
+    }
+}
+}
+}
+

响应:

+
{
+  "errors": [
+    {
+      "message": "MyMessage",
+      "locations": [ ... ],
+      "path": [ ... ],
+      "extensions": {
+      	"details": "some more info...",
+        "code": 500,
+      }
+    }
+  ]
+}
+
+

缺陷

+

Rust 的稳定版本还未提供特化功能,这就是为什么ErrorExtensions&E where E: std::fmt::Display实现,代替E:std::fmt::Display通过提供一些特化功能。

+

Autoref-based stable specialization.

+

缺点是下面的代码不能编译:

+
async fn parse_with_extensions_result(&self) -> Result<i32> {
+    // the trait `error::ErrorExtensions` is not implemented
+    // for `std::num::ParseIntError`
+    "234a".parse().extend_err(|_, e| e.set("code", 404))
+}
+

但这可以通过编译:

+
async fn parse_with_extensions_result(&self) -> Result<i32> {
+    // does work because ErrorExtensions is implemented for &ParseIntError
+    "234a"
+      .parse()
+      .map_err(|ref e: ParseIntError| e.extend_with(|_, e| e.set("code", 404)))
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/error_handling.html b/zh-CN/error_handling.html new file mode 100644 index 000000000..e13f1034f --- /dev/null +++ b/zh-CN/error_handling.html @@ -0,0 +1,242 @@ + + + + + + 错误处理 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

错误处理

+

Resolver 函数可以返回一个 Result 类型,以下是 Result 的定义:

+
type Result<T> = std::result::Result<T, Error>;
+

任何错误都能够被转换为Error,并且你还能扩展标准的错误信息。

+

下面是一个例子,解析一个输入的字符串到整数,当解析失败时返回错误,并且附加额外的错误信息。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use std::num::ParseIntError;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn parse_with_extensions(&self, input: String) -> Result<i32> {
+        Ok("234a"
+            .parse()
+            .map_err(|err: ParseIntError| err.extend_with(|_, e| e.set("code", 400)))?)
+    }
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/extensions.html b/zh-CN/extensions.html new file mode 100644 index 000000000..6c4d82200 --- /dev/null +++ b/zh-CN/extensions.html @@ -0,0 +1,222 @@ + + + + + + 扩展 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

扩展

+

async-graphql 允许你不修改核心代码就能扩展它功能。

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/extensions_available.html b/zh-CN/extensions_available.html new file mode 100644 index 000000000..7d699dd26 --- /dev/null +++ b/zh-CN/extensions_available.html @@ -0,0 +1,258 @@ + + + + + + 可用的扩展列表 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

可用的扩展列表

+

async-graphql 中有很多可用的扩展用于增强你的 GraphQL 服务器。

+

Analyzer

+

Available in the repository

+

Analyzer 扩展将在每个响应的扩展中输出 complexitydepth 字段。

+

Apollo Persisted Queries

+

Available in the repository

+

要提高大型查询的性能,你可以启用此扩展,每个查询语句都将与一个唯一 ID 相关联,因此客户端可以直接发送此 ID 查询以减少请求的大小。

+

这个扩展不会强迫你使用一些缓存策略,你可以选择你想要的缓存策略,你只需要实现 CacheStorage trait:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[async_trait::async_trait]
+pub trait CacheStorage: Send + Sync + Clone + 'static {
+    /// Load the query by `key`.
+    async fn get(&self, key: String) -> Option<String>;
+    /// Save the query by `key`.
+    async fn set(&self, key: String, query: String);
+}
+}
+

References: Apollo doc - Persisted Queries

+

Apollo Tracing

+

Available in the repository

+

Apollo Tracing 扩展用于在响应中包含此查询分析数据。此扩展程序遵循旧的且现已弃用的 Apollo Tracing Spec 。 +如果你想支持更新的 Apollo Reporting Protocol,推荐使用 async-graphql Apollo studio extension

+

Apollo Studio

+

Available at async-graphql/async_graphql_apollo_studio_extension

+

async-graphql 提供了实现官方 Apollo Specification 的扩展,位于 async-graphql-extension- apollo-tracingcrates.io

+

Logger

+

Available in the repository

+

Logger 是一个简单的扩展,允许你向 async-graphql 添加一些日志记录功能。这也是学习如何创建自己的扩展的一个很好的例子。

+

OpenTelemetry

+

Available in the repository

+

OpenTelemetry 扩展提供 opentelemetry crate 的集成,以允许你的应用程序从 async-graphql 捕获分布式跟踪和指标。

+

Tracing

+

Available in the repository

+

Tracing 扩展提供 tracing crate 的集成,允许您向 async-graphql 添加一些跟踪功能,有点像Logger 扩展。

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/extensions_inner_working.html b/zh-CN/extensions_inner_working.html new file mode 100644 index 000000000..10939ac3f --- /dev/null +++ b/zh-CN/extensions_inner_working.html @@ -0,0 +1,409 @@ + + + + + + 扩展如何工作 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

如何定义扩展

+

async-graphql 扩展是通过实现 Extension trait 来定义的。 Extension trait 允许你将自定义代码插入到执行 GraphQL 查询的步骤中。

+

Extensions 很像来自其他框架的中间件,使用它们时要小心:当你使用扩展时它对每个 GraphQL 请求生效

+

一句话解释什么是中间件

+

让我们了解什么是中间件:

+
async fn middleware(&self, ctx: &ExtensionContext<'_>, next: NextMiddleware<'_>) -> MiddlewareResult {
+  // 你的中间件代码
+
+  /*
+   * 调用 next.run 函数执行下个中间件的逻辑
+   */
+  next.run(ctx).await
+}
+

如你所见,middleware 只是在末尾调用 next 函数的函数。但我们也可以在开头使用 next.run 来实现中间件。这就是它变得棘手的地方:根据你放置逻辑的位置以及next.run调用的位置,你的逻辑将不会具有相同的执行顺序。

+

根据你代码,你需要在 next.run 调用之前或之后处理它。如果你需要更多关于中间件的信息,网上有很多。

+

查询的处理

+

查询的每个阶段都有回调,你将能够基于这些回调创建扩展。

+

请求

+

首先,当我们收到一个请求时,如果它不是订阅,第一个被调用的函数将是 request,它在传入请求时调用,并输出结果给客户端。

+

Default implementation for request:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
+    next.run(ctx).await
+}
+}
+}
+

根据你放置逻辑代码的位置,它将在正在查询执行的开头或结尾执行。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
+    // 此处的代码将在执行 prepare_request 之前运行。
+    let result = next.run(ctx).await;
+    // 此处的代码将在把结果发送给客户端之前执行
+    result
+}
+}
+}
+

准备查询

+

request 之后,将调用prepare_request,你可以在此处对请求做一些转换。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+async fn prepare_request(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    request: Request,
+    next: NextPrepareRequest<'_>,
+) -> ServerResult<Request> {
+    // 此处的代码在 prepare_request 之前执行
+    let result = next.run(ctx, request).await;
+    // 此处的代码在 prepare_request 之后执行
+    result
+}
+}
+}
+

解析查询

+

parse_query 将解析查询语句并生成 GraphQL ExecutableDocument,并且检查查询是否遵循 GraphQL 规范。通常,async-graphql 遵循最后一个稳定的规范(October2021)。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+use async_graphql::parser::types::ExecutableDocument;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+/// Called at parse query.
+async fn parse_query(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    // The raw query
+    query: &str,
+    // The variables
+    variables: &Variables,
+    next: NextParseQuery<'_>,
+) -> ServerResult<ExecutableDocument> {
+    next.run(ctx, query, variables).await
+}
+}
+}
+

校验

+

validation 步骤将执行查询校验(取决于你指定的 validation_mode),并向客户端提供有关查询无效的原因。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+/// Called at validation query.
+async fn validation(
+  &self,
+  ctx: &ExtensionContext<'_>,
+  next: NextValidation<'_>,
+) -> Result<ValidationResult, Vec<ServerError>> {
+  next.run(ctx).await
+}
+}
+}
+

执行

+

execution 步骤是一个很大的步骤,它将并发执行Query,或者顺序执行Mutation

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+/// Called at execute query.
+async fn execute(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    operation_name: Option<&str>,
+    next: NextExecute<'_>,
+) -> Response {
+    // 此处的代码在执行完整查询之前执行
+    let result = next.run(ctx, operation_name).await;
+    // 此处的代码在执行完整查询之后执行
+    result
+}
+}
+}
+

resolve

+

为每个字段执行resolve.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware { 
+/// Called at resolve field.
+async fn resolve(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    info: ResolveInfo<'_>,
+    next: NextResolve<'_>,
+) -> ServerResult<Option<Value>> {
+    // resolve 字段之前
+    let result = next.run(ctx, info).await;
+    // resolve 字段之后
+    result
+}
+}
+}
+

订阅

+

subscribe的行为和request很像,只是专门用于订阅查询。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+use futures_util::stream::BoxStream;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+/// Called at subscribe request.
+fn subscribe<'s>(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    stream: BoxStream<'s, Response>,
+    next: NextSubscribe<'_>,
+) -> BoxStream<'s, Response> {
+    next.run(ctx, stream)
+}
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/favicon.png b/zh-CN/favicon.png new file mode 100644 index 000000000..a5b1aa16c Binary files /dev/null and b/zh-CN/favicon.png differ diff --git a/zh-CN/favicon.svg b/zh-CN/favicon.svg new file mode 100644 index 000000000..90e0ea58b --- /dev/null +++ b/zh-CN/favicon.svg @@ -0,0 +1,22 @@ + + + + + diff --git a/zh-CN/field_guard.html b/zh-CN/field_guard.html new file mode 100644 index 000000000..6fd535584 --- /dev/null +++ b/zh-CN/field_guard.html @@ -0,0 +1,309 @@ + + + + + + 字段守卫 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

字段守卫 (Field Guard)

+

你可以为Object, SimpleObject, ComplexObjectSubscription的字段定义守卫,它将在调用字段的 Resolver 函数前执行,如果失败则返回一个错误。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Eq, PartialEq, Copy, Clone)]
+enum Role {
+    Admin,
+    Guest,
+}
+
+struct RoleGuard {
+    role: Role,
+}
+
+impl RoleGuard {
+    fn new(role: Role) -> Self {
+        Self { role }
+    }
+}
+
+impl Guard for RoleGuard {
+    async fn check(&self, ctx: &Context<'_>) -> Result<()> {
+        if ctx.data_opt::<Role>() == Some(&self.role) {
+            Ok(())
+        } else {
+            Err("Forbidden".into())
+        }
+    }
+}
+}
+

guard属性使用它:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Eq, PartialEq, Copy, Clone)]
+enum Role { Admin, Guest, }
+struct RoleGuard { role: Role, }
+impl RoleGuard { fn new(role: Role) -> Self { Self { role } } }
+impl Guard for RoleGuard { async fn check(&self, ctx: &Context<'_>) -> Result<()> { todo!() } }
+#[derive(SimpleObject)]
+struct Query {
+    /// 只允许 Admin 访问
+    #[graphql(guard = "RoleGuard::new(Role::Admin)")]
+    value1: i32,
+    /// 允许 Admin 或者 Guest 访问
+    #[graphql(guard = "RoleGuard::new(Role::Admin).or(RoleGuard::new(Role::Guest))")]
+    value2: i32,
+}
+}
+

从参数中获取值

+

有时候守卫需要从字段参数中获取值,你需要像下面这样在创建守卫时传递该参数值:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct EqGuard {
+    expect: i32,
+    actual: i32,
+}
+
+impl EqGuard {
+    fn new(expect: i32, actual: i32) -> Self {
+        Self { expect, actual }
+    }
+}
+
+impl Guard for EqGuard {
+    async fn check(&self, _ctx: &Context<'_>) -> Result<()> {
+        if self.expect != self.actual {
+            Err("Forbidden".into())
+        } else {
+            Ok(())
+        }
+    }
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    #[graphql(guard = "EqGuard::new(100, value)")]
+    async fn get(&self, value: i32) -> i32 {
+        value
+    }
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/fonts/OPEN-SANS-LICENSE.txt b/zh-CN/fonts/OPEN-SANS-LICENSE.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/zh-CN/fonts/OPEN-SANS-LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/zh-CN/fonts/SOURCE-CODE-PRO-LICENSE.txt b/zh-CN/fonts/SOURCE-CODE-PRO-LICENSE.txt new file mode 100644 index 000000000..366206f54 --- /dev/null +++ b/zh-CN/fonts/SOURCE-CODE-PRO-LICENSE.txt @@ -0,0 +1,93 @@ +Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/zh-CN/fonts/fonts.css b/zh-CN/fonts/fonts.css new file mode 100644 index 000000000..698e1e19e --- /dev/null +++ b/zh-CN/fonts/fonts.css @@ -0,0 +1,100 @@ +/* Open Sans is licensed under the Apache License, Version 2.0. See http://www.apache.org/licenses/LICENSE-2.0 */ +/* Source Code Pro is under the Open Font License. See https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL */ + +/* open-sans-300 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + src: local('Open Sans Light'), local('OpenSans-Light'), + url('../fonts/open-sans-v17-all-charsets-300.woff2') format('woff2'); +} + +/* open-sans-300italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + src: local('Open Sans Light Italic'), local('OpenSans-LightItalic'), + url('../fonts/open-sans-v17-all-charsets-300italic.woff2') format('woff2'); +} + +/* open-sans-regular - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + src: local('Open Sans Regular'), local('OpenSans-Regular'), + url('../fonts/open-sans-v17-all-charsets-regular.woff2') format('woff2'); +} + +/* open-sans-italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + src: local('Open Sans Italic'), local('OpenSans-Italic'), + url('../fonts/open-sans-v17-all-charsets-italic.woff2') format('woff2'); +} + +/* open-sans-600 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), + url('../fonts/open-sans-v17-all-charsets-600.woff2') format('woff2'); +} + +/* open-sans-600italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + src: local('Open Sans SemiBold Italic'), local('OpenSans-SemiBoldItalic'), + url('../fonts/open-sans-v17-all-charsets-600italic.woff2') format('woff2'); +} + +/* open-sans-700 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 700; + src: local('Open Sans Bold'), local('OpenSans-Bold'), + url('../fonts/open-sans-v17-all-charsets-700.woff2') format('woff2'); +} + +/* open-sans-700italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 700; + src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'), + url('../fonts/open-sans-v17-all-charsets-700italic.woff2') format('woff2'); +} + +/* open-sans-800 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 800; + src: local('Open Sans ExtraBold'), local('OpenSans-ExtraBold'), + url('../fonts/open-sans-v17-all-charsets-800.woff2') format('woff2'); +} + +/* open-sans-800italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 800; + src: local('Open Sans ExtraBold Italic'), local('OpenSans-ExtraBoldItalic'), + url('../fonts/open-sans-v17-all-charsets-800italic.woff2') format('woff2'); +} + +/* source-code-pro-500 - latin_vietnamese_latin-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Source Code Pro'; + font-style: normal; + font-weight: 500; + src: url('../fonts/source-code-pro-v11-all-charsets-500.woff2') format('woff2'); +} diff --git a/zh-CN/fonts/open-sans-v17-all-charsets-300.woff2 b/zh-CN/fonts/open-sans-v17-all-charsets-300.woff2 new file mode 100644 index 000000000..9f51be370 Binary files /dev/null and b/zh-CN/fonts/open-sans-v17-all-charsets-300.woff2 differ diff --git a/zh-CN/fonts/open-sans-v17-all-charsets-300italic.woff2 b/zh-CN/fonts/open-sans-v17-all-charsets-300italic.woff2 new file mode 100644 index 000000000..2f5454484 Binary files /dev/null and b/zh-CN/fonts/open-sans-v17-all-charsets-300italic.woff2 differ diff --git a/zh-CN/fonts/open-sans-v17-all-charsets-600.woff2 b/zh-CN/fonts/open-sans-v17-all-charsets-600.woff2 new file mode 100644 index 000000000..f503d558d Binary files /dev/null and b/zh-CN/fonts/open-sans-v17-all-charsets-600.woff2 differ diff --git a/zh-CN/fonts/open-sans-v17-all-charsets-600italic.woff2 b/zh-CN/fonts/open-sans-v17-all-charsets-600italic.woff2 new file mode 100644 index 000000000..c99aabe80 Binary files /dev/null and b/zh-CN/fonts/open-sans-v17-all-charsets-600italic.woff2 differ diff --git a/zh-CN/fonts/open-sans-v17-all-charsets-700.woff2 b/zh-CN/fonts/open-sans-v17-all-charsets-700.woff2 new file mode 100644 index 000000000..421a1ab25 Binary files /dev/null and b/zh-CN/fonts/open-sans-v17-all-charsets-700.woff2 differ diff --git a/zh-CN/fonts/open-sans-v17-all-charsets-700italic.woff2 b/zh-CN/fonts/open-sans-v17-all-charsets-700italic.woff2 new file mode 100644 index 000000000..12ce3d20d Binary files /dev/null and b/zh-CN/fonts/open-sans-v17-all-charsets-700italic.woff2 differ diff --git a/zh-CN/fonts/open-sans-v17-all-charsets-800.woff2 b/zh-CN/fonts/open-sans-v17-all-charsets-800.woff2 new file mode 100644 index 000000000..c94a223b0 Binary files /dev/null and b/zh-CN/fonts/open-sans-v17-all-charsets-800.woff2 differ diff --git a/zh-CN/fonts/open-sans-v17-all-charsets-800italic.woff2 b/zh-CN/fonts/open-sans-v17-all-charsets-800italic.woff2 new file mode 100644 index 000000000..eed7d3c63 Binary files /dev/null and b/zh-CN/fonts/open-sans-v17-all-charsets-800italic.woff2 differ diff --git a/zh-CN/fonts/open-sans-v17-all-charsets-italic.woff2 b/zh-CN/fonts/open-sans-v17-all-charsets-italic.woff2 new file mode 100644 index 000000000..398b68a08 Binary files /dev/null and b/zh-CN/fonts/open-sans-v17-all-charsets-italic.woff2 differ diff --git a/zh-CN/fonts/open-sans-v17-all-charsets-regular.woff2 b/zh-CN/fonts/open-sans-v17-all-charsets-regular.woff2 new file mode 100644 index 000000000..8383e94c6 Binary files /dev/null and b/zh-CN/fonts/open-sans-v17-all-charsets-regular.woff2 differ diff --git a/zh-CN/fonts/source-code-pro-v11-all-charsets-500.woff2 b/zh-CN/fonts/source-code-pro-v11-all-charsets-500.woff2 new file mode 100644 index 000000000..722245682 Binary files /dev/null and b/zh-CN/fonts/source-code-pro-v11-all-charsets-500.woff2 differ diff --git a/zh-CN/highlight.css b/zh-CN/highlight.css new file mode 100644 index 000000000..352c79b96 --- /dev/null +++ b/zh-CN/highlight.css @@ -0,0 +1,83 @@ +/* + * An increased contrast highlighting scheme loosely based on the + * "Base16 Atelier Dune Light" theme by Bram de Haan + * (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) + * Original Base16 color scheme by Chris Kempson + * (https://github.com/chriskempson/base16) + */ + +/* Comment */ +.hljs-comment, +.hljs-quote { + color: #575757; +} + +/* Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-attr, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #d70025; +} + +/* Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #b21e00; +} + +/* Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #008200; +} + +/* Blue */ +.hljs-title, +.hljs-section { + color: #0030f2; +} + +/* Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #9d00ec; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f6f7f6; + color: #000; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-addition { + color: #22863a; + background-color: #f0fff4; +} + +.hljs-deletion { + color: #b31d28; + background-color: #ffeef0; +} diff --git a/zh-CN/highlight.js b/zh-CN/highlight.js new file mode 100644 index 000000000..18d24345b --- /dev/null +++ b/zh-CN/highlight.js @@ -0,0 +1,54 @@ +/* + Highlight.js 10.1.1 (93fd0d73) + License: BSD-3-Clause + Copyright (c) 2006-2020, Ivan Sagalaev +*/ +var hljs=function(){"use strict";function e(n){Object.freeze(n);var t="function"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!Object.hasOwnProperty.call(n,r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r])})),n}class n{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data}ignoreMatch(){this.ignore=!0}}function t(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function r(e,...n){var t={};for(const n in e)t[n]=e[n];return n.forEach((function(e){for(const n in e)t[n]=e[n]})),t}function a(e){return e.nodeName.toLowerCase()}var i=Object.freeze({__proto__:null,escapeHTML:t,inherit:r,nodeStream:function(e){var n=[];return function e(t,r){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?r+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:r,node:i}),r=e(i,r),a(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:r,node:i}));return r}(e,0),n},mergeStreams:function(e,n,r){var i=0,s="",o=[];function l(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function u(e){s+=""}function d(e){("start"===e.event?c:u)(e.node)}for(;e.length||n.length;){var g=l();if(s+=t(r.substring(i,g[0].offset)),i=g[0].offset,g===e){o.reverse().forEach(u);do{d(g.splice(0,1)[0]),g=l()}while(g===e&&g.length&&g[0].offset===i);o.reverse().forEach(c)}else"start"===g[0].event?o.push(g[0].node):o.pop(),d(g.splice(0,1)[0])}return s+t(r.substr(i))}});const s="",o=e=>!!e.kind;class l{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=t(e)}openNode(e){if(!o(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){o(e)&&(this.buffer+=s)}value(){return this.buffer}span(e){this.buffer+=``}}class c{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every(e=>"string"==typeof e)?e.children=[e.children.join("")]:e.children.forEach(e=>{c._collapse(e)}))}}class u extends c{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){const t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new l(this,this.options).value()}finalize(){return!0}}function d(e){return e?"string"==typeof e?e:e.source:null}const g="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",h={begin:"\\\\[\\s\\S]",relevance:0},f={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[h]},p={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[h]},b={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},m=function(e,n,t={}){var a=r({className:"comment",begin:e,end:n,contains:[]},t);return a.contains.push(b),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a},v=m("//","$"),x=m("/\\*","\\*/"),E=m("#","$");var _=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:g,BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const n=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map(e=>d(e)).join("")}(n,/.*\b/,e.binary,/\b.*/)),r({className:"meta",begin:n,end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)},BACKSLASH_ESCAPE:h,APOS_STRING_MODE:f,QUOTE_STRING_MODE:p,PHRASAL_WORDS_MODE:b,COMMENT:m,C_LINE_COMMENT_MODE:v,C_BLOCK_COMMENT_MODE:x,HASH_COMMENT_MODE:E,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:g,relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[h,{begin:/\[/,end:/\]/,relevance:0,contains:[h]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{n.data._beginMatch!==e[1]&&n.ignoreMatch()}})}}),N="of and for in not or if then".split(" ");function w(e,n){return n?+n:function(e){return N.includes(e.toLowerCase())}(e)?0:1}const R=t,y=r,{nodeStream:k,mergeStreams:O}=i,M=Symbol("nomatch");return function(t){var a=[],i={},s={},o=[],l=!0,c=/(^(<[^>]+>|\t|)+|\n)/gm,g="Could not find the language '{}', did you forget to load/include a language module?";const h={disableAutodetect:!0,name:"Plain text",contains:[]};var f={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:u};function p(e){return f.noHighlightRe.test(e)}function b(e,n,t,r){var a={code:n,language:e};S("before:highlight",a);var i=a.result?a.result:m(a.language,a.code,t,r);return i.code=a.code,S("after:highlight",i),i}function m(e,t,a,s){var o=t;function c(e,n){var t=E.case_insensitive?n[0].toLowerCase():n[0];return Object.prototype.hasOwnProperty.call(e.keywords,t)&&e.keywords[t]}function u(){null!=y.subLanguage?function(){if(""!==A){var e=null;if("string"==typeof y.subLanguage){if(!i[y.subLanguage])return void O.addText(A);e=m(y.subLanguage,A,!0,k[y.subLanguage]),k[y.subLanguage]=e.top}else e=v(A,y.subLanguage.length?y.subLanguage:null);y.relevance>0&&(I+=e.relevance),O.addSublanguage(e.emitter,e.language)}}():function(){if(!y.keywords)return void O.addText(A);let e=0;y.keywordPatternRe.lastIndex=0;let n=y.keywordPatternRe.exec(A),t="";for(;n;){t+=A.substring(e,n.index);const r=c(y,n);if(r){const[e,a]=r;O.addText(t),t="",I+=a,O.addKeyword(n[0],e)}else t+=n[0];e=y.keywordPatternRe.lastIndex,n=y.keywordPatternRe.exec(A)}t+=A.substr(e),O.addText(t)}(),A=""}function h(e){return e.className&&O.openNode(e.className),y=Object.create(e,{parent:{value:y}})}function p(e){return 0===y.matcher.regexIndex?(A+=e[0],1):(L=!0,0)}var b={};function x(t,r){var i=r&&r[0];if(A+=t,null==i)return u(),0;if("begin"===b.type&&"end"===r.type&&b.index===r.index&&""===i){if(A+=o.slice(r.index,r.index+1),!l){const n=Error("0 width match regex");throw n.languageName=e,n.badRule=b.rule,n}return 1}if(b=r,"begin"===r.type)return function(e){var t=e[0],r=e.rule;const a=new n(r),i=[r.__beforeBegin,r["on:begin"]];for(const n of i)if(n&&(n(e,a),a.ignore))return p(t);return r&&r.endSameAsBegin&&(r.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),r.skip?A+=t:(r.excludeBegin&&(A+=t),u(),r.returnBegin||r.excludeBegin||(A=t)),h(r),r.returnBegin?0:t.length}(r);if("illegal"===r.type&&!a){const e=Error('Illegal lexeme "'+i+'" for mode "'+(y.className||"")+'"');throw e.mode=y,e}if("end"===r.type){var s=function(e){var t=e[0],r=o.substr(e.index),a=function e(t,r,a){let i=function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(t.endRe,a);if(i){if(t["on:end"]){const e=new n(t);t["on:end"](r,e),e.ignore&&(i=!1)}if(i){for(;t.endsParent&&t.parent;)t=t.parent;return t}}if(t.endsWithParent)return e(t.parent,r,a)}(y,e,r);if(!a)return M;var i=y;i.skip?A+=t:(i.returnEnd||i.excludeEnd||(A+=t),u(),i.excludeEnd&&(A=t));do{y.className&&O.closeNode(),y.skip||y.subLanguage||(I+=y.relevance),y=y.parent}while(y!==a.parent);return a.starts&&(a.endSameAsBegin&&(a.starts.endRe=a.endRe),h(a.starts)),i.returnEnd?0:t.length}(r);if(s!==M)return s}if("illegal"===r.type&&""===i)return 1;if(B>1e5&&B>3*r.index)throw Error("potential infinite loop, way more iterations than matches");return A+=i,i.length}var E=T(e);if(!E)throw console.error(g.replace("{}",e)),Error('Unknown language: "'+e+'"');var _=function(e){function n(n,t){return RegExp(d(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class t{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n="|"){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i0&&(a+=n),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"===l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("===l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const n=this.matcherRe.exec(e);if(!n)return null;const t=n.findIndex((e,n)=>n>0&&void 0!==e),r=this.matchIndexes[t];return n.splice(0,t),Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;const t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e,n){const t=e.input[e.index-1],r=e.input[e.index+e[0].length];"."!==t&&"."!==r||n.ignoreMatch()}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return function t(s,o){const l=s;if(s.compiled)return l;s.compiled=!0,s.__beforeBegin=null,s.keywords=s.keywords||s.beginKeywords;let c=null;if("object"==typeof s.keywords&&(c=s.keywords.$pattern,delete s.keywords.$pattern),s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,w(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemes&&c)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l.keywordPatternRe=n(s.lexemes||c||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__beforeBegin=i),s.begin||(s.begin=/\B|\b/),l.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(l.endRe=n(s.end)),l.terminator_end=d(s.end)||"",s.endsWithParent&&o.terminator_end&&(l.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(l.illegalRe=n(s.illegal)),void 0===s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return r(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?r(e,{starts:e.starts?r(e.starts):null}):Object.isFrozen(e)?r(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){t(e,l)})),s.starts&&t(s.starts,o),l.matcher=function(e){const n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(l),l}(e)}(E),N="",y=s||_,k={},O=new f.__emitter(f);!function(){for(var e=[],n=y;n!==E;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>O.openNode(e))}();var A="",I=0,S=0,B=0,L=!1;try{for(y.matcher.considerAll();;){B++,L?L=!1:(y.matcher.lastIndex=S,y.matcher.considerAll());const e=y.matcher.exec(o);if(!e)break;const n=x(o.substring(S,e.index),e);S=e.index+n}return x(o.substr(S)),O.closeAllNodes(),O.finalize(),N=O.toHTML(),{relevance:I,value:N,language:e,illegal:!1,emitter:O,top:y}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:o.slice(S-100,S+100),mode:n.mode},sofar:N,relevance:0,value:R(o),emitter:O};if(l)return{illegal:!1,relevance:0,value:R(o),emitter:O,language:e,top:y,errorRaised:n};throw n}}function v(e,n){n=n||f.languages||Object.keys(i);var t=function(e){const n={relevance:0,emitter:new f.__emitter(f),value:R(e),illegal:!1,top:h};return n.emitter.addText(e),n}(e),r=t;return n.filter(T).filter(I).forEach((function(n){var a=m(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function x(e){return f.tabReplace||f.useBR?e.replace(c,e=>"\n"===e?f.useBR?"
":e:f.tabReplace?e.replace(/\t/g,f.tabReplace):e):e}function E(e){let n=null;const t=function(e){var n=e.className+" ";n+=e.parentNode?e.parentNode.className:"";const t=f.languageDetectRe.exec(n);if(t){var r=T(t[1]);return r||(console.warn(g.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?t[1]:"no-highlight"}return n.split(/\s+/).find(e=>p(e)||T(e))}(e);if(p(t))return;S("before:highlightBlock",{block:e,language:t}),f.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"):n=e;const r=n.textContent,a=t?b(t,r,!0):v(r),i=k(n);if(i.length){const e=document.createElement("div");e.innerHTML=a.value,a.value=O(i,k(e),r)}a.value=x(a.value),S("after:highlightBlock",{block:e,result:a}),e.innerHTML=a.value,e.className=function(e,n,t){var r=n?s[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,t,a.language),e.result={language:a.language,re:a.relevance,relavance:a.relevance},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.relevance,relavance:a.second_best.relevance})}const N=()=>{if(!N.called){N.called=!0;var e=document.querySelectorAll("pre code");a.forEach.call(e,E)}};function T(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]}function A(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach(e=>{s[e]=n})}function I(e){var n=T(e);return n&&!n.disableAutodetect}function S(e,n){var t=e;o.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(t,{highlight:b,highlightAuto:v,fixMarkup:x,highlightBlock:E,configure:function(e){f=y(f,e)},initHighlighting:N,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",N,!1)},registerLanguage:function(e,n){var r=null;try{r=n(t)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!l)throw n;console.error(n),r=h}r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&A(r.aliases,{languageName:e})},listLanguages:function(){return Object.keys(i)},getLanguage:T,registerAliases:A,requireLanguage:function(e){var n=T(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:I,inherit:y,addPlugin:function(e){o.push(e)}}),t.debugMode=function(){l=!1},t.safeMode=function(){l=!0},t.versionString="10.1.1";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(t,_),t}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs); +hljs.registerLanguage("apache",function(){"use strict";return function(e){var n={className:"number",begin:"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?"};return{name:"Apache config",aliases:["apacheconf"],case_insensitive:!0,contains:[e.HASH_COMMENT_MODE,{className:"section",begin:"",contains:[n,{className:"number",begin:":\\d{1,5}"},e.inherit(e.QUOTE_STRING_MODE,{relevance:0})]},{className:"attribute",begin:/\w+/,relevance:0,keywords:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{end:/$/,relevance:0,keywords:{literal:"on off all deny allow"},contains:[{className:"meta",begin:"\\s\\[",end:"\\]$"},{className:"variable",begin:"[\\$%]\\{",end:"\\}",contains:["self",{className:"number",begin:"[\\$%]\\d+"}]},n,{className:"number",begin:"\\d+"},e.QUOTE_STRING_MODE]}}],illegal:/\S/}}}()); +hljs.registerLanguage("bash",function(){"use strict";return function(e){const s={};Object.assign(s,{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:[{begin:/:-/,contains:[s]}]}]});const t={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},n={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,t]};t.contains.push(n);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]},i=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b-?[a-z\._]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[i,e.SHEBANG(),c,a,e.HASH_COMMENT_MODE,n,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}}()); +hljs.registerLanguage("c-like",function(){"use strict";return function(e){function t(e){return"(?:"+e+")?"}var n="(decltype\\(auto\\)|"+t("[a-zA-Z_]\\w*::")+"[a-zA-Z_]\\w*"+t("<.*?>")+")",r={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},a={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",end:"'",illegal:"."},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},i={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(a,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},o={className:"title",begin:t("[a-zA-Z_]\\w*::")+e.IDENT_RE,relevance:0},c=t("[a-zA-Z_]\\w*::")+e.IDENT_RE+"\\s*\\(",l={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},d=[r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,i,a],_={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:l,contains:d.concat([{begin:/\(/,end:/\)/,keywords:l,contains:d.concat(["self"]),relevance:0}]),relevance:0},u={className:"function",begin:"("+n+"[\\*&\\s]+)+"+c,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:l,illegal:/[^\w\s\*&:<>]/,contains:[{begin:"decltype\\(auto\\)",keywords:l,relevance:0},{begin:c,returnBegin:!0,contains:[o],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r,{begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r]}]},r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],keywords:l,disableAutodetect:!0,illegal:"",keywords:l,contains:["self",r]},{begin:e.IDENT_RE+"::",keywords:l},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin://,contains:["self"]},e.TITLE_MODE]}]),exports:{preprocessor:s,strings:a,keywords:l}}}}()); +hljs.registerLanguage("c",function(){"use strict";return function(e){var n=e.getLanguage("c-like").rawDefinition();return n.name="C",n.aliases=["c","h"],n}}()); +hljs.registerLanguage("coffeescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={keyword:e.concat(["then","unless","until","loop","by","when","and","or","is","isnt","not"]).filter((e=>n=>!e.includes(n))(["var","const","let","function","static"])).join(" "),literal:n.concat(["yes","no","on","off"]).join(" "),built_in:a.concat(["npm","print"]).join(" ")},i="[A-Za-z$_][0-9A-Za-z$_]*",s={className:"subst",begin:/#\{/,end:/}/,keywords:t},o=[r.BINARY_NUMBER_MODE,r.inherit(r.C_NUMBER_MODE,{starts:{end:"(\\s*/)?",relevance:0}}),{className:"string",variants:[{begin:/'''/,end:/'''/,contains:[r.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[r.BACKSLASH_ESCAPE]},{begin:/"""/,end:/"""/,contains:[r.BACKSLASH_ESCAPE,s]},{begin:/"/,end:/"/,contains:[r.BACKSLASH_ESCAPE,s]}]},{className:"regexp",variants:[{begin:"///",end:"///",contains:[s,r.HASH_COMMENT_MODE]},{begin:"//[gim]{0,3}(?=\\W)",relevance:0},{begin:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{begin:"@"+i},{subLanguage:"javascript",excludeBegin:!0,excludeEnd:!0,variants:[{begin:"```",end:"```"},{begin:"`",end:"`"}]}];s.contains=o;var c=r.inherit(r.TITLE_MODE,{begin:i}),l={className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/,end:/\)/,keywords:t,contains:["self"].concat(o)}]};return{name:"CoffeeScript",aliases:["coffee","cson","iced"],keywords:t,illegal:/\/\*/,contains:o.concat([r.COMMENT("###","###"),r.HASH_COMMENT_MODE,{className:"function",begin:"^\\s*"+i+"\\s*=\\s*(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[c,l]},{begin:/[:\(,=]\s*/,relevance:0,contains:[{className:"function",begin:"(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[l]}]},{className:"class",beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[c]},c]},{begin:i+":",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}}()); +hljs.registerLanguage("cpp",function(){"use strict";return function(e){var t=e.getLanguage("c-like").rawDefinition();return t.disableAutodetect=!1,t.name="C++",t.aliases=["cc","c++","h++","hpp","hh","hxx","cxx"],t}}()); +hljs.registerLanguage("csharp",function(){"use strict";return function(e){var n={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},t=e.inherit(s,{illegal:/\n/}),l={className:"subst",begin:"{",end:"}",keywords:n},r=e.inherit(l,{illegal:/\n/}),c={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},e.BACKSLASH_ESCAPE,r]},o={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},l]},g=e.inherit(o,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},r]});l.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],r.contains=[g,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];var d={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},E={begin:"<",end:">",contains:[{beginKeywords:"in out"},i]},_=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",b={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:""}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,a,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},i,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+_+"\\s+)+"+e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{begin:e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,contains:[e.TITLE_MODE,E],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[d,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},b]}}}()); +hljs.registerLanguage("css",function(){"use strict";return function(e){var n={begin:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute",begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/},{begin:/\(/,end:/\)/,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]}}]};return{name:"CSS",case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",illegal:/:/,returnBegin:!0,contains:[{className:"keyword",begin:/@\-?\w[\w]*(\-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:"and or not only",contains:[{begin:/[a-z-]+:/,className:"attribute"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{begin:"{",end:"}",illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,n]}]}}}()); +hljs.registerLanguage("diff",function(){"use strict";return function(e){return{name:"Diff",aliases:["patch"],contains:[{className:"meta",relevance:10,variants:[{begin:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{begin:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{begin:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{className:"comment",variants:[{begin:/Index: /,end:/$/},{begin:/={3,}/,end:/$/},{begin:/^\-{3}/,end:/$/},{begin:/^\*{3} /,end:/$/},{begin:/^\+{3}/,end:/$/},{begin:/^\*{15}$/}]},{className:"addition",begin:"^\\+",end:"$"},{className:"deletion",begin:"^\\-",end:"$"},{className:"addition",begin:"^\\!",end:"$"}]}}}()); +hljs.registerLanguage("go",function(){"use strict";return function(e){var n={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{name:"Go",aliases:["golang"],keywords:n,illegal:"e(n)).join("")}return function(a){var s={className:"number",relevance:0,variants:[{begin:/([\+\-]+)?[\d]+_[\d_]+/},{begin:a.NUMBER_RE}]},i=a.COMMENT();i.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];var t={className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)}/}]},r={className:"literal",begin:/\bon|off|true|false|yes|no\b/},l={className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:"'''",end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'},{begin:"'",end:"'"}]},c={begin:/\[/,end:/\]/,contains:[i,r,t,l,s,"self"],relevance:0},g="("+[/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/].map(n=>e(n)).join("|")+")";return{name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/,contains:[i,{className:"section",begin:/\[+/,end:/\]+/},{begin:n(g,"(\\s*\\.\\s*",g,")*",n("(?=",/\s*=\s*[^#\s]/,")")),className:"attr",starts:{end:/$/,contains:[i,c,r,t,l,s]}}]}}}()); +hljs.registerLanguage("java",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(e){return a("(",e,")?")}function a(...n){return n.map(n=>e(n)).join("")}function s(...n){return"("+n.map(n=>e(n)).join("|")+")"}return function(e){var t="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",i={className:"meta",begin:"@[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",contains:[{begin:/\(/,end:/\)/,contains:["self"]}]},r=e=>a("[",e,"]+([",e,"_]*[",e,"]+)?"),c={className:"number",variants:[{begin:`\\b(0[bB]${r("01")})[lL]?`},{begin:`\\b(0${r("0-7")})[dDfFlL]?`},{begin:a(/\b0[xX]/,s(a(r("a-fA-F0-9"),/\./,r("a-fA-F0-9")),a(r("a-fA-F0-9"),/\.?/),a(/\./,r("a-fA-F0-9"))),/([pP][+-]?(\d+))?/,/[fFdDlL]?/)},{begin:a(/\b/,s(a(/\d*\./,r("\\d")),r("\\d")),/[eE][+-]?[\d]+[dDfF]?/)},{begin:a(/\b/,r(/\d/),n(/\.?/),n(r(/\d/)),/[dDfFlL]?/)}],relevance:0};return{name:"Java",aliases:["jsp"],keywords:t,illegal:/<\/|#/,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:t,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:t,relevance:0,contains:[i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},c,i]}}}()); +hljs.registerLanguage("javascript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function s(e){return r("(?=",e,")")}function r(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(t){var i="[A-Za-z$_][0-9A-Za-z$_]*",c={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/},o={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.join(" "),literal:n.join(" "),built_in:a.join(" ")},l={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:t.C_NUMBER_RE+"n?"}],relevance:0},E={className:"subst",begin:"\\$\\{",end:"\\}",keywords:o,contains:[]},d={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"xml"}},g={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"css"}},u={className:"string",begin:"`",end:"`",contains:[t.BACKSLASH_ESCAPE,E]};E.contains=[t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,l,t.REGEXP_MODE];var b=E.contains.concat([{begin:/\(/,end:/\)/,contains:["self"].concat(E.contains,[t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE])},t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE]),_={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:b};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:o,contains:[t.SHEBANG({binary:"node",relevance:5}),{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,t.C_LINE_COMMENT_MODE,t.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:i+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),t.C_BLOCK_COMMENT_MODE,l,{begin:r(/[{,\n]\s*/,s(r(/(((\/\/.*)|(\/\*(.|\n)*\*\/))\s*)*/,i+"\\s*:"))),relevance:0,contains:[{className:"attr",begin:i+s("\\s*:"),relevance:0}]},{begin:"("+t.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[t.C_LINE_COMMENT_MODE,t.C_BLOCK_COMMENT_MODE,t.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+t.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:t.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:o,contains:b}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:""},{begin:c.begin,end:c.end}],subLanguage:"xml",contains:[{begin:c.begin,end:c.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[t.inherit(t.TITLE_MODE,{begin:i}),_],illegal:/\[|%/},{begin:/\$[(.]/},t.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},t.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0},{begin:"(get|set)\\s+(?="+i+"\\()",end:/{/,keywords:"get set",contains:[t.inherit(t.TITLE_MODE,{begin:i}),{begin:/\(\)/},_]}],illegal:/#(?!!)/}}}()); +hljs.registerLanguage("json",function(){"use strict";return function(n){var e={literal:"true false null"},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],t=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],a={end:",",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:e},l={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(a,{begin:/:/})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(a)],illegal:"\\S"};return t.push(l,s),i.forEach((function(n){t.push(n)})),{name:"JSON",contains:t,keywords:e,illegal:"\\S"}}}()); +hljs.registerLanguage("kotlin",function(){"use strict";return function(e){var n={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual trait volatile transient native default",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},a={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@"},i={className:"subst",begin:"\\${",end:"}",contains:[e.C_NUMBER_MODE]},s={className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},t={className:"string",variants:[{begin:'"""',end:'"""(?=[^"])',contains:[s,i]},{begin:"'",end:"'",illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,contains:[e.BACKSLASH_ESCAPE,s,i]}]};i.contains.push(t);var r={className:"meta",begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?"},l={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,end:/\)/,contains:[e.inherit(t,{className:"meta-string"})]}]},c=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),o={variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,contains:[]}]},d=o;return d.variants[1].contains=[o],o.variants[1].contains=[d],{name:"Kotlin",aliases:["kt"],keywords:n,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,c,{className:"keyword",begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol",begin:/@\w+/}]}},a,r,l,{className:"function",beginKeywords:"fun",end:"[(]|$",returnBegin:!0,excludeEnd:!0,keywords:n,illegal:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin://,keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,endsWithParent:!0,contains:[o,e.C_LINE_COMMENT_MODE,c],relevance:0},e.C_LINE_COMMENT_MODE,c,r,l,t,e.C_NUMBER_MODE]},c]},{className:"class",beginKeywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,illegal:"extends implements",contains:[{beginKeywords:"public protected internal private constructor"},e.UNDERSCORE_TITLE_MODE,{className:"type",begin://,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,]|$/,excludeBegin:!0,returnEnd:!0},r,l]},t,{className:"meta",begin:"^#!/usr/bin/env",end:"$",illegal:"\n"},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0}]}}}()); +hljs.registerLanguage("less",function(){"use strict";return function(e){var n="([\\w-]+|@{[\\w-]+})",a=[],s=[],t=function(e){return{className:"string",begin:"~?"+e+".*?"+e}},r=function(e,n,a){return{className:e,begin:n,relevance:a}},i={begin:"\\(",end:"\\)",contains:s,relevance:0};s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t("'"),t('"'),e.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},r("number","#[0-9A-Fa-f]+\\b"),i,r("variable","@@?[\\w-]+",10),r("variable","@{[\\w-]+}"),r("built_in","~?`[^`]*?`"),{className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0},{className:"meta",begin:"!important"});var c=s.concat({begin:"{",end:"}",contains:a}),l={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(s)},o={begin:n+"\\s*:",returnBegin:!0,end:"[;}]",relevance:0,contains:[{className:"attribute",begin:n,end:":",excludeEnd:!0,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}]},g={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",returnEnd:!0,contains:s,relevance:0}},d={className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:c}},b={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:n,end:"{"}],returnBegin:!0,returnEnd:!0,illegal:"[<='$\"]",relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,l,r("keyword","all\\b"),r("variable","@{[\\w-]+}"),r("selector-tag",n+"%?",0),r("selector-id","#"+n),r("selector-class","\\."+n,0),r("selector-tag","&",0),{className:"selector-attr",begin:"\\[",end:"\\]"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"\\(",end:"\\)",contains:c},{begin:"!important"}]};return a.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,g,d,o,b),{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:a}}}()); +hljs.registerLanguage("lua",function(){"use strict";return function(e){var t={begin:"\\[=*\\[",end:"\\]=*\\]",contains:["self"]},a=[e.COMMENT("--(?!\\[=*\\[)","$"),e.COMMENT("--\\[=*\\[","\\]=*\\]",{contains:[t],relevance:10})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE,literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},contains:a.concat([{className:"function",beginKeywords:"function",end:"\\)",contains:[e.inherit(e.TITLE_MODE,{begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params",begin:"\\(",endsWithParent:!0,contains:a}].concat(a)},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",begin:"\\[=*\\[",end:"\\]=*\\]",contains:[t],relevance:5}])}}}()); +hljs.registerLanguage("makefile",function(){"use strict";return function(e){var i={className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)",contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"",relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"",contains:[a,s,i,t]}]}]},e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:")",end:">",keywords:{name:"style"},contains:[c],starts:{end:"",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:")",end:">",keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},c]}]}}}()); +hljs.registerLanguage("markdown",function(){"use strict";return function(n){const e={begin:"<",end:">",subLanguage:"xml",relevance:0},a={begin:"\\[.+?\\][\\(\\[].*?[\\)\\]]",returnBegin:!0,contains:[{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0,relevance:0},{className:"link",begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"symbol",begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}],relevance:10},i={className:"strong",contains:[],variants:[{begin:/_{2}/,end:/_{2}/},{begin:/\*{2}/,end:/\*{2}/}]},s={className:"emphasis",contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{begin:/_(?!_)/,end:/_/,relevance:0}]};i.contains.push(s),s.contains.push(i);var c=[e,a];return i.contains=i.contains.concat(c),s.contains=s.contains.concat(c),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:c=c.concat(i,s)},{begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",contains:c}]}]},e,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:c,end:"$"},{className:"code",variants:[{begin:"(`{3,})(.|\\n)*?\\1`*[ ]*"},{begin:"(~{3,})(.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}}()); +hljs.registerLanguage("nginx",function(){"use strict";return function(e){var n={className:"variable",variants:[{begin:/\$\d+/},{begin:/\$\{/,end:/}/},{begin:"[\\$\\@]"+e.UNDERSCORE_IDENT_RE}]},a={endsWithParent:!0,keywords:{$pattern:"[a-z/_]+",literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},relevance:0,illegal:"=>",contains:[e.HASH_COMMENT_MODE,{className:"string",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/}]},{begin:"([a-z]+):/",end:"\\s",endsWithParent:!0,excludeEnd:!0,contains:[n]},{className:"regexp",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:"\\s\\^",end:"\\s|{|;",returnEnd:!0},{begin:"~\\*?\\s+",end:"\\s|{|;",returnEnd:!0},{begin:"\\*(\\.[a-z\\-]+)+"},{begin:"([a-z\\-]+\\.)+\\*"}]},{className:"number",begin:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{className:"number",begin:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},n]};return{name:"Nginx config",aliases:["nginxconf"],contains:[e.HASH_COMMENT_MODE,{begin:e.UNDERSCORE_IDENT_RE+"\\s+{",returnBegin:!0,end:"{",contains:[{className:"section",begin:e.UNDERSCORE_IDENT_RE}],relevance:0},{begin:e.UNDERSCORE_IDENT_RE+"\\s",end:";|{",returnBegin:!0,contains:[{className:"attribute",begin:e.UNDERSCORE_IDENT_RE,starts:a}],relevance:0}],illegal:"[^\\s\\}]"}}}()); +hljs.registerLanguage("objectivec",function(){"use strict";return function(e){var n=/[a-zA-Z@][a-zA-Z0-9_]*/,_={$pattern:n,keyword:"@interface @class @protocol @implementation"};return{name:"Objective-C",aliases:["mm","objc","obj-c"],keywords:{$pattern:n,keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},illegal:"/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"class",begin:"("+_.keyword.split(" ").join("|")+")\\b",end:"({|$)",excludeEnd:!0,keywords:_,contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"\\."+e.UNDERSCORE_IDENT_RE,relevance:0}]}}}()); +hljs.registerLanguage("perl",function(){"use strict";return function(e){var n={$pattern:/[\w.]+/,keyword:"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qq fileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmget sub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedir ioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when"},t={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:n},s={begin:"->{",end:"}"},r={variants:[{begin:/\$\d/},{begin:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{begin:/[\$%@][^\s\w{]/,relevance:0}]},i=[e.BACKSLASH_ESCAPE,t,r],a=[r,e.HASH_COMMENT_MODE,e.COMMENT("^\\=\\w","\\=cut",{endsWithParent:!0}),s,{className:"string",contains:i,variants:[{begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*\\<",end:"\\>",relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'",contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE]},{begin:"{\\w+}",contains:[],relevance:0},{begin:"-?\\w+\\s*\\=\\>",contains:[],relevance:0}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*",keywords:"split return print reverse grep",relevance:0,contains:[e.HASH_COMMENT_MODE,{className:"regexp",begin:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{className:"regexp",begin:"(m|qr)?/",end:"/[a-z]*",contains:[e.BACKSLASH_ESCAPE],relevance:0}]},{className:"function",beginKeywords:"sub",end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$",subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}]}];return t.contains=a,s.contains=a,{name:"Perl",aliases:["pl","pm"],keywords:n,contains:a}}}()); +hljs.registerLanguage("php",function(){"use strict";return function(e){var r={begin:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},t={className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{begin:/\?>/}]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:'b"',end:'"'},{begin:"b'",end:"'"},e.inherit(e.APOS_STRING_MODE,{illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null})]},n={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]},i={keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list new object or private protected public real return string switch throw trait try unset use var void while xor yield",literal:"false null true",built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Throwable Traversable WeakReference Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass"};return{aliases:["php","php3","php4","php5","php6","php7"],case_insensitive:!0,keywords:i,contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t]}),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler"}),{className:"string",begin:/<<<['"]?\w+['"]?$/,end:/^\w+;?$/,contains:[e.BACKSLASH_ESCAPE,{className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]}]},t,{className:"keyword",begin:/\$this\b/},r,{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:i,contains:["self",r,e.C_BLOCK_COMMENT_MODE,a,n]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"=>"},a,n]}}}()); +hljs.registerLanguage("php-template",function(){"use strict";return function(n){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},n.inherit(n.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}}()); +hljs.registerLanguage("plaintext",function(){"use strict";return function(t){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}}()); +hljs.registerLanguage("properties",function(){"use strict";return function(e){var n="[ \\t\\f]*",t="("+n+"[:=]"+n+"|[ \\t\\f]+)",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",s={end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+"+t,returnBegin:!0,contains:[{className:"attr",begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:s},{begin:a+t,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:a,endsParent:!0,relevance:0}],starts:s},{className:"attr",relevance:0,begin:a+n+"$"}]}}}()); +hljs.registerLanguage("python",function(){"use strict";return function(e){var n={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10",built_in:"Ellipsis NotImplemented",literal:"False None True"},a={className:"meta",begin:/^(>>>|\.\.\.) /},i={className:"subst",begin:/\{/,end:/\}/,keywords:n,illegal:/#/},s={begin:/\{\{/,relevance:0},r={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,i]},{begin:/(fr|rf|f)"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,i]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l={className:"number",relevance:0,variants:[{begin:e.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:e.C_NUMBER_RE+"[lLjJ]?"}]},t={className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:["self",a,l,r,e.HASH_COMMENT_MODE]}]};return i.contains=[r,l,a],{name:"Python",aliases:["py","gyp","ipython"],keywords:n,illegal:/(<\/|->|\?)|=>/,contains:[a,l,{beginKeywords:"if",relevance:0},r,e.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,contains:[e.UNDERSCORE_TITLE_MODE,t,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}}}()); +hljs.registerLanguage("python-repl",function(){"use strict";return function(n){return{aliases:["pycon"],contains:[{className:"meta",starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}}}()); +hljs.registerLanguage("ruby",function(){"use strict";return function(e){var n="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",a={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},s={className:"doctag",begin:"@[A-Za-z]+"},i={begin:"#<",end:">"},r=[e.COMMENT("#","$",{contains:[s]}),e.COMMENT("^\\=begin","^\\=end",{contains:[s],relevance:10}),e.COMMENT("^__END__","\\n$")],c={className:"subst",begin:"#\\{",end:"}",keywords:a},t={className:"string",contains:[e.BACKSLASH_ESCAPE,c],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/",end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{begin:"%[qQwWx]?\\|",end:"\\|"},{begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{begin:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,returnBegin:!0,contains:[{begin:/<<[-~]?'?/},e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[e.BACKSLASH_ESCAPE,c]})]}]},b={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:a},d=[t,i,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<\\s*",contains:[{begin:"("+e.IDENT_RE+"::)?"+e.IDENT_RE}]}].concat(r)},{className:"function",beginKeywords:"def",end:"$|;",contains:[e.inherit(e.TITLE_MODE,{begin:n}),b].concat(r)},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[t,{begin:n}],relevance:0},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params",begin:/\|/,end:/\|/,keywords:a},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[i,{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(r),relevance:0}].concat(r);c.contains=d,b.contains=d;var g=[{begin:/^\s*=>/,starts:{end:"$",contains:d}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{end:"$",contains:d}}];return{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:a,illegal:/\/\*/,contains:r.concat(g).concat(d)}}}()); +hljs.registerLanguage("rust",function(){"use strict";return function(e){var n="([ui](8|16|32|64|128|size)|f(32|64))?",t="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:t},illegal:""}]}}}()); +hljs.registerLanguage("scss",function(){"use strict";return function(e){var t={className:"variable",begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={className:"number",begin:"#[0-9A-Fa-f]+"};return e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:"\\#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},{className:"selector-attr",begin:"\\[",end:"\\]",illegal:"$"},{className:"selector-tag",begin:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{className:"selector-pseudo",begin:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{className:"selector-pseudo",begin:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},t,{className:"attribute",begin:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",illegal:"[^\\s]"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{begin:":",end:";",contains:[t,i,e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"meta",begin:"!important"}]},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",returnBegin:!0,keywords:"and or not only",contains:[{begin:"@[a-z-]+",className:"keyword"},t,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,i,e.CSS_NUMBER_MODE]}]}}}()); +hljs.registerLanguage("shell",function(){"use strict";return function(s){return{name:"Shell Session",aliases:["console"],contains:[{className:"meta",begin:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{end:"$",subLanguage:"bash"}}]}}}()); +hljs.registerLanguage("sql",function(){"use strict";return function(e){var t=e.COMMENT("--","$");return{name:"SQL",case_insensitive:!0,illegal:/[<>{}*]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",end:/;/,endsWithParent:!0,keywords:{$pattern:/[\w\.]+/,keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:"`",end:"`"},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]},e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]}}}()); +hljs.registerLanguage("swift",function(){"use strict";return function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},n=e.COMMENT("/\\*","\\*/",{contains:["self"]}),t={className:"subst",begin:/\\\(/,end:"\\)",keywords:i,contains:[]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:/"""/,end:/"""/},{begin:/"/,end:/"/}]},r={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return t.contains=[r],{name:"Swift",keywords:i,contains:[a,e.C_LINE_COMMENT_MODE,n,{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},r,{className:"function",beginKeywords:"func",end:"{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin://},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:i,contains:["self",r,a,e.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:i,end:"\\{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta",begin:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)\\b"},{beginKeywords:"import",end:/$/,contains:[e.C_LINE_COMMENT_MODE,n]}]}}}()); +hljs.registerLanguage("typescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.concat(["type","namespace","typedef","interface","public","private","protected","implements","declare","abstract","readonly"]).join(" "),literal:n.join(" "),built_in:a.concat(["any","void","number","boolean","string","object","never","enum"]).join(" ")},s={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},i={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:r.C_NUMBER_RE+"n?"}],relevance:0},o={className:"subst",begin:"\\$\\{",end:"\\}",keywords:t,contains:[]},c={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"xml"}},l={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"css"}},E={className:"string",begin:"`",end:"`",contains:[r.BACKSLASH_ESCAPE,o]};o.contains=[r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,i,r.REGEXP_MODE];var d={begin:"\\(",end:/\)/,keywords:t,contains:["self",r.QUOTE_STRING_MODE,r.APOS_STRING_MODE,r.NUMBER_MODE]},u={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,s,d]};return{name:"TypeScript",aliases:["ts"],keywords:t,contains:[r.SHEBANG(),{className:"meta",begin:/^\s*['"]use strict['"]/},r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,i,{begin:"("+r.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,r.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+r.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:r.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:d.contains}]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[\{;]/,excludeEnd:!0,keywords:t,contains:["self",r.inherit(r.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),u],illegal:/%/,relevance:0},{beginKeywords:"constructor",end:/[\{;]/,excludeEnd:!0,contains:["self",u]},{begin:/module\./,keywords:{built_in:"module"},relevance:0},{beginKeywords:"module",end:/\{/,excludeEnd:!0},{beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:"interface extends"},{begin:/\$[(.]/},{begin:"\\."+r.IDENT_RE,relevance:0},s,d]}}}()); +hljs.registerLanguage("yaml",function(){"use strict";return function(e){var n="true false yes no null",a="[\\w#;/?:@&=+$,.~*\\'()[\\]]+",s={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:"{{",end:"}}"},{begin:"%{",end:"}"}]}]},i=e.inherit(s,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={end:",",endsWithParent:!0,excludeEnd:!0,contains:[],keywords:n,relevance:0},t={begin:"{",end:"}",contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]",contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---s*$",relevance:10},{className:"string",begin:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type",begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"\\-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},{className:"number",begin:e.C_NUMBER_RE+"\\b"},t,g,s],c=[...b];return c.pop(),c.push(i),l.contains=c,{name:"YAML",case_insensitive:!0,aliases:["yml","YAML"],contains:b}}}()); +hljs.registerLanguage("armasm",function(){"use strict";return function(s){const e={variants:[s.COMMENT("^[ \\t]*(?=#)","$",{relevance:0,excludeBegin:!0}),s.COMMENT("[;@]","$",{relevance:0}),s.C_LINE_COMMENT_MODE,s.C_BLOCK_COMMENT_MODE]};return{name:"ARM Assembly",case_insensitive:!0,aliases:["arm"],keywords:{$pattern:"\\.?"+s.IDENT_RE,meta:".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .arm .thumb .code16 .code32 .force_thumb .thumb_func .ltorg ALIAS ALIGN ARM AREA ASSERT ATTR CN CODE CODE16 CODE32 COMMON CP DATA DCB DCD DCDU DCDO DCFD DCFDU DCI DCQ DCQU DCW DCWU DN ELIF ELSE END ENDFUNC ENDIF ENDP ENTRY EQU EXPORT EXPORTAS EXTERN FIELD FILL FUNCTION GBLA GBLL GBLS GET GLOBAL IF IMPORT INCBIN INCLUDE INFO KEEP LCLA LCLL LCLS LTORG MACRO MAP MEND MEXIT NOFP OPT PRESERVE8 PROC QN READONLY RELOC REQUIRE REQUIRE8 RLIST FN ROUT SETA SETL SETS SN SPACE SUBT THUMB THUMBX TTL WHILE WEND ",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 pc lr sp ip sl sb fp a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 v7 v8 f0 f1 f2 f3 f4 f5 f6 f7 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 q0 q1 q2 q3 q4 q5 q6 q7 q8 q9 q10 q11 q12 q13 q14 q15 cpsr_c cpsr_x cpsr_s cpsr_f cpsr_cx cpsr_cxs cpsr_xs cpsr_xsf cpsr_sf cpsr_cxsf spsr_c spsr_x spsr_s spsr_f spsr_cx spsr_cxs spsr_xs spsr_xsf spsr_sf spsr_cxsf s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s30 s31 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 d16 d17 d18 d19 d20 d21 d22 d23 d24 d25 d26 d27 d28 d29 d30 d31 {PC} {VAR} {TRUE} {FALSE} {OPT} {CONFIG} {ENDIAN} {CODESIZE} {CPU} {FPU} {ARCHITECTURE} {PCSTOREOFFSET} {ARMASM_VERSION} {INTER} {ROPI} {RWPI} {SWST} {NOSWST} . @"},contains:[{className:"keyword",begin:"\\b(adc|(qd?|sh?|u[qh]?)?add(8|16)?|usada?8|(q|sh?|u[qh]?)?(as|sa)x|and|adrl?|sbc|rs[bc]|asr|b[lx]?|blx|bxj|cbn?z|tb[bh]|bic|bfc|bfi|[su]bfx|bkpt|cdp2?|clz|clrex|cmp|cmn|cpsi[ed]|cps|setend|dbg|dmb|dsb|eor|isb|it[te]{0,3}|lsl|lsr|ror|rrx|ldm(([id][ab])|f[ds])?|ldr((s|ex)?[bhd])?|movt?|mvn|mra|mar|mul|[us]mull|smul[bwt][bt]|smu[as]d|smmul|smmla|mla|umlaal|smlal?([wbt][bt]|d)|mls|smlsl?[ds]|smc|svc|sev|mia([bt]{2}|ph)?|mrr?c2?|mcrr2?|mrs|msr|orr|orn|pkh(tb|bt)|rbit|rev(16|sh)?|sel|[su]sat(16)?|nop|pop|push|rfe([id][ab])?|stm([id][ab])?|str(ex)?[bhd]?|(qd?)?sub|(sh?|q|u[qh]?)?sub(8|16)|[su]xt(a?h|a?b(16)?)|srs([id][ab])?|swpb?|swi|smi|tst|teq|wfe|wfi|yield)(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al|hs|lo)?[sptrx]?(?=\\s)"},e,s.QUOTE_STRING_MODE,{className:"string",begin:"'",end:"[^\\\\]'",relevance:0},{className:"title",begin:"\\|",end:"\\|",illegal:"\\n",relevance:0},{className:"number",variants:[{begin:"[#$=]?0x[0-9a-f]+"},{begin:"[#$=]?0b[01]+"},{begin:"[#$=]\\d+"},{begin:"\\b\\d+"}],relevance:0},{className:"symbol",variants:[{begin:"^[ \\t]*[a-z_\\.\\$][a-z0-9_\\.\\$]+:"},{begin:"^[a-z_\\.\\$][a-z0-9_\\.\\$]+"},{begin:"[=#]\\w+"}],relevance:0}]}}}()); +hljs.registerLanguage("d",function(){"use strict";return function(e){var a={$pattern:e.UNDERSCORE_IDENT_RE,keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},d="((0|[1-9][\\d_]*)|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))",n="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",t={className:"number",begin:"\\b"+d+"(L|u|U|Lu|LU|uL|UL)?",relevance:0},_={className:"number",begin:"\\b(((0[xX](([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)\\.([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)|\\.?([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))[pP][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))|((0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(\\.\\d*|([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)))|\\d+\\.(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)|\\.(0|[1-9][\\d_]*)([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))?))([fF]|L|i|[fF]i|Li)?|"+d+"(i|[fF]i|Li))",relevance:0},r={className:"string",begin:"'("+n+"|.)",end:"'",illegal:"."},i={className:"string",begin:'"',contains:[{begin:n,relevance:0}],end:'"[cwd]?'},s=e.COMMENT("\\/\\+","\\+\\/",{contains:["self"],relevance:10});return{name:"D",keywords:a,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,{className:"string",begin:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',relevance:10},i,{className:"string",begin:'[rq]"',end:'"[cwd]?',relevance:5},{className:"string",begin:"`",end:"`[cwd]?"},{className:"string",begin:'q"\\{',end:'\\}"'},_,t,r,{className:"meta",begin:"^#!",end:"$",relevance:5},{className:"meta",begin:"#(line)",end:"$",relevance:5},{className:"keyword",begin:"@[a-zA-Z_][a-zA-Z_\\d]*"}]}}}()); +hljs.registerLanguage("handlebars",function(){"use strict";function e(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(n){const a={"builtin-name":"action bindattr collection component concat debugger each each-in get hash if in input link-to loc log lookup mut outlet partial query-params render template textarea unbound unless view with yield"},t=/\[.*?\]/,s=/[^\s!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~]+/,i=e("(",/'.*?'/,"|",/".*?"/,"|",t,"|",s,"|",/\.|\//,")+"),r=e("(",t,"|",s,")(?==)"),l={begin:i,lexemes:/[\w.\/]+/},c=n.inherit(l,{keywords:{literal:"true false undefined null"}}),o={begin:/\(/,end:/\)/},m={className:"attr",begin:r,relevance:0,starts:{begin:/=/,end:/=/,starts:{contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,c,o]}}},d={contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,{begin:/as\s+\|/,keywords:{keyword:"as"},end:/\|/,contains:[{begin:/\w+/}]},m,c,o],returnEnd:!0},g=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/\)/})});o.contains=[g];const u=n.inherit(l,{keywords:a,className:"name",starts:n.inherit(d,{end:/}}/})}),b=n.inherit(l,{keywords:a,className:"name"}),h=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/}}/})});return{name:"Handlebars",aliases:["hbs","html.hbs","html.handlebars","htmlbars"],case_insensitive:!0,subLanguage:"xml",contains:[{begin:/\\\{\{/,skip:!0},{begin:/\\\\(?=\{\{)/,skip:!0},n.COMMENT(/\{\{!--/,/--\}\}/),n.COMMENT(/\{\{!/,/\}\}/),{className:"template-tag",begin:/\{\{\{\{(?!\/)/,end:/\}\}\}\}/,contains:[u],starts:{end:/\{\{\{\{\//,returnEnd:!0,subLanguage:"xml"}},{className:"template-tag",begin:/\{\{\{\{\//,end:/\}\}\}\}/,contains:[b]},{className:"template-tag",begin:/\{\{#/,end:/\}\}/,contains:[u]},{className:"template-tag",begin:/\{\{(?=else\}\})/,end:/\}\}/,keywords:"else"},{className:"template-tag",begin:/\{\{\//,end:/\}\}/,contains:[b]},{className:"template-variable",begin:/\{\{\{/,end:/\}\}\}/,contains:[h]},{className:"template-variable",begin:/\{\{/,end:/\}\}/,contains:[h]}]}}}()); +hljs.registerLanguage("haskell",function(){"use strict";return function(e){var n={variants:[e.COMMENT("--","$"),e.COMMENT("{-","-}",{contains:["self"]})]},i={className:"meta",begin:"{-#",end:"#-}"},a={className:"meta",begin:"^#",end:"$"},s={className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},l={begin:"\\(",end:"\\)",illegal:'"',contains:[i,a,{className:"type",begin:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},e.inherit(e.TITLE_MODE,{begin:"[_a-z][\\w']*"}),n]};return{name:"Haskell",aliases:["hs"],keywords:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",contains:[{beginKeywords:"module",end:"where",keywords:"module where",contains:[l,n],illegal:"\\W\\.|;"},{begin:"\\bimport\\b",end:"$",keywords:"import qualified as hiding",contains:[l,n],illegal:"\\W\\.|;"},{className:"class",begin:"^(\\s*)?(class|instance)\\b",end:"where",keywords:"class family instance where",contains:[s,l,n]},{className:"class",begin:"\\b(data|(new)?type)\\b",end:"$",keywords:"data family type newtype deriving",contains:[i,s,l,{begin:"{",end:"}",contains:l.contains},n]},{beginKeywords:"default",end:"$",contains:[s,l,n]},{beginKeywords:"infix infixl infixr",end:"$",contains:[e.C_NUMBER_MODE,n]},{begin:"\\bforeign\\b",end:"$",keywords:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",contains:[s,e.QUOTE_STRING_MODE,n]},{className:"meta",begin:"#!\\/usr\\/bin\\/env runhaskell",end:"$"},i,a,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,s,e.inherit(e.TITLE_MODE,{begin:"^[_a-z][\\w']*"}),n,{begin:"->|<-"}]}}}()); +hljs.registerLanguage("julia",function(){"use strict";return function(e){var r="[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*",t={$pattern:r,keyword:"in isa where baremodule begin break catch ccall const continue do else elseif end export false finally for function global if import importall let local macro module quote return true try using while type immutable abstract bitstype typealias ",literal:"true false ARGS C_NULL DevNull ENDIAN_BOM ENV I Inf Inf16 Inf32 Inf64 InsertionSort JULIA_HOME LOAD_PATH MergeSort NaN NaN16 NaN32 NaN64 PROGRAM_FILE QuickSort RoundDown RoundFromZero RoundNearest RoundNearestTiesAway RoundNearestTiesUp RoundToZero RoundUp STDERR STDIN STDOUT VERSION catalan e|0 eu|0 eulergamma golden im nothing pi γ π φ ",built_in:"ANY AbstractArray AbstractChannel AbstractFloat AbstractMatrix AbstractRNG AbstractSerializer AbstractSet AbstractSparseArray AbstractSparseMatrix AbstractSparseVector AbstractString AbstractUnitRange AbstractVecOrMat AbstractVector Any ArgumentError Array AssertionError Associative Base64DecodePipe Base64EncodePipe Bidiagonal BigFloat BigInt BitArray BitMatrix BitVector Bool BoundsError BufferStream CachingPool CapturedException CartesianIndex CartesianRange Cchar Cdouble Cfloat Channel Char Cint Cintmax_t Clong Clonglong ClusterManager Cmd CodeInfo Colon Complex Complex128 Complex32 Complex64 CompositeException Condition ConjArray ConjMatrix ConjVector Cptrdiff_t Cshort Csize_t Cssize_t Cstring Cuchar Cuint Cuintmax_t Culong Culonglong Cushort Cwchar_t Cwstring DataType Date DateFormat DateTime DenseArray DenseMatrix DenseVecOrMat DenseVector Diagonal Dict DimensionMismatch Dims DirectIndexString Display DivideError DomainError EOFError EachLine Enum Enumerate ErrorException Exception ExponentialBackOff Expr Factorization FileMonitor Float16 Float32 Float64 Function Future GlobalRef GotoNode HTML Hermitian IO IOBuffer IOContext IOStream IPAddr IPv4 IPv6 IndexCartesian IndexLinear IndexStyle InexactError InitError Int Int128 Int16 Int32 Int64 Int8 IntSet Integer InterruptException InvalidStateException Irrational KeyError LabelNode LinSpace LineNumberNode LoadError LowerTriangular MIME Matrix MersenneTwister Method MethodError MethodTable Module NTuple NewvarNode NullException Nullable Number ObjectIdDict OrdinalRange OutOfMemoryError OverflowError Pair ParseError PartialQuickSort PermutedDimsArray Pipe PollingFileWatcher ProcessExitedException Ptr QuoteNode RandomDevice Range RangeIndex Rational RawFD ReadOnlyMemoryError Real ReentrantLock Ref Regex RegexMatch RemoteChannel RemoteException RevString RoundingMode RowVector SSAValue SegmentationFault SerializationState Set SharedArray SharedMatrix SharedVector Signed SimpleVector Slot SlotNumber SparseMatrixCSC SparseVector StackFrame StackOverflowError StackTrace StepRange StepRangeLen StridedArray StridedMatrix StridedVecOrMat StridedVector String SubArray SubString SymTridiagonal Symbol Symmetric SystemError TCPSocket Task Text TextDisplay Timer Tridiagonal Tuple Type TypeError TypeMapEntry TypeMapLevel TypeName TypeVar TypedSlot UDPSocket UInt UInt128 UInt16 UInt32 UInt64 UInt8 UndefRefError UndefVarError UnicodeError UniformScaling Union UnionAll UnitRange Unsigned UpperTriangular Val Vararg VecElement VecOrMat Vector VersionNumber Void WeakKeyDict WeakRef WorkerConfig WorkerPool "},a={keywords:t,illegal:/<\//},n={className:"subst",begin:/\$\(/,end:/\)/,keywords:t},o={className:"variable",begin:"\\$"+r},i={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],variants:[{begin:/\w*"""/,end:/"""\w*/,relevance:10},{begin:/\w*"/,end:/"\w*/}]},l={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],begin:"`",end:"`"},s={className:"meta",begin:"@"+r};return a.name="Julia",a.contains=[{className:"number",begin:/(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/,relevance:0},{className:"string",begin:/'(.|\\[xXuU][a-zA-Z0-9]+)'/},i,l,s,{className:"comment",variants:[{begin:"#=",end:"=#",relevance:10},{begin:"#",end:"$"}]},e.HASH_COMMENT_MODE,{className:"keyword",begin:"\\b(((abstract|primitive)\\s+)type|(mutable\\s+)?struct)\\b"},{begin:/<:/}],n.contains=a.contains,a}}()); +hljs.registerLanguage("nim",function(){"use strict";return function(e){return{name:"Nim",aliases:["nim"],keywords:{keyword:"addr and as asm bind block break case cast const continue converter discard distinct div do elif else end enum except export finally for from func generic if import in include interface is isnot iterator let macro method mixin mod nil not notin object of or out proc ptr raise ref return shl shr static template try tuple type using var when while with without xor yield",literal:"shared guarded stdin stdout stderr result true false",built_in:"int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 float float32 float64 bool char string cstring pointer expr stmt void auto any range array openarray varargs seq set clong culong cchar cschar cshort cint csize clonglong cfloat cdouble clongdouble cuchar cushort cuint culonglong cstringarray semistatic"},contains:[{className:"meta",begin:/{\./,end:/\.}/,relevance:10},{className:"string",begin:/[a-zA-Z]\w*"/,end:/"/,contains:[{begin:/""/}]},{className:"string",begin:/([a-zA-Z]\w*)?"""/,end:/"""/},e.QUOTE_STRING_MODE,{className:"type",begin:/\b[A-Z]\w+\b/,relevance:0},{className:"number",relevance:0,variants:[{begin:/\b(0[xX][0-9a-fA-F][_0-9a-fA-F]*)('?[iIuU](8|16|32|64))?/},{begin:/\b(0o[0-7][_0-7]*)('?[iIuUfF](8|16|32|64))?/},{begin:/\b(0(b|B)[01][_01]*)('?[iIuUfF](8|16|32|64))?/},{begin:/\b(\d[_\d]*)('?[iIuUfF](8|16|32|64))?/}]},e.HASH_COMMENT_MODE]}}}()); +hljs.registerLanguage("nix",function(){"use strict";return function(e){var n={keyword:"rec with let in inherit assert if else then",literal:"true false or and null",built_in:"import abort baseNameOf dirOf isNull builtins map removeAttrs throw toString derivation"},i={className:"subst",begin:/\$\{/,end:/}/,keywords:n},t={className:"string",contains:[i],variants:[{begin:"''",end:"''"},{begin:'"',end:'"'}]},s=[e.NUMBER_MODE,e.HASH_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t,{begin:/[a-zA-Z0-9-_]+(\s*=)/,returnBegin:!0,relevance:0,contains:[{className:"attr",begin:/\S+/}]}];return i.contains=s,{name:"Nix",aliases:["nixos"],keywords:n,contains:s}}}()); +hljs.registerLanguage("r",function(){"use strict";return function(e){var n="([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*";return{name:"R",contains:[e.HASH_COMMENT_MODE,{begin:n,keywords:{$pattern:n,keyword:"function if in break next repeat else for return switch while try tryCatch stop warning require library attach detach source setMethod setGeneric setGroupGeneric setClass ...",literal:"NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10"},relevance:0},{className:"number",begin:"0[xX][0-9a-fA-F]+[Li]?\\b",relevance:0},{className:"number",begin:"\\d+(?:[eE][+\\-]?\\d*)?L\\b",relevance:0},{className:"number",begin:"\\d+\\.(?!\\d)(?:i\\b)?",relevance:0},{className:"number",begin:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{className:"number",begin:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{begin:"`",end:"`",relevance:0},{className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:'"',end:'"'},{begin:"'",end:"'"}]}]}}}()); +hljs.registerLanguage("scala",function(){"use strict";return function(e){var n={className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"},{begin:"\\${",end:"}"}]},a={className:"string",variants:[{begin:'"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:'"""',end:'"""',relevance:10},{begin:'[a-z]+"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE,n]},{className:"string",begin:'[a-z]+"""',end:'"""',contains:[n],relevance:10}]},s={className:"type",begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},t={className:"title",begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,relevance:0},i={className:"class",beginKeywords:"class object trait type",end:/[:={\[\n;]/,excludeEnd:!0,contains:[{beginKeywords:"extends with",relevance:10},{begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},t]},l={className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,excludeEnd:!0,contains:[t]};return{name:"Scala",keywords:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,{className:"symbol",begin:"'\\w[\\w\\d_]*(?!')"},s,l,i,e.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"}]}}}()); +hljs.registerLanguage("x86asm",function(){"use strict";return function(s){return{name:"Intel x86 Assembly",case_insensitive:!0,keywords:{$pattern:"[.%]?"+s.IDENT_RE,keyword:"lock rep repe repz repne repnz xaquire xrelease bnd nobnd aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63",built_in:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr",meta:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %if %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},contains:[s.COMMENT(";","$",{relevance:0}),{className:"number",variants:[{begin:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",relevance:0},{begin:"\\$[0-9][0-9A-Fa-f]*",relevance:0},{begin:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{begin:"\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"}]},s.QUOTE_STRING_MODE,{className:"string",variants:[{begin:"'",end:"[^\\\\]'"},{begin:"`",end:"[^\\\\]`"}],relevance:0},{className:"symbol",variants:[{begin:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)"},{begin:"^\\s*%%[A-Za-z0-9_$#@~.?]*:"}],relevance:0},{className:"subst",begin:"%[0-9]+",relevance:0},{className:"subst",begin:"%!S+",relevance:0},{className:"meta",begin:/^\s*\.[\w_-]+/}]}}}()); \ No newline at end of file diff --git a/zh-CN/index.html b/zh-CN/index.html new file mode 100644 index 000000000..7b5660ad6 --- /dev/null +++ b/zh-CN/index.html @@ -0,0 +1,219 @@ + + + + + + 介绍 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

介绍

+

Async-graphql是用 Rust 语言实现的 GraphQL 服务端库。它完全兼容 GraphQL 规范以及绝大部分的扩展功能,类型安全并且高性能。

+

你可以用 Rust 语言的方式来定义 Schema,过程宏会自动生成 GraphQL 查询的框架代码,没有扩展 Rust 的语法,意味着 Rustfmt 可以正常使用,我很看重这一点,这也是为什么我会开发Async-graphql的原因之一。

+

为什么我要开发 Async-graphql?

+

我喜欢 GraphQL 和 Rust,之前我一直用Juniper,它解决了我用 Rust 实现 GraphQL 服务器的问题,但也有一些遗憾,其中最重要的是它当时不支持 async/await,所以我决定做一个给自己用。

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/input_value_validators.html b/zh-CN/input_value_validators.html new file mode 100644 index 000000000..3f32a8b3f --- /dev/null +++ b/zh-CN/input_value_validators.html @@ -0,0 +1,306 @@ + + + + + + 输入值校验器 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

输入值校验器

+

Async-graphql内置了一些常用的校验器,你可以在对象字段的参数或者InputObject的字段上使用它们。

+
    +
  • maximum=N 指定数字不能大于N
  • +
  • minimum=N 指定数字不能小于N
  • +
  • multiple_of=N 指定数字必须是N的倍数
  • +
  • max_items=N 指定列表的长度不能大于N
  • +
  • min_items=N 指定列表的长度不能小于N
  • +
  • max_length=N 字符串的长度不能大于N
  • +
  • min_length=N 字符串的长度不能小于N
  • +
  • chars_max_length=N 字符串中 unicode 字符的的数量不能小于N
  • +
  • chars_min_length=N 字符串中 unicode 字符的的数量不能大于N
  • +
  • email 有效的 email
  • +
  • url 有效的 url
  • +
  • ip 有效的 ip 地址
  • +
  • regex=RE 匹配正则表达式
  • +
+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    /// name 参数的长度必须大于等于 5,小于等于 10
+    async fn input(&self, #[graphql(validator(min_length = 5, max_length = 10))] name: String) -> Result<i32> {
+       todo!()    
+    }
+}
+}
+

校验列表成员

+

你可以打开list属性表示校验器作用于一个列表内的所有成员:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn input(&self, #[graphql(validator(list, max_length = 10))] names: Vec<String>) -> Result<i32> {
+       todo!()
+    }
+}
+}
+

自定义校验器

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct MyValidator {
+    expect: i32,
+}
+
+impl MyValidator {
+    pub fn new(n: i32) -> Self {
+        MyValidator { expect: n }
+    }
+}
+
+impl CustomValidator<i32> for MyValidator {
+    fn check(&self, value: &i32) -> Result<(), InputValueError<i32>> {
+        if *value == self.expect {
+            Ok(())
+        } else {
+            Err(InputValueError::custom(format!("expect 100, actual {}", value)))
+        }
+    }
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    /// n 的值必须等于 100
+    async fn value(
+        &self,
+        #[graphql(validator(custom = "MyValidator::new(100)"))] n: i32,
+    ) -> i32 {
+        n
+    }
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/integrations.html b/zh-CN/integrations.html new file mode 100644 index 000000000..ff3b766ac --- /dev/null +++ b/zh-CN/integrations.html @@ -0,0 +1,230 @@ + + + + + + 集成到 WebServer - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

集成到 WebServer

+

Async-graphql提供了对一些常用 Web Server 的集成支持。

+ +

即使你目前使用的 Web Server 不在上面的列表中,自己实现类似的功能也相当的简单。

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/integrations_to_actix_web.html b/zh-CN/integrations_to_actix_web.html new file mode 100644 index 000000000..ef75eaa10 --- /dev/null +++ b/zh-CN/integrations_to_actix_web.html @@ -0,0 +1,265 @@ + + + + + + Actix-web - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Actix-web

+

Async-graphql-actix-web提供了GraphQLRequest提取器用于提取GraphQL请求,和GraphQLResponse用于输出GraphQL响应。

+

GraphQLSubscription用于创建一个支持 Web Socket 订阅的 Actor。

+

请求例子

+

你需要把 Schema 传入actix_web::App作为全局数据。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_actix_web;
+extern crate async_graphql;
+extern crate actix_web;
+use async_graphql::*;
+#[derive(Default,SimpleObject)]
+struct Query { a: i32 }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+use actix_web::{web, HttpRequest, HttpResponse};
+use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse};
+async fn index(
+    schema: web::Data<Schema<Query, EmptyMutation, EmptySubscription>>,
+    request: GraphQLRequest,
+) -> web::Json<GraphQLResponse> {
+    web::Json(schema.execute(request.into_inner()).await.into())
+}
+}
+

订阅例子

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_actix_web;
+extern crate async_graphql;
+extern crate actix_web;
+use async_graphql::*;
+#[derive(Default,SimpleObject)]
+struct Query { a: i32 }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+use actix_web::{web, HttpRequest, HttpResponse};
+use async_graphql_actix_web::GraphQLSubscription;
+async fn index_ws(
+    schema: web::Data<Schema<Query, EmptyMutation, EmptySubscription>>,
+    req: HttpRequest,
+    payload: web::Payload,
+) -> actix_web::Result<HttpResponse> {
+    GraphQLSubscription::new(Schema::clone(&*schema)).start(&req, payload)
+}
+}
+

更多例子

+

https://github.com/async-graphql/examples/tree/master/actix-web

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/integrations_to_poem.html b/zh-CN/integrations_to_poem.html new file mode 100644 index 000000000..b1aff1ae1 --- /dev/null +++ b/zh-CN/integrations_to_poem.html @@ -0,0 +1,255 @@ + + + + + + Poem - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Poem

+

请求例子

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_poem;
+extern crate async_graphql;
+extern crate poem;
+use async_graphql::*;
+#[derive(Default, SimpleObject)]
+struct Query { a: i32 }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+use poem::Route;
+use async_graphql_poem::GraphQL;
+
+let app = Route::new()
+    .at("/ws", GraphQL::new(schema));
+}
+

订阅例子

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_poem;
+extern crate async_graphql;
+extern crate poem;
+use async_graphql::*;
+#[derive(Default, SimpleObject)]
+struct Query { a: i32 }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+use poem::{get, Route};
+use async_graphql_poem::GraphQLSubscription;
+
+let app = Route::new()
+    .at("/ws", get(GraphQLSubscription::new(schema)));
+}
+

更多例子

+

https://github.com/async-graphql/examples/tree/master/poem

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/integrations_to_warp.html b/zh-CN/integrations_to_warp.html new file mode 100644 index 000000000..bfd2e9919 --- /dev/null +++ b/zh-CN/integrations_to_warp.html @@ -0,0 +1,278 @@ + + + + + + Warp - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Warp

+

Async-graphql-warp提供了两个Filtergraphqlgraphql_subscription

+

graphql用于执行QueryMutation请求,它提取 GraphQL 请求,然后输出一个包含async_graphql::Schemaasync_graphql::Request元组,你可以在之后组合其它 Filter,或者直接调用Schema::execute执行查询。

+

graphql_subscription用于实现基于 Web Socket 的订阅,它输出warp::Reply

+

请求例子

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_warp;
+extern crate async_graphql;
+extern crate warp;
+use async_graphql::*;
+use std::convert::Infallible;
+use warp::Filter;
+struct QueryRoot;
+#[Object]
+impl QueryRoot { async fn version(&self) -> &str { "1.0" } }
+async fn other() {
+type MySchema = Schema<QueryRoot, EmptyMutation, EmptySubscription>;
+
+let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription);
+let filter = async_graphql_warp::graphql(schema).and_then(|(schema, request): (MySchema, async_graphql::Request)| async move {
+    // 执行查询
+    let resp = schema.execute(request).await;
+
+    // 返回结果
+    Ok::<_, Infallible>(async_graphql_warp::GraphQLResponse::from(resp))
+});
+warp::serve(filter).run(([0, 0, 0, 0], 8000)).await;
+}
+}
+

订阅例子

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_warp;
+extern crate async_graphql;
+extern crate warp;
+use async_graphql::*;
+use futures_util::stream::{Stream, StreamExt};
+use std::convert::Infallible;
+use warp::Filter;
+struct SubscriptionRoot;
+#[Subscription]
+impl SubscriptionRoot {
+  async fn tick(&self) -> impl Stream<Item = i32> {
+    futures_util::stream::iter(0..10)
+  }
+}
+struct QueryRoot;
+#[Object]
+impl QueryRoot { async fn version(&self) -> &str { "1.0" } }
+async fn other() {
+let schema = Schema::new(QueryRoot, EmptyMutation, SubscriptionRoot);
+let filter = async_graphql_warp::graphql_subscription(schema);
+warp::serve(filter).run(([0, 0, 0, 0], 8000)).await;
+}
+}
+

更多例子

+

https://github.com/async-graphql/examples/tree/master/warp

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/introduction.html b/zh-CN/introduction.html new file mode 100644 index 000000000..7b5660ad6 --- /dev/null +++ b/zh-CN/introduction.html @@ -0,0 +1,219 @@ + + + + + + 介绍 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

介绍

+

Async-graphql是用 Rust 语言实现的 GraphQL 服务端库。它完全兼容 GraphQL 规范以及绝大部分的扩展功能,类型安全并且高性能。

+

你可以用 Rust 语言的方式来定义 Schema,过程宏会自动生成 GraphQL 查询的框架代码,没有扩展 Rust 的语法,意味着 Rustfmt 可以正常使用,我很看重这一点,这也是为什么我会开发Async-graphql的原因之一。

+

为什么我要开发 Async-graphql?

+

我喜欢 GraphQL 和 Rust,之前我一直用Juniper,它解决了我用 Rust 实现 GraphQL 服务器的问题,但也有一些遗憾,其中最重要的是它当时不支持 async/await,所以我决定做一个给自己用。

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/mark.min.js b/zh-CN/mark.min.js new file mode 100644 index 000000000..163623188 --- /dev/null +++ b/zh-CN/mark.min.js @@ -0,0 +1,7 @@ +/*!*************************************************** +* mark.js v8.11.1 +* https://markjs.io/ +* Copyright (c) 2014–2018, Julian Kühnel +* Released under the MIT license https://git.io/vwTVl +*****************************************************/ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Mark=t()}(this,function(){"use strict";var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},n=function(){function e(e,t){for(var n=0;n1&&void 0!==arguments[1])||arguments[1],i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:5e3;t(this,e),this.ctx=n,this.iframes=r,this.exclude=i,this.iframesTimeout=o}return n(e,[{key:"getContexts",value:function(){var e=[];return(void 0!==this.ctx&&this.ctx?NodeList.prototype.isPrototypeOf(this.ctx)?Array.prototype.slice.call(this.ctx):Array.isArray(this.ctx)?this.ctx:"string"==typeof this.ctx?Array.prototype.slice.call(document.querySelectorAll(this.ctx)):[this.ctx]:[]).forEach(function(t){var n=e.filter(function(e){return e.contains(t)}).length>0;-1!==e.indexOf(t)||n||e.push(t)}),e}},{key:"getIframeContents",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){},r=void 0;try{var i=e.contentWindow;if(r=i.document,!i||!r)throw new Error("iframe inaccessible")}catch(e){n()}r&&t(r)}},{key:"isIframeBlank",value:function(e){var t="about:blank",n=e.getAttribute("src").trim();return e.contentWindow.location.href===t&&n!==t&&n}},{key:"observeIframeLoad",value:function(e,t,n){var r=this,i=!1,o=null,a=function a(){if(!i){i=!0,clearTimeout(o);try{r.isIframeBlank(e)||(e.removeEventListener("load",a),r.getIframeContents(e,t,n))}catch(e){n()}}};e.addEventListener("load",a),o=setTimeout(a,this.iframesTimeout)}},{key:"onIframeReady",value:function(e,t,n){try{"complete"===e.contentWindow.document.readyState?this.isIframeBlank(e)?this.observeIframeLoad(e,t,n):this.getIframeContents(e,t,n):this.observeIframeLoad(e,t,n)}catch(e){n()}}},{key:"waitForIframes",value:function(e,t){var n=this,r=0;this.forEachIframe(e,function(){return!0},function(e){r++,n.waitForIframes(e.querySelector("html"),function(){--r||t()})},function(e){e||t()})}},{key:"forEachIframe",value:function(t,n,r){var i=this,o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},a=t.querySelectorAll("iframe"),s=a.length,c=0;a=Array.prototype.slice.call(a);var u=function(){--s<=0&&o(c)};s||u(),a.forEach(function(t){e.matches(t,i.exclude)?u():i.onIframeReady(t,function(e){n(t)&&(c++,r(e)),u()},u)})}},{key:"createIterator",value:function(e,t,n){return document.createNodeIterator(e,t,n,!1)}},{key:"createInstanceOnIframe",value:function(t){return new e(t.querySelector("html"),this.iframes)}},{key:"compareNodeIframe",value:function(e,t,n){if(e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_PRECEDING){if(null===t)return!0;if(t.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_FOLLOWING)return!0}return!1}},{key:"getIteratorNode",value:function(e){var t=e.previousNode();return{prevNode:t,node:null===t?e.nextNode():e.nextNode()&&e.nextNode()}}},{key:"checkIframeFilter",value:function(e,t,n,r){var i=!1,o=!1;return r.forEach(function(e,t){e.val===n&&(i=t,o=e.handled)}),this.compareNodeIframe(e,t,n)?(!1!==i||o?!1===i||o||(r[i].handled=!0):r.push({val:n,handled:!0}),!0):(!1===i&&r.push({val:n,handled:!1}),!1)}},{key:"handleOpenIframes",value:function(e,t,n,r){var i=this;e.forEach(function(e){e.handled||i.getIframeContents(e.val,function(e){i.createInstanceOnIframe(e).forEachNode(t,n,r)})})}},{key:"iterateThroughNodes",value:function(e,t,n,r,i){for(var o,a=this,s=this.createIterator(t,e,r),c=[],u=[],l=void 0,h=void 0;void 0,o=a.getIteratorNode(s),h=o.prevNode,l=o.node;)this.iframes&&this.forEachIframe(t,function(e){return a.checkIframeFilter(l,h,e,c)},function(t){a.createInstanceOnIframe(t).forEachNode(e,function(e){return u.push(e)},r)}),u.push(l);u.forEach(function(e){n(e)}),this.iframes&&this.handleOpenIframes(c,e,n,r),i()}},{key:"forEachNode",value:function(e,t,n){var r=this,i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},o=this.getContexts(),a=o.length;a||i(),o.forEach(function(o){var s=function(){r.iterateThroughNodes(e,o,t,n,function(){--a<=0&&i()})};r.iframes?r.waitForIframes(o,s):s()})}}],[{key:"matches",value:function(e,t){var n="string"==typeof t?[t]:t,r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector;if(r){var i=!1;return n.every(function(t){return!r.call(e,t)||(i=!0,!1)}),i}return!1}}]),e}(),o=function(){function e(n){t(this,e),this.opt=r({},{diacritics:!0,synonyms:{},accuracy:"partially",caseSensitive:!1,ignoreJoiners:!1,ignorePunctuation:[],wildcards:"disabled"},n)}return n(e,[{key:"create",value:function(e){return"disabled"!==this.opt.wildcards&&(e=this.setupWildcardsRegExp(e)),e=this.escapeStr(e),Object.keys(this.opt.synonyms).length&&(e=this.createSynonymsRegExp(e)),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),this.opt.diacritics&&(e=this.createDiacriticsRegExp(e)),e=this.createMergedBlanksRegExp(e),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.createJoinersRegExp(e)),"disabled"!==this.opt.wildcards&&(e=this.createWildcardsRegExp(e)),e=this.createAccuracyRegExp(e),new RegExp(e,"gm"+(this.opt.caseSensitive?"":"i"))}},{key:"escapeStr",value:function(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}},{key:"createSynonymsRegExp",value:function(e){var t=this.opt.synonyms,n=this.opt.caseSensitive?"":"i",r=this.opt.ignoreJoiners||this.opt.ignorePunctuation.length?"\0":"";for(var i in t)if(t.hasOwnProperty(i)){var o=t[i],a="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(i):this.escapeStr(i),s="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(o):this.escapeStr(o);""!==a&&""!==s&&(e=e.replace(new RegExp("("+this.escapeStr(a)+"|"+this.escapeStr(s)+")","gm"+n),r+"("+this.processSynonyms(a)+"|"+this.processSynonyms(s)+")"+r))}return e}},{key:"processSynonyms",value:function(e){return(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),e}},{key:"setupWildcardsRegExp",value:function(e){return(e=e.replace(/(?:\\)*\?/g,function(e){return"\\"===e.charAt(0)?"?":""})).replace(/(?:\\)*\*/g,function(e){return"\\"===e.charAt(0)?"*":""})}},{key:"createWildcardsRegExp",value:function(e){var t="withSpaces"===this.opt.wildcards;return e.replace(/\u0001/g,t?"[\\S\\s]?":"\\S?").replace(/\u0002/g,t?"[\\S\\s]*?":"\\S*")}},{key:"setupIgnoreJoinersRegExp",value:function(e){return e.replace(/[^(|)\\]/g,function(e,t,n){var r=n.charAt(t+1);return/[(|)\\]/.test(r)||""===r?e:e+"\0"})}},{key:"createJoinersRegExp",value:function(e){var t=[],n=this.opt.ignorePunctuation;return Array.isArray(n)&&n.length&&t.push(this.escapeStr(n.join(""))),this.opt.ignoreJoiners&&t.push("\\u00ad\\u200b\\u200c\\u200d"),t.length?e.split(/\u0000+/).join("["+t.join("")+"]*"):e}},{key:"createDiacriticsRegExp",value:function(e){var t=this.opt.caseSensitive?"":"i",n=this.opt.caseSensitive?["aàáảãạăằắẳẵặâầấẩẫậäåāą","AÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćč","CÇĆČ","dđď","DĐĎ","eèéẻẽẹêềếểễệëěēę","EÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïī","IÌÍỈĨỊÎÏĪ","lł","LŁ","nñňń","NÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøō","OÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rř","RŘ","sšśșş","SŠŚȘŞ","tťțţ","TŤȚŢ","uùúủũụưừứửữựûüůū","UÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿ","YÝỲỶỸỴŸ","zžżź","ZŽŻŹ"]:["aàáảãạăằắẳẵặâầấẩẫậäåāąAÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćčCÇĆČ","dđďDĐĎ","eèéẻẽẹêềếểễệëěēęEÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïīIÌÍỈĨỊÎÏĪ","lłLŁ","nñňńNÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøōOÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rřRŘ","sšśșşSŠŚȘŞ","tťțţTŤȚŢ","uùúủũụưừứửữựûüůūUÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿYÝỲỶỸỴŸ","zžżźZŽŻŹ"],r=[];return e.split("").forEach(function(i){n.every(function(n){if(-1!==n.indexOf(i)){if(r.indexOf(n)>-1)return!1;e=e.replace(new RegExp("["+n+"]","gm"+t),"["+n+"]"),r.push(n)}return!0})}),e}},{key:"createMergedBlanksRegExp",value:function(e){return e.replace(/[\s]+/gim,"[\\s]+")}},{key:"createAccuracyRegExp",value:function(e){var t=this,n=this.opt.accuracy,r="string"==typeof n?n:n.value,i="";switch(("string"==typeof n?[]:n.limiters).forEach(function(e){i+="|"+t.escapeStr(e)}),r){case"partially":default:return"()("+e+")";case"complementary":return"()([^"+(i="\\s"+(i||this.escapeStr("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~¡¿")))+"]*"+e+"[^"+i+"]*)";case"exactly":return"(^|\\s"+i+")("+e+")(?=$|\\s"+i+")"}}}]),e}(),a=function(){function a(e){t(this,a),this.ctx=e,this.ie=!1;var n=window.navigator.userAgent;(n.indexOf("MSIE")>-1||n.indexOf("Trident")>-1)&&(this.ie=!0)}return n(a,[{key:"log",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"debug",r=this.opt.log;this.opt.debug&&"object"===(void 0===r?"undefined":e(r))&&"function"==typeof r[n]&&r[n]("mark.js: "+t)}},{key:"getSeparatedKeywords",value:function(e){var t=this,n=[];return e.forEach(function(e){t.opt.separateWordSearch?e.split(" ").forEach(function(e){e.trim()&&-1===n.indexOf(e)&&n.push(e)}):e.trim()&&-1===n.indexOf(e)&&n.push(e)}),{keywords:n.sort(function(e,t){return t.length-e.length}),length:n.length}}},{key:"isNumeric",value:function(e){return Number(parseFloat(e))==e}},{key:"checkRanges",value:function(e){var t=this;if(!Array.isArray(e)||"[object Object]"!==Object.prototype.toString.call(e[0]))return this.log("markRanges() will only accept an array of objects"),this.opt.noMatch(e),[];var n=[],r=0;return e.sort(function(e,t){return e.start-t.start}).forEach(function(e){var i=t.callNoMatchOnInvalidRanges(e,r),o=i.start,a=i.end;i.valid&&(e.start=o,e.length=a-o,n.push(e),r=a)}),n}},{key:"callNoMatchOnInvalidRanges",value:function(e,t){var n=void 0,r=void 0,i=!1;return e&&void 0!==e.start?(r=(n=parseInt(e.start,10))+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&r-t>0&&r-n>0?i=!0:(this.log("Ignoring invalid or overlapping range: "+JSON.stringify(e)),this.opt.noMatch(e))):(this.log("Ignoring invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:n,end:r,valid:i}}},{key:"checkWhitespaceRanges",value:function(e,t,n){var r=void 0,i=!0,o=n.length,a=t-o,s=parseInt(e.start,10)-a;return(r=(s=s>o?o:s)+parseInt(e.length,10))>o&&(r=o,this.log("End range automatically set to the max value of "+o)),s<0||r-s<0||s>o||r>o?(i=!1,this.log("Invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)):""===n.substring(s,r).replace(/\s+/g,"")&&(i=!1,this.log("Skipping whitespace only range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:s,end:r,valid:i}}},{key:"getTextNodes",value:function(e){var t=this,n="",r=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,function(e){r.push({start:n.length,end:(n+=e.textContent).length,node:e})},function(e){return t.matchesExclude(e.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT},function(){e({value:n,nodes:r})})}},{key:"matchesExclude",value:function(e){return i.matches(e,this.opt.exclude.concat(["script","style","title","head","html"]))}},{key:"wrapRangeInTextNode",value:function(e,t,n){var r=this.opt.element?this.opt.element:"mark",i=e.splitText(t),o=i.splitText(n-t),a=document.createElement(r);return a.setAttribute("data-markjs","true"),this.opt.className&&a.setAttribute("class",this.opt.className),a.textContent=i.textContent,i.parentNode.replaceChild(a,i),o}},{key:"wrapRangeInMappedTextNode",value:function(e,t,n,r,i){var o=this;e.nodes.every(function(a,s){var c=e.nodes[s+1];if(void 0===c||c.start>t){if(!r(a.node))return!1;var u=t-a.start,l=(n>a.end?a.end:n)-a.start,h=e.value.substr(0,a.start),f=e.value.substr(l+a.start);if(a.node=o.wrapRangeInTextNode(a.node,u,l),e.value=h+f,e.nodes.forEach(function(t,n){n>=s&&(e.nodes[n].start>0&&n!==s&&(e.nodes[n].start-=l),e.nodes[n].end-=l)}),n-=l,i(a.node.previousSibling,a.start),!(n>a.end))return!1;t=a.end}return!0})}},{key:"wrapGroups",value:function(e,t,n,r){return r((e=this.wrapRangeInTextNode(e,t,t+n)).previousSibling),e}},{key:"separateGroups",value:function(e,t,n,r,i){for(var o=t.length,a=1;a-1&&r(t[a],e)&&(e=this.wrapGroups(e,s,t[a].length,i))}return e}},{key:"wrapMatches",value:function(e,t,n,r,i){var o=this,a=0===t?0:t+1;this.getTextNodes(function(t){t.nodes.forEach(function(t){t=t.node;for(var i=void 0;null!==(i=e.exec(t.textContent))&&""!==i[a];){if(o.opt.separateGroups)t=o.separateGroups(t,i,a,n,r);else{if(!n(i[a],t))continue;var s=i.index;if(0!==a)for(var c=1;c + + + + + 合并对象 (MergedObject) - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

合并对象 (MergedObject)

+

为同一类型实现多次 Object

+

通常我们在 Rust 中可以为同一类型创建多个实现,但由于过程宏的限制,无法为同一个类型创建多个 Object 实现。例如,下面的代码将无法通过编译。

+
#[Object]
+impl MyObject {
+    async fn field1(&self) -> i32 {
+        todo!()
+    }
+}
+
+#[Object]
+impl MyObject {
+    async fn field2(&self) -> i32 {
+        todo!()    
+    }
+}
+

#[derive(MergedObject)] 宏允许你合并多个独立的 Object 为一个。

+

提示: 每个#[Object]需要一个唯一的名称,即使在一个MergedObject内,所以确保每个对象有单独的名称。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct User { a: i32 }
+#[derive(SimpleObject)]
+struct Movie { a: i32 }
+#[derive(Default)]
+struct UserQuery;
+
+#[Object]
+impl UserQuery {
+    async fn users(&self) -> Vec<User> {
+        todo!()
+    }
+}
+
+#[derive(Default)]
+struct MovieQuery;
+
+#[Object]
+impl MovieQuery {
+    async fn movies(&self) -> Vec<Movie> {
+        todo!()
+    }
+}
+
+#[derive(MergedObject, Default)]
+struct Query(UserQuery, MovieQuery);
+
+let schema = Schema::new(
+    Query::default(),
+    EmptyMutation,
+    EmptySubscription
+);
+}
+
+

⚠️ 合并的对象无法在 Interface 中使用。

+
+

合并订阅

+

MergedObject一样,你可以派生MergedSubscription来合并单独的#[Subscription]块。

+

像合并对象一样,每个订阅块都需要一个唯一的名称。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use futures_util::stream::{Stream};
+#[derive(Default,SimpleObject)]
+struct Query { a: i32 }
+#[derive(Default)]
+struct Subscription1;
+
+#[Subscription]
+impl Subscription1 {
+    async fn events1(&self) -> impl Stream<Item = i32> {
+        futures_util::stream::iter(0..10)
+    }
+}
+
+#[derive(Default)]
+struct Subscription2;
+
+#[Subscription]
+impl Subscription2 {
+    async fn events2(&self) -> impl Stream<Item = i32> {
+        futures_util::stream::iter(10..20)
+    }
+}
+
+#[derive(MergedSubscription, Default)]
+struct Subscription(Subscription1, Subscription2);
+
+let schema = Schema::new(
+    Query::default(),
+    EmptyMutation,
+    Subscription::default()
+);
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/print.html b/zh-CN/print.html new file mode 100644 index 000000000..8d9438e43 --- /dev/null +++ b/zh-CN/print.html @@ -0,0 +1,2562 @@ + + + + + + Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

介绍

+

Async-graphql是用 Rust 语言实现的 GraphQL 服务端库。它完全兼容 GraphQL 规范以及绝大部分的扩展功能,类型安全并且高性能。

+

你可以用 Rust 语言的方式来定义 Schema,过程宏会自动生成 GraphQL 查询的框架代码,没有扩展 Rust 的语法,意味着 Rustfmt 可以正常使用,我很看重这一点,这也是为什么我会开发Async-graphql的原因之一。

+

为什么我要开发 Async-graphql?

+

我喜欢 GraphQL 和 Rust,之前我一直用Juniper,它解决了我用 Rust 实现 GraphQL 服务器的问题,但也有一些遗憾,其中最重要的是它当时不支持 async/await,所以我决定做一个给自己用。

+

快速开始

+

添加依赖

+
[dependencies]
+async-graphql = "4.0"
+async-graphql-actix-web = "4.0" # 如果你需要集成到 Actix-web
+async-graphql-warp = "4.0" # 如果你需要集成到 Warp
+async-graphql-tide = "4.0" # 如果你需要集成到 Tide
+
+

写一个 Schema

+

一个 GraphQL 的 Schema 包含一个必须的查询 (Query) 根对象,可选的变更 (Mutation) 根对象和可选的订阅 (Subscription) 根对象,这些对象类型都是用 Rust 语言的结构来描述它们,结构的字段对应 GraphQL 对象的字段。

+

Async-graphql 实现了常用数据类型到 GraphQL 类型的映射,例如i32, f64, Option<T>, Vec<T>等。同时,你也能够扩展这些基础类型,基础数据类型在 GraphQL 里面称为标量。

+

下面是一个简单的例子,我们只提供一个查询,返回ab的和。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    /// Returns the sum of a and b
+    async fn add(&self, a: i32, b: i32) -> i32 {
+        a + b
+    }
+}
+
+}
+

执行查询

+

在我们这个例子里面,只有 Query,没有 Mutation 和 Subscription,所以我们用EmptyMutationEmptySubscription来创建 Schema,然后调用Schema::execute来执行查询。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+#[Object]
+impl Query {
+  async fn version(&self) -> &str { "1.0" }    
+}
+async fn other() {
+let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
+let res = schema.execute("{ add(a: 10, b: 20) }").await;
+}
+}
+

把查询结果输出为 JSON

+
let json = serde_json::to_string(&res);
+

和 Web Server 的集成

+

请参考 https://github.com/async-graphql/examples。

+

类型系统

+

Async-graphql包含 GraphQL 类型到 Rust 类型的完整实现,并且非常易于使用。

+

简单对象 (SimpleObject)

+

简单对象是把 Rust 结构的所有字段都直接映射到 GraphQL 对象,不支持定义单独的 Resolver 函数。

+

下面的例子定义了一个名称为 MyObject 的对象,包含字段abc由于标记为#[graphql(skip)],所以不会映射到 GraphQL。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+#[derive(SimpleObject)]
+struct MyObject {
+    /// Value a
+    a: i32,
+    
+    /// Value b
+    b: i32,
+
+    #[graphql(skip)]
+    c: i32,
+}
+}
+

泛型

+

如果你希望其它类型能够重用SimpleObject,则可以定义泛型的SimpleObject,并指定具体的类型。

+

在下面的示例中,创建了两种SimpleObject类型:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct SomeType { a: i32 }
+#[derive(SimpleObject)]
+struct SomeOtherType { a: i32 }
+#[derive(SimpleObject)]
+#[graphql(concrete(name = "SomeName", params(SomeType)))]
+#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))]
+pub struct SomeGenericObject<T: OutputType> {
+    field1: Option<T>,
+    field2: String
+}
+}
+

注意:每个泛型参数必须实现OutputType,如上所示。

+

生成的 SDL 如下:

+
type SomeName {
+  field1: SomeType
+  field2: String!
+}
+
+type SomeOtherName {
+  field1: SomeOtherType
+  field2: String!
+}
+
+

在其它Object中使用具体的泛型类型:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct SomeType { a: i32 }
+#[derive(SimpleObject)]
+struct SomeOtherType { a: i32 }
+#[derive(SimpleObject)]
+#[graphql(concrete(name = "SomeName", params(SomeType)))]
+#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))]
+pub struct SomeGenericObject<T: OutputType> {
+    field1: Option<T>,
+    field2: String,
+}
+#[derive(SimpleObject)]
+pub struct YetAnotherObject {
+    a: SomeGenericObject<SomeType>,
+    b: SomeGenericObject<SomeOtherType>,
+}
+}
+

你可以将多个通用类型传递给params(),并用逗号分隔。

+

复杂字段

+

有时 GraphQL 对象的大多数字段仅返回结构成员的值,但是少数字段需要计算。通常我们使用Object宏来定义这样一个 GraphQL 对象。

+

ComplexObject宏可以更漂亮的完成这件事,我们可以使用SimpleObject宏来定义 +一些简单的字段,并使用ComplexObject宏来定义其他一些需要计算的字段。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+#[graphql(complex)] // 注意:如果你希望 ComplexObject 宏生效,complex 属性是必须的
+struct MyObj {
+    a: i32,
+    b: i32,
+}
+
+#[ComplexObject]
+impl MyObj {
+    async fn c(&self) -> i32 {
+        self.a + self.b     
+    }
+}
+}
+

同时用于输入和输出

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject, InputObject)]
+#[graphql(input_name = "MyObjInput")] // 注意:你必须用 input_name 属性为输入类型定义一个新的名称,否则将产生一个运行时错误。
+struct MyObj {
+    a: i32,
+    b: i32,
+}
+}
+

对象 (Object)

+

和简单对象不同,对象必须为所有的字段定义 Resolver 函数,Resolver 函数定义在 impl 块中。

+

一个 Resolver 函数必须是异步的,它的第一个参数必须是&self,第二个参数是可选的Context,接下来是字段的参数。

+

Resolver 函数用于计算字段的值,你可以执行一个数据库查询,并返回查询结果。函数的返回值是字段的类型,你也可以返回一个async_graphql::Result类型,这样能够返回一个错误,这个错误信息将输出到查询结果中。

+

在查询数据库时,你可能需要一个数据库连接池对象,这个对象是个全局的,你可以在创建 Schema 的时候,用SchemaBuilder::data函数设置Schema数据,用Context::data函数设置Context数据。下面的value_from_db字段展示了如何从Context中获取一个数据库连接。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+struct Data { pub name: String }
+struct DbConn {}
+impl DbConn {
+  fn query_something(&self, id: i64) -> std::result::Result<Data, String> { Ok(Data {name:"".into()})}
+}
+struct DbPool {}
+impl DbPool {
+  fn take(&self) -> DbConn { DbConn {} }    
+}
+use async_graphql::*;
+
+struct MyObject {
+    value: i32,
+}
+
+#[Object]
+impl MyObject {
+    async fn value(&self) -> String {
+        self.value.to_string()
+    }
+
+    async fn value_from_db(
+        &self,
+        ctx: &Context<'_>,
+        #[graphql(desc = "Id of object")] id: i64
+    ) -> Result<String> {
+        let conn = ctx.data::<DbPool>()?.take();
+        Ok(conn.query_something(id)?.name)
+    }
+}
+}
+

查询上下文 (Context)

+

Context的主要目标是获取附加到Schema的全局数据或者与正在处理的实际查询相关的数据。

+

存储数据

+

Context中你可以存放全局数据,例如环境变量、数据库连接池,以及你在每个查询中可能需要的任何内容。

+

数据必须实现SendSync

+

你可以通过调用ctx.data::<TypeOfYourData>()来获取查询中的数据。

+

主意:如果 Resolver 函数的返回值是从Context中借用的,则需要明确说明参数的生命周期。

+

下面的例子展示了如何从Context中借用数据。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn borrow_from_context_data<'ctx>(
+        &self,
+        ctx: &Context<'ctx>
+    ) -> Result<&'ctx String> {
+        ctx.data::<String>()
+    }
+}
+}
+

Schema 数据

+

你可以在创建Schema时将数据放入上下文中,这对于不会更改的数据非常有用,例如连接池。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Default,SimpleObject)]
+struct Query { version: i32}
+struct EnvStruct;
+let env_struct = EnvStruct;
+struct S3Object;
+let s3_storage = S3Object;
+struct DBConnection;
+let db_core = DBConnection;
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription)
+    .data(env_struct)
+    .data(s3_storage)
+    .data(db_core)
+    .finish();
+}
+

请求数据

+

你可以在执行请求时将数据放入上下文中,它对于身份验证数据很有用。

+

一个使用warp的小例子:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate async_graphql_warp;
+extern crate warp;
+use async_graphql::*;
+use warp::{Filter, Reply};
+use std::convert::Infallible;
+#[derive(Default, SimpleObject)]
+struct Query { name: String }
+struct AuthInfo { pub token: Option<String> }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+let schema_filter = async_graphql_warp::graphql(schema);
+let graphql_post = warp::post()
+  .and(warp::path("graphql"))
+  .and(warp::header::optional("Authorization"))
+  .and(schema_filter)
+  .and_then( |auth: Option<String>, (schema, mut request): (Schema<Query, EmptyMutation, EmptySubscription>, async_graphql::Request)| async move {
+    // Do something to get auth data from the header
+    let your_auth_data = AuthInfo { token: auth };
+    let response = schema
+      .execute(
+        request
+         .data(your_auth_data)
+      ).await;
+      
+    Ok::<_, Infallible>(async_graphql_warp::GraphQLResponse::from(response))
+  });
+}
+

HTTP 头

+

使用Context你还可以插入或添加 HTTP 头。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate http;
+use ::http::header::ACCESS_CONTROL_ALLOW_ORIGIN;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query {
+    async fn greet(&self, ctx: &Context<'_>) -> String {
+        // Headers can be inserted using the `http` constants
+        let was_in_headers = ctx.insert_http_header(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
+
+        // They can also be inserted using &str
+        let was_in_headers = ctx.insert_http_header("Custom-Header", "1234");
+
+        // If multiple headers with the same key are `inserted` then the most recent
+        // one overwrites the previous. If you want multiple headers for the same key, use
+        // `append_http_header` for subsequent headers
+        let was_in_headers = ctx.append_http_header("Custom-Header", "Hello World");
+
+        String::from("Hello world")
+    }
+}
+}
+

Selection / LookAhead

+

有时你想知道子查询中请求了哪些字段用于优化数据处理,则可以使用ctx.field()读取查询中的字段,它将提供一个SelectionField,允许你在当前字段和子字段之间导航。

+

如果要跨查询或子查询执行搜索,则不必使用 SelectionField 手动执行此操作,可以使用 ctx.look_ahead() 来执行选择。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+#[derive(SimpleObject)]
+struct Detail {
+    c: i32,
+    d: i32,
+}
+
+#[derive(SimpleObject)]
+struct MyObj {
+    a: i32,
+    b: i32,
+    detail: Detail,
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn obj(&self, ctx: &Context<'_>) -> MyObj {
+        if ctx.look_ahead().field("a").exists() {
+            // This is a query like `obj { a }`
+        } else if ctx.look_ahead().field("detail").field("c").exists() {
+            // This is a query like `obj { detail { c } }`
+        } else {
+            // This query doesn't have `a`
+        }
+        unimplemented!()
+    }
+}
+}
+

错误处理

+

Resolver 函数可以返回一个 Result 类型,以下是 Result 的定义:

+
type Result<T> = std::result::Result<T, Error>;
+

任何错误都能够被转换为Error,并且你还能扩展标准的错误信息。

+

下面是一个例子,解析一个输入的字符串到整数,当解析失败时返回错误,并且附加额外的错误信息。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use std::num::ParseIntError;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn parse_with_extensions(&self, input: String) -> Result<i32> {
+        Ok("234a"
+            .parse()
+            .map_err(|err: ParseIntError| err.extend_with(|_, e| e.set("code", 400)))?)
+    }
+}
+}
+

合并对象 (MergedObject)

+

为同一类型实现多次 Object

+

通常我们在 Rust 中可以为同一类型创建多个实现,但由于过程宏的限制,无法为同一个类型创建多个 Object 实现。例如,下面的代码将无法通过编译。

+
#[Object]
+impl MyObject {
+    async fn field1(&self) -> i32 {
+        todo!()
+    }
+}
+
+#[Object]
+impl MyObject {
+    async fn field2(&self) -> i32 {
+        todo!()    
+    }
+}
+

#[derive(MergedObject)] 宏允许你合并多个独立的 Object 为一个。

+

提示: 每个#[Object]需要一个唯一的名称,即使在一个MergedObject内,所以确保每个对象有单独的名称。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct User { a: i32 }
+#[derive(SimpleObject)]
+struct Movie { a: i32 }
+#[derive(Default)]
+struct UserQuery;
+
+#[Object]
+impl UserQuery {
+    async fn users(&self) -> Vec<User> {
+        todo!()
+    }
+}
+
+#[derive(Default)]
+struct MovieQuery;
+
+#[Object]
+impl MovieQuery {
+    async fn movies(&self) -> Vec<Movie> {
+        todo!()
+    }
+}
+
+#[derive(MergedObject, Default)]
+struct Query(UserQuery, MovieQuery);
+
+let schema = Schema::new(
+    Query::default(),
+    EmptyMutation,
+    EmptySubscription
+);
+}
+
+

⚠️ 合并的对象无法在 Interface 中使用。

+
+

合并订阅

+

MergedObject一样,你可以派生MergedSubscription来合并单独的#[Subscription]块。

+

像合并对象一样,每个订阅块都需要一个唯一的名称。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use futures_util::stream::{Stream};
+#[derive(Default,SimpleObject)]
+struct Query { a: i32 }
+#[derive(Default)]
+struct Subscription1;
+
+#[Subscription]
+impl Subscription1 {
+    async fn events1(&self) -> impl Stream<Item = i32> {
+        futures_util::stream::iter(0..10)
+    }
+}
+
+#[derive(Default)]
+struct Subscription2;
+
+#[Subscription]
+impl Subscription2 {
+    async fn events2(&self) -> impl Stream<Item = i32> {
+        futures_util::stream::iter(10..20)
+    }
+}
+
+#[derive(MergedSubscription, Default)]
+struct Subscription(Subscription1, Subscription2);
+
+let schema = Schema::new(
+    Query::default(),
+    EmptyMutation,
+    Subscription::default()
+);
+}
+

派生字段

+

有时两个字段有一样的查询逻辑,仅仅是输出的类型不同,在 async-graphql 中,你可以为它创建派生字段。

+

在以下例子中,你已经有一个duration_rfc2822字段输出RFC2822格式的时间格式,然后复用它派生一个新的date_rfc3339字段。

+
#![allow(unused)]
+fn main() {
+extern crate chrono;
+use chrono::Utc;
+extern crate async_graphql;
+use async_graphql::*;
+struct DateRFC3339(chrono::DateTime<Utc>);
+struct DateRFC2822(chrono::DateTime<Utc>);
+
+#[Scalar]
+impl ScalarType for DateRFC3339 {
+  fn parse(value: Value) -> InputValueResult<Self> { todo!() } 
+
+  fn to_value(&self) -> Value {
+    Value::String(self.0.to_rfc3339())
+  }
+}
+
+#[Scalar]
+impl ScalarType for DateRFC2822 {
+  fn parse(value: Value) -> InputValueResult<Self> { todo!() } 
+
+  fn to_value(&self) -> Value {
+    Value::String(self.0.to_rfc2822())
+  }
+}
+
+impl From<DateRFC2822> for DateRFC3339 {
+    fn from(value: DateRFC2822) -> Self {
+      DateRFC3339(value.0)
+    }
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    #[graphql(derived(name = "date_rfc3339", into = "DateRFC3339"))]
+    async fn duration_rfc2822(&self, arg: String) -> DateRFC2822 {
+        todo!()
+    }
+}
+}
+

它将呈现为如下 GraphQL:

+
type Query {
+	duration_rfc2822(arg: String): DateRFC2822!
+	duration_rfc3339(arg: String): DateRFC3339!
+}
+
+

包装类型

+

因为 孤儿规则,以下代码无法通过编译:

+
impl From<Vec<U>> for Vec<T> {
+  ...
+}
+

因此,你将无法为现有的包装类型结构(如VecOption)生成派生字段。 +但是当你为 T 实现了 From<U> 后,你可以为 Vec<T> 实现 From<Vec<U>>,为 Option<T> 实现 From<Option<U>>. +使用 with 参数来定义一个转换函数,而不是用 Into::into

+

Example

+
#![allow(unused)]
+fn main() {
+extern crate serde;
+use serde::{Serialize, Deserialize};
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Serialize, Deserialize, Clone)]
+struct ValueDerived(String);
+
+#[derive(Serialize, Deserialize, Clone)]
+struct ValueDerived2(String);
+
+scalar!(ValueDerived);
+scalar!(ValueDerived2);
+
+impl From<ValueDerived> for ValueDerived2 {
+    fn from(value: ValueDerived) -> Self {
+        ValueDerived2(value.0)
+    }
+}
+
+fn option_to_option<T, U: From<T>>(value: Option<T>) -> Option<U> {
+    value.map(|x| x.into())
+}
+
+#[derive(SimpleObject)]
+struct TestObj {
+    #[graphql(derived(owned, name = "value2", into = "Option<ValueDerived2>", with = "option_to_option"))]
+    pub value1: Option<ValueDerived>,
+}
+}
+

枚举 (Enum)

+

定义枚举相当简单,直接给出一个例子。

+

Async-graphql 会自动把枚举项的名称转换为 GraphQL 标准的大写加下划线形式,你也可以用name属性自已定义名称。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+/// One of the films in the Star Wars Trilogy
+#[derive(Enum, Copy, Clone, Eq, PartialEq)]
+pub enum Episode {
+    /// Released in 1977.
+    NewHope,
+
+    /// Released in 1980.
+    Empire,
+
+    /// Released in 1983.
+    #[graphql(name="AAA")]
+    Jedi,
+}
+}
+

封装外部枚举类型

+

Rust 的 孤儿规则 要求特质或您要实现特质的类型必须在相同的板条箱中定义,因此你不能向 GraphQL 公开外部枚举类型。为了创建Enum类型,一种常见的解决方法是创建一个新的与现有远程枚举类型同等的枚举。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+mod remote_crate { pub enum RemoteEnum { A, B, C } }
+use async_graphql::*;
+
+/// Provides parity with a remote enum type
+#[derive(Enum, Copy, Clone, Eq, PartialEq)]
+pub enum LocalEnum {
+    A,
+    B,
+    C,
+}
+
+/// Conversion interface from remote type to our local GraphQL enum type
+impl From<remote_crate::RemoteEnum> for LocalEnum {
+    fn from(e: remote_crate::RemoteEnum) -> Self {
+        match e {
+            remote_crate::RemoteEnum::A => Self::A,
+            remote_crate::RemoteEnum::B => Self::B,
+            remote_crate::RemoteEnum::C => Self::C,
+        }
+    }
+}
+}
+

该过程很繁琐,需要多个步骤才能使本地枚举和远程枚举保持同步。Async_graphql提供了一个方便的功能,可在派生Enum之后通过附加属性生成 LocalEnum 的From <remote_crate::RemoteEnum>以及相反的From<LocalEnum> for remote_crate::RemoteEnum:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+mod remote_crate { pub enum RemoteEnum { A, B, C } }
+#[derive(Enum, Copy, Clone, Eq, PartialEq)]
+#[graphql(remote = "remote_crate::RemoteEnum")]
+enum LocalEnum {
+    A,
+    B,
+    C,
+}
+}
+

接口 (Interface)

+

接口用于抽象具有特定字段集合的对象,Async-graphql内部实现实际上是一个包装器,包装器转发接口上定义的 Resolver 函数到实现该接口的对象,所以接口类型所包含的字段类型,参数都必须和实现该接口的对象完全匹配。

+

Async-graphql自动实现了对象到接口的转换,把一个对象类型转换为接口类型只需要调用Into::into

+

接口字段的name属性表示转发的 Resolver 函数,并且它将被转换为驼峰命名作为字段名称。 +如果你需要自定义 GraphQL 接口字段名称,可以同时使用namemethod属性。

+
    +
  • namemethod属性同时存在时,name是 GraphQL 接口字段名,而method是 Resolver 函数名。
  • +
  • 当只有name存在时,转换为驼峰命名后的name是 GraphQL 接口字段名,而name是 Resolver 函数名。
  • +
+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Circle {
+    radius: f32,
+}
+
+#[Object]
+impl Circle {
+    async fn area(&self) -> f32 {
+        std::f32::consts::PI * self.radius * self.radius
+    }
+
+    async fn scale(&self, s: f32) -> Shape {
+        Circle { radius: self.radius * s }.into()
+    }
+
+    #[graphql(name = "short_description")]
+    async fn short_description(&self) -> String {
+        "Circle".to_string()
+    }
+}
+
+struct Square {
+    width: f32,
+}
+
+#[Object]
+impl Square {
+    async fn area(&self) -> f32 {
+        self.width * self.width
+    }
+
+    async fn scale(&self, s: f32) -> Shape {
+        Square { width: self.width * s }.into()
+    }
+
+    #[graphql(name = "short_description")]
+    async fn short_description(&self) -> String {
+        "Square".to_string()
+    }
+}
+
+#[derive(Interface)]
+#[graphql(
+    field(name = "area", ty = "f32"),
+    field(name = "scale", ty = "Shape", arg(name = "s", ty = "f32")),
+    field(name = "short_description", method = "short_description", ty = "String")
+)]
+enum Shape {
+    Circle(Circle),
+    Square(Square),
+}
+}
+

手工注册接口类型

+

Async-graphql在初始化阶段从Schema开始遍历并注册所有被直接或者间接引用的类型,如果某个接口没有被引用到,那么它将不会存在于注册表中,就像下面的例子, +即使MyObject实现了MyInterface,但由于Schema中并没有引用MyInterface,类型注册表中将不会存在MyInterface类型的信息。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Interface)]
+#[graphql(
+    field(name = "name", ty = "String"),
+)]
+enum MyInterface {
+    MyObject(MyObject),
+}
+
+#[derive(SimpleObject)]
+struct MyObject {
+    name: String,
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn obj(&self) -> MyObject {
+        todo!()
+    }
+}
+
+type MySchema = Schema<Query, EmptyMutation, EmptySubscription>;
+}
+

你需要在构造 Schema 时手工注册MyInterface类型:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Interface)]
+#[graphql(field(name = "name", ty = "String"))]
+enum MyInterface { MyObject(MyObject) }
+#[derive(SimpleObject)]
+struct MyObject { name: String, }
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+
+Schema::build(Query, EmptyMutation, EmptySubscription)
+    .register_output_type::<MyInterface>()
+    .finish();
+}
+

联合 (Union)

+

联合的定义和接口非常像,但不允许定义字段。这两个类型的实现原理也差不多,对于Async-graphql来说,联合类型是接口类型的子集。

+

下面把接口定义的例子做一个小小的修改,去掉字段的定义。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Circle {
+    radius: f32,
+}
+
+#[Object]
+impl Circle {
+    async fn area(&self) -> f32 {
+        std::f32::consts::PI * self.radius * self.radius
+    }
+
+    async fn scale(&self, s: f32) -> Shape {
+        Circle { radius: self.radius * s }.into()
+    }
+}
+
+struct Square {
+    width: f32,
+}
+
+#[Object]
+impl Square {
+    async fn area(&self) -> f32 {
+        self.width * self.width
+    }
+
+    async fn scale(&self, s: f32) -> Shape {
+        Square { width: self.width * s }.into()
+    }
+}
+
+#[derive(Union)]
+enum Shape {
+    Circle(Circle),
+    Square(Square),
+}
+}
+

展平嵌套联合

+

GraphQL 的有个限制是Union类型内不能包含其它联合类型。所有成员必须为Object。 +位置支持嵌套Union,我们可以用#graphql(flatten),是它们合并到上级Union类型。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+#[derive(async_graphql::Union)]
+pub enum TopLevelUnion {
+    A(A),
+
+    // 除非我们使用 `flatten` 属性,否则将无法编译
+    #[graphql(flatten)]
+    B(B),
+}
+
+#[derive(async_graphql::SimpleObject)]
+pub struct A {
+    a: i32,
+    // ...
+}
+
+#[derive(async_graphql::Union)]
+pub enum B {
+    C(C),
+    D(D),
+}
+
+#[derive(async_graphql::SimpleObject)]
+pub struct C {
+    c: i32,
+    // ...
+}
+
+#[derive(async_graphql::SimpleObject)]
+pub struct D {
+    d: i32,
+    // ...
+}
+}
+

上面的示例将顶级Union转换为以下等效形式:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+#[derive(async_graphql::SimpleObject)]
+struct A { a: i32 }
+#[derive(async_graphql::SimpleObject)]
+struct C { c: i32 }
+#[derive(async_graphql::SimpleObject)]
+struct D { d: i32 }
+#[derive(async_graphql::Union)]
+pub enum TopLevelUnion {
+    A(A),
+    C(C),
+    D(D),
+}
+}
+

输入对象 (InputObject)

+

你可以定义一个对象作为参数类型,GraphQL 称之为Input Object,输入对象的定义方式和简单对象很像,不同的是,简单对象只能用于输出,而输入对象只能用于输入。

+

你也通过可选的#[graphql]属性来给字段添加描述,重命名。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+#[derive(SimpleObject)]
+struct User { a: i32 }
+use async_graphql::*;
+
+#[derive(InputObject)]
+struct Coordinate {
+    latitude: f64,
+    longitude: f64,
+}
+
+struct Mutation;
+
+#[Object]
+impl Mutation {
+    async fn users_at_location(&self, coordinate: Coordinate, radius: f64) -> Vec<User> {
+        // 将坐标写入数据库
+        // ...
+      todo!()
+    }
+}
+}
+

泛型

+

如果你希望其它类型能够重用InputObject,则可以定义泛型的InputObject,并指定具体的类型。

+

在下面的示例中,创建了两种InputObject类型:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(InputObject)]
+struct SomeType { a: i32 }
+#[derive(InputObject)]
+struct SomeOtherType { a: i32 }
+#[derive(InputObject)]
+#[graphql(concrete(name = "SomeName", params(SomeType)))]
+#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))]
+pub struct SomeGenericInput<T: InputType> {
+    field1: Option<T>,
+    field2: String
+}
+}
+

注意:每个泛型参数必须实现InputType,如上所示。

+

生成的 SDL 如下:

+
input SomeName {
+  field1: SomeType
+  field2: String!
+}
+
+input SomeOtherName {
+  field1: SomeOtherType
+  field2: String!
+}
+
+

在其它InputObject中使用具体的泛型类型:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(InputObject)]
+struct SomeType { a: i32 }
+#[derive(InputObject)]
+struct SomeOtherType { a: i32 }
+#[derive(InputObject)]
+#[graphql(concrete(name = "SomeName", params(SomeType)))]
+#[graphql(concrete(name = "SomeOtherName", params(SomeOtherType)))]
+pub struct SomeGenericInput<T: InputType> {
+    field1: Option<T>,
+    field2: String
+}
+#[derive(InputObject)]
+pub struct YetAnotherInput {
+    a: SomeGenericInput<SomeType>,
+    b: SomeGenericInput<SomeOtherType>,
+}
+}
+

你可以将多个通用类型传递给params(),并用逗号分隔。

+

默认值

+

你可以为输入值类型定义默认值,下面展示了在不同类型上默认值的定义方法。

+

对象字段参数

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+fn my_default() -> i32 {
+    30
+}
+
+#[Object]
+impl Query {
+    // value 参数的默认值为 0,它会调用 i32::default()
+    async fn test1(&self, #[graphql(default)] value: i32) -> i32 { todo!() }
+
+    // value 参数的默认值为 10
+    async fn test2(&self, #[graphql(default = 10)] value: i32) -> i32 { todo!() }
+
+    // value 参数的默认值使用 my_default 函数的返回结果,值为 30
+    async fn test3(&self, #[graphql(default_with = "my_default()")] value: i32) -> i32 { todo!() }
+}
+}
+

接口字段参数

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+fn my_default() -> i32 { 5 }
+struct MyObj;
+#[Object]
+impl MyObj {
+   async fn test1(&self, value: i32) -> i32 { todo!() }
+   async fn test2(&self, value: i32) -> i32 { todo!() }
+   async fn test3(&self, value: i32) -> i32 { todo!() }
+}
+use async_graphql::*;
+
+#[derive(Interface)]
+#[graphql(
+    field(name = "test1", ty = "i32", arg(name = "value", ty = "i32", default)),
+    field(name = "test2", ty = "i32", arg(name = "value", ty = "i32", default = 10)),
+    field(name = "test3", ty = "i32", arg(name = "value", ty = "i32", default_with = "my_default()")),
+)]
+enum MyInterface {
+    MyObj(MyObj),
+}
+}
+

输入对象 (InputObject)

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+fn my_default() -> i32 { 5 }
+use async_graphql::*;
+
+#[derive(InputObject)]
+struct MyInputObject {
+    #[graphql(default)]
+    value1: i32,
+
+    #[graphql(default = 10)]
+    value2: i32,
+
+    #[graphql(default_with = "my_default()")]
+    value3: i32,
+}
+}
+

定义模式 (Schema)

+

在定义了基本的类型之后,需要定义一个模式把他们组合起来,模式由三种类型组成,查询对象,变更对象和订阅对象,其中变更对象和订阅对象是可选的。

+

当模式创建时,Async-graphql会遍历所有对象图,并注册所有类型。这意味着,如果定义了 GraphQL 对象但从未引用,那么此对象就不会暴露在模式中。

+

查询和变更

+

查询根对象

+

查询根对象是一个 GraphQL 对象,定义类似其它对象。查询对象的所有字段 Resolver 函数是并发执行的。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct User { a: i32 }
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn user(&self, username: String) -> Result<Option<User>> {
+        // 在数据库中查找用户
+       todo!()
+    }
+}
+
+}
+

变更根对象

+

变更根对象也是一个 GraphQL,但变更根对象的执行是顺序的,只有第一个变更执行完成之后才会执行下一个。

+

下面的变更根对象提供用户注册和登录操作:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Mutation;
+
+#[Object]
+impl Mutation {
+    async fn signup(&self, username: String, password: String) -> Result<bool> {
+        // 用户注册
+       todo!()
+    }
+
+    async fn login(&self, username: String, password: String) -> Result<String> {
+        // 用户登录并生成 token
+       todo!()
+    }
+}
+}
+

订阅

+

订阅根对象和其它根对象定义稍有不同,它的 Resolver 函数总是返回一个 Stream 或者Result<Stream>,而字段参数通常作为数据的筛选条件。

+

下面的例子订阅一个整数流,它每秒产生一个整数,参数step指定了整数的步长,默认为 1。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use std::time::Duration;
+use async_graphql::futures_util::stream::Stream;
+use async_graphql::futures_util::StreamExt;
+extern crate tokio_stream;
+extern crate tokio;
+use async_graphql::*;
+
+struct Subscription;
+
+#[Subscription]
+impl Subscription {
+    async fn integers(&self, #[graphql(default = 1)] step: i32) -> impl Stream<Item = i32> {
+        let mut value = 0;
+        tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(Duration::from_secs(1)))
+            .map(move |_| {
+                value += step;
+                value
+            })
+    }
+}
+}
+

实用功能

+

字段守卫 (Field Guard)

+

你可以为Object, SimpleObject, ComplexObjectSubscription的字段定义守卫,它将在调用字段的 Resolver 函数前执行,如果失败则返回一个错误。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Eq, PartialEq, Copy, Clone)]
+enum Role {
+    Admin,
+    Guest,
+}
+
+struct RoleGuard {
+    role: Role,
+}
+
+impl RoleGuard {
+    fn new(role: Role) -> Self {
+        Self { role }
+    }
+}
+
+impl Guard for RoleGuard {
+    async fn check(&self, ctx: &Context<'_>) -> Result<()> {
+        if ctx.data_opt::<Role>() == Some(&self.role) {
+            Ok(())
+        } else {
+            Err("Forbidden".into())
+        }
+    }
+}
+}
+

guard属性使用它:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(Eq, PartialEq, Copy, Clone)]
+enum Role { Admin, Guest, }
+struct RoleGuard { role: Role, }
+impl RoleGuard { fn new(role: Role) -> Self { Self { role } } }
+impl Guard for RoleGuard { async fn check(&self, ctx: &Context<'_>) -> Result<()> { todo!() } }
+#[derive(SimpleObject)]
+struct Query {
+    /// 只允许 Admin 访问
+    #[graphql(guard = "RoleGuard::new(Role::Admin)")]
+    value1: i32,
+    /// 允许 Admin 或者 Guest 访问
+    #[graphql(guard = "RoleGuard::new(Role::Admin).or(RoleGuard::new(Role::Guest))")]
+    value2: i32,
+}
+}
+

从参数中获取值

+

有时候守卫需要从字段参数中获取值,你需要像下面这样在创建守卫时传递该参数值:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct EqGuard {
+    expect: i32,
+    actual: i32,
+}
+
+impl EqGuard {
+    fn new(expect: i32, actual: i32) -> Self {
+        Self { expect, actual }
+    }
+}
+
+impl Guard for EqGuard {
+    async fn check(&self, _ctx: &Context<'_>) -> Result<()> {
+        if self.expect != self.actual {
+            Err("Forbidden".into())
+        } else {
+            Ok(())
+        }
+    }
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    #[graphql(guard = "EqGuard::new(100, value)")]
+    async fn get(&self, value: i32) -> i32 {
+        value
+    }
+}
+}
+

输入值校验器

+

Async-graphql内置了一些常用的校验器,你可以在对象字段的参数或者InputObject的字段上使用它们。

+
    +
  • maximum=N 指定数字不能大于N
  • +
  • minimum=N 指定数字不能小于N
  • +
  • multiple_of=N 指定数字必须是N的倍数
  • +
  • max_items=N 指定列表的长度不能大于N
  • +
  • min_items=N 指定列表的长度不能小于N
  • +
  • max_length=N 字符串的长度不能大于N
  • +
  • min_length=N 字符串的长度不能小于N
  • +
  • chars_max_length=N 字符串中 unicode 字符的的数量不能小于N
  • +
  • chars_min_length=N 字符串中 unicode 字符的的数量不能大于N
  • +
  • email 有效的 email
  • +
  • url 有效的 url
  • +
  • ip 有效的 ip 地址
  • +
  • regex=RE 匹配正则表达式
  • +
+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    /// name 参数的长度必须大于等于 5,小于等于 10
+    async fn input(&self, #[graphql(validator(min_length = 5, max_length = 10))] name: String) -> Result<i32> {
+       todo!()    
+    }
+}
+}
+

校验列表成员

+

你可以打开list属性表示校验器作用于一个列表内的所有成员:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn input(&self, #[graphql(validator(list, max_length = 10))] names: Vec<String>) -> Result<i32> {
+       todo!()
+    }
+}
+}
+

自定义校验器

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct MyValidator {
+    expect: i32,
+}
+
+impl MyValidator {
+    pub fn new(n: i32) -> Self {
+        MyValidator { expect: n }
+    }
+}
+
+impl CustomValidator<i32> for MyValidator {
+    fn check(&self, value: &i32) -> Result<(), InputValueError<i32>> {
+        if *value == self.expect {
+            Ok(())
+        } else {
+            Err(InputValueError::custom(format!("expect 100, actual {}", value)))
+        }
+    }
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+    /// n 的值必须等于 100
+    async fn value(
+        &self,
+        #[graphql(validator(custom = "MyValidator::new(100)"))] n: i32,
+    ) -> i32 {
+        n
+    }
+}
+}
+

查询缓存控制

+

生产环境下通常依赖缓存来提高性能。

+

一个 GraphQL 查询会调用多个 Resolver 函数,每个 Resolver 函数都能够具有不同的缓存定义。有的可能缓存几秒钟,有的可能缓存几个小时,有的可能所有用户都相同,有的可能每个会话都不同。

+

Async-graphql提供一种机制允许定义结果的缓存时间和作用域。

+

你可以在对象上定义缓存参数,也可以在字段上定义,下面的例子展示了缓存控制参数的两种用法。

+

你可以用max_age参数来控制缓存时长(单位是秒),也可以用publicprivate来控制缓存的作用域,当你不指定时,作用域默认是public

+

Async-graphql查询时会合并所有缓存控制指令的结果,max_age取最小值。如果任何对象或者字段的作用域为private,则其结果的作用域为private,否则为public

+

我们可以从查询结果QueryResponse中获取缓存控制合并结果,并且调用CacheControl::value来获取对应的 HTTP 头。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object(cache_control(max_age = 60))]
+impl Query {
+    #[graphql(cache_control(max_age = 30))]
+    async fn value1(&self) -> i32 {
+        1
+    }
+
+    #[graphql(cache_control(private))]
+    async fn value2(&self) -> i32 {
+        2
+    }
+
+    async fn value3(&self) -> i32 {
+        3
+    }
+}
+}
+

下面是不同的查询对应不同缓存控制结果:

+
# max_age=30
+{ value1 }
+
+
# max_age=30, private
+{ value1 value2 }
+
+
# max_age=60
+{ value3 }
+
+

游标连接 (Cursor Connections)

+

Relay 定义了一套游标连接规范,以提供一致性的分页查询方式,具体的规范文档请参考GraphQL Cursor Connections Specification

+

Async-graphql中定义一个游标连接非常简单,你只需要调用 connection::query 函数,并在闭包中查询数据。

+

下面是一个简单的获取连续整数的数据源:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::types::connection::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn numbers(&self,
+        after: Option<String>,
+        before: Option<String>,
+        first: Option<i32>,
+        last: Option<i32>,
+    ) -> Result<Connection<usize, i32, EmptyFields, EmptyFields>> {
+        query(after, before, first, last, |after, before, first, last| async move {
+            let mut start = after.map(|after| after + 1).unwrap_or(0);
+            let mut end = before.unwrap_or(10000);
+            if let Some(first) = first {
+                end = (start + first).min(end);
+            }
+            if let Some(last) = last {
+                start = if last > end - start {
+                     end
+                } else {
+                    end - last
+                };
+            }
+            let mut connection = Connection::new(start > 0, end < 10000);
+            connection.edges.extend(
+                (start..end).into_iter().map(|n|
+                    Edge::with_additional_fields(n, n as i32, EmptyFields)
+            ));
+            Ok::<_, async_graphql::Error>(connection)
+        }).await
+    }
+}
+
+}
+

# 错误扩展

+

引用 graphql-spec

+
+

GraphQL 服务可以通过扩展提供错误的附加条目。 +该条目(如果设置)必须是一个映射作为其值,用于附加错误的其它信息。

+
+

示例

+

我建议您查看此 错误扩展示例 作为快速入门。

+

一般概念

+

Async-graphql中,所有面向用户的错误都强制转换为Error类型,默认情况下会提供 +由std:::fmt::Display暴露的错误消息。但是,Error实际上提供了一个额外的可以扩展错误的信息。

+

Resolver 函数类似这样:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query {
+async fn parse_with_extensions(&self) -> Result<i32, Error> {
+    Err(Error::new("MyMessage").extend_with(|_, e| e.set("details", "CAN_NOT_FETCH")))
+}
+}
+}
+

然后可以返回如下响应:

+
{
+  "errors": [
+    {
+      "message": "MyMessage",
+      "locations": [ ... ],
+      "path": [ ... ],
+      "extensions": {
+        "details": "CAN_NOT_FETCH",
+      }
+    }
+  ]
+}
+
+

ErrorExtensions

+

手动构造新的Error很麻烦。这就是为什么Async-graphql提供 +两个方便特性,可将您的错误转换为适当的Error扩展。

+

扩展任何错误的最简单方法是对错误调用extend_with。 +这将把任何错误转换为具有给定扩展信息的Error

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+use std::num::ParseIntError;
+#[Object]
+impl Query {
+async fn parse_with_extensions(&self) -> Result<i32> {
+     Ok("234a"
+         .parse()
+         .map_err(|err: ParseIntError| err.extend_with(|_err, e| e.set("code", 404)))?)
+}
+}
+}
+

为自定义错误实现 ErrorExtensions

+

你也可以给自己的错误类型实现ErrorExtensions:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate thiserror;
+use async_graphql::*;
+#[derive(Debug, thiserror::Error)]
+pub enum MyError {
+    #[error("Could not find resource")]
+    NotFound,
+
+    #[error("ServerError")]
+    ServerError(String),
+
+    #[error("No Extensions")]
+    ErrorWithoutExtensions,
+}
+
+impl ErrorExtensions for MyError {
+    // lets define our base extensions
+    fn extend(&self) -> Error {
+        Error::new(format!("{}", self)).extend_with(|err, e| 
+            match self {
+              MyError::NotFound => e.set("code", "NOT_FOUND"),
+              MyError::ServerError(reason) => e.set("reason", reason.clone()),
+              MyError::ErrorWithoutExtensions => {}
+          })
+    }
+}
+}
+

您只需要对错误调用extend即可将错误与其提供的扩展信息一起传递,或者通过extend_with进一步扩展错误信息。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate thiserror;
+use async_graphql::*;
+#[derive(Debug, thiserror::Error)]
+pub enum MyError {
+    #[error("Could not find resource")]
+    NotFound,
+
+    #[error("ServerError")]
+    ServerError(String),
+
+    #[error("No Extensions")]
+    ErrorWithoutExtensions,
+}
+struct Query;
+#[Object]
+impl Query {
+async fn parse_with_extensions_result(&self) -> Result<i32> {
+    // Err(MyError::NotFound.extend())
+    // OR
+    Err(MyError::NotFound.extend_with(|_, e| e.set("on_the_fly", "some_more_info")))
+}
+}
+}
+
{
+  "errors": [
+    {
+      "message": "NotFound",
+      "locations": [ ... ],
+      "path": [ ... ],
+      "extensions": {
+        "code": "NOT_FOUND",
+        "on_the_fly": "some_more_info"
+      }
+    }
+  ]
+}
+
+

ResultExt

+

这个特质使您可以直接在结果上调用extend_err。因此上面的代码不再那么冗长。

+
// @todo figure out why this example does not compile!
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query {
+async fn parse_with_extensions(&self) -> Result<i32> {
+     Ok("234a"
+         .parse()
+         .extend_err(|_, e| e.set("code", 404))?)
+}
+}
+

链式调用

+

由于对所有&E where E: std::fmt::Display实现了ErrorExtensionsResultsExt,我们可以将扩展链接在一起。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query {
+async fn parse_with_extensions(&self) -> Result<i32> {
+    match "234a".parse() {
+        Ok(n) => Ok(n),
+        Err(e) => Err(e
+            .extend_with(|_, e| e.set("code", 404))
+            .extend_with(|_, e| e.set("details", "some more info.."))
+            // keys may also overwrite previous keys...
+            .extend_with(|_, e| e.set("code", 500))),
+    }
+}
+}
+}
+

响应:

+
{
+  "errors": [
+    {
+      "message": "MyMessage",
+      "locations": [ ... ],
+      "path": [ ... ],
+      "extensions": {
+      	"details": "some more info...",
+        "code": 500,
+      }
+    }
+  ]
+}
+
+

缺陷

+

Rust 的稳定版本还未提供特化功能,这就是为什么ErrorExtensions&E where E: std::fmt::Display实现,代替E:std::fmt::Display通过提供一些特化功能。

+

Autoref-based stable specialization.

+

缺点是下面的代码不能编译:

+
async fn parse_with_extensions_result(&self) -> Result<i32> {
+    // the trait `error::ErrorExtensions` is not implemented
+    // for `std::num::ParseIntError`
+    "234a".parse().extend_err(|_, e| e.set("code", 404))
+}
+

但这可以通过编译:

+
async fn parse_with_extensions_result(&self) -> Result<i32> {
+    // does work because ErrorExtensions is implemented for &ParseIntError
+    "234a"
+      .parse()
+      .map_err(|ref e: ParseIntError| e.extend_with(|_, e| e.set("code", 404)))
+}
+

Apollo Tracing 支持

+

Apollo Tracing提供了查询每个步骤的性能分析结果,它是一个Schema扩展,性能分析结果保存在QueryResponse中。

+

启用Apollo Tracing扩展需要在创建Schema的时候添加该扩展。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::ApolloTracing;
+
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+
+let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
+    .extension(ApolloTracing) // 启用 ApolloTracing 扩展
+    .finish();
+
+}
+

查询的深度和复杂度

+

⚠️GraphQL 提供了非常灵活的查询方法,但在客户端上滥用复杂的查询可能造成风险,限制查询语句的深度和复杂度可以减轻这种风险。

+

昂贵的查询

+

考虑一种允许列出博客文章的架构。每个博客帖子也与其他帖子相关。

+
type Query {
+	posts(count: Int = 10): [Post!]!
+}
+
+type Post {
+	title: String!
+	text: String!
+	related(count: Int = 10): [Post!]!
+}
+
+

创建一个会引起很大响应的查询不是很困难:

+
{
+    posts(count: 100) {
+        related(count: 100) {
+            related(count: 100) {
+                related(count: 100) {
+                    title
+                }
+            }
+        }
+    }
+}
+
+

响应的大小随related字段的每个其他级别呈指数增长。幸运的是,Async-graphql提供了一种防止此类查询的方法。

+

限制查询的深度

+

查询的深度是字段嵌套的层数,下面是一个深度为3的查询。

+
{
+    a {
+        b {
+            c
+        }
+    }
+}
+
+

在创建Schema的时候可以限制深度,如果查询语句超过这个限制,则会出错并且返回Query is nested too deep.消息。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
+    .limit_depth(5) // 限制最大深度为 5
+    .finish();
+}
+

限制查询的复杂度

+

复杂度是查询语句中字段的数量,每个字段的复杂度默认为1,下面是一个复杂度为6的查询。

+
{
+    a b c {
+        d {
+            e f
+        }
+    }
+}
+
+

在创建Schema的时候可以限制复杂度,如果查询语句超过这个限制,则会出错并且返回Query is too complex.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
+    .limit_complexity(5) // 限制复杂度为 5
+    .finish();
+}
+

自定义字段的复杂度

+

针对非列表类型和列表类型的字段,有两种自定义复杂度的方法。 +下面的代码中,value字段的复杂度为5。而values字段的复杂度为count * child_complexitychild_complexity是一个特殊的变量,表示子查询的复杂度, +count是字段的参数,这个表达式用于计算values字段的复杂度,并且返回值的类型必须是usize

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+
+#[Object]
+impl Query {
+    #[graphql(complexity = 5)]
+    async fn value(&self) -> i32 {
+        todo!()
+    }
+
+    #[graphql(complexity = "count * child_complexity")]
+    async fn values(&self, count: usize) -> i32 {
+        todo!()
+    }
+}
+}
+

注意:计算复杂度是在验证阶段完成而不是在执行阶段,所以你不用担心超限的查询语句会导致查询只执行一部分。

+

在内省中隐藏内容

+

默认情况下,所有类型,字段在内省中都是可见的。但可能你希望根据不同的用户来隐藏一些信息,避免引起不必要的误会。你可以在类型或者字段上添加visible属性来做到。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+#[derive(SimpleObject)]
+struct MyObj {
+    // 这个字段将在内省中可见
+    a: i32,
+
+    // 这个字段在内省中总是隐藏
+    #[graphql(visible = false)]
+    b: i32, 
+
+    // 这个字段调用 `is_admin` 函数,如果函数的返回值为 `true` 则可见
+    #[graphql(visible = "is_admin")]
+    c: i32, 
+}
+
+#[derive(Enum, Copy, Clone, Eq, PartialEq)]
+enum MyEnum {
+    // 这个项目将在内省中可见
+    A,
+
+    // 这个项目在内省中总是隐藏
+    #[graphql(visible = false)]
+    B,
+
+    // 这个项目调用 `is_admin` 函数,如果函数的返回值为 `true` 则可见
+    #[graphql(visible = "is_admin")]
+    C,
+}
+
+struct IsAdmin(bool);
+
+fn is_admin(ctx: &Context<'_>) -> bool {
+    ctx.data_unchecked::<IsAdmin>().0
+}
+
+}
+

扩展

+

async-graphql 允许你不修改核心代码就能扩展它功能。

+

如何定义扩展

+

async-graphql 扩展是通过实现 Extension trait 来定义的。 Extension trait 允许你将自定义代码插入到执行 GraphQL 查询的步骤中。

+

Extensions 很像来自其他框架的中间件,使用它们时要小心:当你使用扩展时它对每个 GraphQL 请求生效

+

一句话解释什么是中间件

+

让我们了解什么是中间件:

+
async fn middleware(&self, ctx: &ExtensionContext<'_>, next: NextMiddleware<'_>) -> MiddlewareResult {
+  // 你的中间件代码
+
+  /*
+   * 调用 next.run 函数执行下个中间件的逻辑
+   */
+  next.run(ctx).await
+}
+

如你所见,middleware 只是在末尾调用 next 函数的函数。但我们也可以在开头使用 next.run 来实现中间件。这就是它变得棘手的地方:根据你放置逻辑的位置以及next.run调用的位置,你的逻辑将不会具有相同的执行顺序。

+

根据你代码,你需要在 next.run 调用之前或之后处理它。如果你需要更多关于中间件的信息,网上有很多。

+

查询的处理

+

查询的每个阶段都有回调,你将能够基于这些回调创建扩展。

+

请求

+

首先,当我们收到一个请求时,如果它不是订阅,第一个被调用的函数将是 request,它在传入请求时调用,并输出结果给客户端。

+

Default implementation for request:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
+    next.run(ctx).await
+}
+}
+}
+

根据你放置逻辑代码的位置,它将在正在查询执行的开头或结尾执行。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
+    // 此处的代码将在执行 prepare_request 之前运行。
+    let result = next.run(ctx).await;
+    // 此处的代码将在把结果发送给客户端之前执行
+    result
+}
+}
+}
+

准备查询

+

request 之后,将调用prepare_request,你可以在此处对请求做一些转换。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+async fn prepare_request(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    request: Request,
+    next: NextPrepareRequest<'_>,
+) -> ServerResult<Request> {
+    // 此处的代码在 prepare_request 之前执行
+    let result = next.run(ctx, request).await;
+    // 此处的代码在 prepare_request 之后执行
+    result
+}
+}
+}
+

解析查询

+

parse_query 将解析查询语句并生成 GraphQL ExecutableDocument,并且检查查询是否遵循 GraphQL 规范。通常,async-graphql 遵循最后一个稳定的规范(October2021)。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+use async_graphql::parser::types::ExecutableDocument;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+/// Called at parse query.
+async fn parse_query(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    // The raw query
+    query: &str,
+    // The variables
+    variables: &Variables,
+    next: NextParseQuery<'_>,
+) -> ServerResult<ExecutableDocument> {
+    next.run(ctx, query, variables).await
+}
+}
+}
+

校验

+

validation 步骤将执行查询校验(取决于你指定的 validation_mode),并向客户端提供有关查询无效的原因。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+/// Called at validation query.
+async fn validation(
+  &self,
+  ctx: &ExtensionContext<'_>,
+  next: NextValidation<'_>,
+) -> Result<ValidationResult, Vec<ServerError>> {
+  next.run(ctx).await
+}
+}
+}
+

执行

+

execution 步骤是一个很大的步骤,它将并发执行Query,或者顺序执行Mutation

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+/// Called at execute query.
+async fn execute(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    operation_name: Option<&str>,
+    next: NextExecute<'_>,
+) -> Response {
+    // 此处的代码在执行完整查询之前执行
+    let result = next.run(ctx, operation_name).await;
+    // 此处的代码在执行完整查询之后执行
+    result
+}
+}
+}
+

resolve

+

为每个字段执行resolve.

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware { 
+/// Called at resolve field.
+async fn resolve(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    info: ResolveInfo<'_>,
+    next: NextResolve<'_>,
+) -> ServerResult<Option<Value>> {
+    // resolve 字段之前
+    let result = next.run(ctx, info).await;
+    // resolve 字段之后
+    result
+}
+}
+}
+

订阅

+

subscribe的行为和request很像,只是专门用于订阅查询。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+use async_graphql::extensions::*;
+use futures_util::stream::BoxStream;
+struct MyMiddleware;
+#[async_trait::async_trait]
+impl Extension for MyMiddleware {
+/// Called at subscribe request.
+fn subscribe<'s>(
+    &self,
+    ctx: &ExtensionContext<'_>,
+    stream: BoxStream<'s, Response>,
+    next: NextSubscribe<'_>,
+) -> BoxStream<'s, Response> {
+    next.run(ctx, stream)
+}
+}
+}
+

可用的扩展列表

+

async-graphql 中有很多可用的扩展用于增强你的 GraphQL 服务器。

+

Analyzer

+

Available in the repository

+

Analyzer 扩展将在每个响应的扩展中输出 complexitydepth 字段。

+

Apollo Persisted Queries

+

Available in the repository

+

要提高大型查询的性能,你可以启用此扩展,每个查询语句都将与一个唯一 ID 相关联,因此客户端可以直接发送此 ID 查询以减少请求的大小。

+

这个扩展不会强迫你使用一些缓存策略,你可以选择你想要的缓存策略,你只需要实现 CacheStorage trait:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[async_trait::async_trait]
+pub trait CacheStorage: Send + Sync + Clone + 'static {
+    /// Load the query by `key`.
+    async fn get(&self, key: String) -> Option<String>;
+    /// Save the query by `key`.
+    async fn set(&self, key: String, query: String);
+}
+}
+

References: Apollo doc - Persisted Queries

+

Apollo Tracing

+

Available in the repository

+

Apollo Tracing 扩展用于在响应中包含此查询分析数据。此扩展程序遵循旧的且现已弃用的 Apollo Tracing Spec 。 +如果你想支持更新的 Apollo Reporting Protocol,推荐使用 async-graphql Apollo studio extension

+

Apollo Studio

+

Available at async-graphql/async_graphql_apollo_studio_extension

+

async-graphql 提供了实现官方 Apollo Specification 的扩展,位于 async-graphql-extension- apollo-tracingcrates.io

+

Logger

+

Available in the repository

+

Logger 是一个简单的扩展,允许你向 async-graphql 添加一些日志记录功能。这也是学习如何创建自己的扩展的一个很好的例子。

+

OpenTelemetry

+

Available in the repository

+

OpenTelemetry 扩展提供 opentelemetry crate 的集成,以允许你的应用程序从 async-graphql 捕获分布式跟踪和指标。

+

Tracing

+

Available in the repository

+

Tracing 扩展提供 tracing crate 的集成,允许您向 async-graphql 添加一些跟踪功能,有点像Logger 扩展。

+

集成到 WebServer

+

Async-graphql提供了对一些常用 Web Server 的集成支持。

+ +

即使你目前使用的 Web Server 不在上面的列表中,自己实现类似的功能也相当的简单。

+

Poem

+

请求例子

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_poem;
+extern crate async_graphql;
+extern crate poem;
+use async_graphql::*;
+#[derive(Default, SimpleObject)]
+struct Query { a: i32 }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+use poem::Route;
+use async_graphql_poem::GraphQL;
+
+let app = Route::new()
+    .at("/ws", GraphQL::new(schema));
+}
+

订阅例子

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_poem;
+extern crate async_graphql;
+extern crate poem;
+use async_graphql::*;
+#[derive(Default, SimpleObject)]
+struct Query { a: i32 }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+use poem::{get, Route};
+use async_graphql_poem::GraphQLSubscription;
+
+let app = Route::new()
+    .at("/ws", get(GraphQLSubscription::new(schema)));
+}
+

更多例子

+

https://github.com/async-graphql/examples/tree/master/poem

+

Warp

+

Async-graphql-warp提供了两个Filtergraphqlgraphql_subscription

+

graphql用于执行QueryMutation请求,它提取 GraphQL 请求,然后输出一个包含async_graphql::Schemaasync_graphql::Request元组,你可以在之后组合其它 Filter,或者直接调用Schema::execute执行查询。

+

graphql_subscription用于实现基于 Web Socket 的订阅,它输出warp::Reply

+

请求例子

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_warp;
+extern crate async_graphql;
+extern crate warp;
+use async_graphql::*;
+use std::convert::Infallible;
+use warp::Filter;
+struct QueryRoot;
+#[Object]
+impl QueryRoot { async fn version(&self) -> &str { "1.0" } }
+async fn other() {
+type MySchema = Schema<QueryRoot, EmptyMutation, EmptySubscription>;
+
+let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription);
+let filter = async_graphql_warp::graphql(schema).and_then(|(schema, request): (MySchema, async_graphql::Request)| async move {
+    // 执行查询
+    let resp = schema.execute(request).await;
+
+    // 返回结果
+    Ok::<_, Infallible>(async_graphql_warp::GraphQLResponse::from(resp))
+});
+warp::serve(filter).run(([0, 0, 0, 0], 8000)).await;
+}
+}
+

订阅例子

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_warp;
+extern crate async_graphql;
+extern crate warp;
+use async_graphql::*;
+use futures_util::stream::{Stream, StreamExt};
+use std::convert::Infallible;
+use warp::Filter;
+struct SubscriptionRoot;
+#[Subscription]
+impl SubscriptionRoot {
+  async fn tick(&self) -> impl Stream<Item = i32> {
+    futures_util::stream::iter(0..10)
+  }
+}
+struct QueryRoot;
+#[Object]
+impl QueryRoot { async fn version(&self) -> &str { "1.0" } }
+async fn other() {
+let schema = Schema::new(QueryRoot, EmptyMutation, SubscriptionRoot);
+let filter = async_graphql_warp::graphql_subscription(schema);
+warp::serve(filter).run(([0, 0, 0, 0], 8000)).await;
+}
+}
+

更多例子

+

https://github.com/async-graphql/examples/tree/master/warp

+

Actix-web

+

Async-graphql-actix-web提供了GraphQLRequest提取器用于提取GraphQL请求,和GraphQLResponse用于输出GraphQL响应。

+

GraphQLSubscription用于创建一个支持 Web Socket 订阅的 Actor。

+

请求例子

+

你需要把 Schema 传入actix_web::App作为全局数据。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_actix_web;
+extern crate async_graphql;
+extern crate actix_web;
+use async_graphql::*;
+#[derive(Default,SimpleObject)]
+struct Query { a: i32 }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+use actix_web::{web, HttpRequest, HttpResponse};
+use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse};
+async fn index(
+    schema: web::Data<Schema<Query, EmptyMutation, EmptySubscription>>,
+    request: GraphQLRequest,
+) -> web::Json<GraphQLResponse> {
+    web::Json(schema.execute(request.into_inner()).await.into())
+}
+}
+

订阅例子

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql_actix_web;
+extern crate async_graphql;
+extern crate actix_web;
+use async_graphql::*;
+#[derive(Default,SimpleObject)]
+struct Query { a: i32 }
+let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();
+use actix_web::{web, HttpRequest, HttpResponse};
+use async_graphql_actix_web::GraphQLSubscription;
+async fn index_ws(
+    schema: web::Data<Schema<Query, EmptyMutation, EmptySubscription>>,
+    req: HttpRequest,
+    payload: web::Payload,
+) -> actix_web::Result<HttpResponse> {
+    GraphQLSubscription::new(Schema::clone(&*schema)).start(&req, payload)
+}
+}
+

更多例子

+

https://github.com/async-graphql/examples/tree/master/actix-web

+

高级主题

+

自定义标量

+

Async-graphql已经内置了绝大部分常用的标量类型,同时你也能自定义标量。

+

实现Async-graphql::Scalar即可自定义一个标量,你只需要实现一个解析函数和输出函数。

+

下面的例子定义一个 64 位整数标量,但它的输入输出都是字符串。 (Async-graphql已经内置了对 64 位整数的支持,正是采用字符串作为输入输出)

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+
+struct StringNumber(i64);
+
+#[Scalar]
+impl ScalarType for StringNumber {
+    fn parse(value: Value) -> InputValueResult<Self> {
+        if let Value::String(value) = &value {
+            // 解析整数
+            Ok(value.parse().map(StringNumber)?)
+        } else {
+            // 类型不匹配
+            Err(InputValueError::expected_type(value))
+        }
+    }
+
+    fn to_value(&self) -> Value {
+        Value::String(self.0.to_string())
+    }
+}
+
+}
+

使用scalar!宏定义标量

+

如果你的类型实现了serde :: Serializeserde :: Deserialize,那么可以使用此宏更简单地定义标量。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+extern crate serde;
+use async_graphql::*;
+use serde::{Serialize, Deserialize};
+use std::collections::HashMap;
+#[derive(Serialize, Deserialize)]
+struct MyValue {
+    a: i32,
+    b: HashMap<String, i32>,     
+}
+
+scalar!(MyValue);
+
+// 重命名为 `MV`.
+// scalar!(MyValue, "MV");
+
+// 重命名为 `MV` 并且添加描述。
+// scalar!(MyValue, "MV", "This is my value");
+}
+

优化查询(解决 N+1 问题)

+

您是否注意到某些 GraphQL 查询需要执行数百个数据库查询,这些查询通常包含重复的数据,让我们来看看为什么以及如何修复它。

+

查询解析

+

想象一下,如果您有一个简单的查询,例如:

+
query { todos { users { name } } }
+
+

实现User的 resolver 代码如下:

+
struct User {
+    id: u64,
+}
+
+#[Object]
+impl User {
+    async fn name(&self, ctx: &Context<'_>) -> Result<String> {
+        let pool = ctx.data_unchecked::<Pool<Postgres>>();
+        let (name,): (String,) = sqlx::query_as("SELECT name FROM user WHERE id = $1")
+            .bind(self.id)
+            .fetch_one(pool)
+            .await?;
+        Ok(name)
+    }
+}
+

执行查询将调用Todos的 resolver,该 resolver 执行SELECT * FROM todo并返回 N 个Todo对象。然后对每个Todo对象同时调用User的 +resolver 执行SELECT name FROM user where id = $1

+

例如:

+
SELECT id, todo, user_id FROM todo
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+SELECT name FROM user WHERE id = $1
+
+

执行了多次SELECT name FROM user WHERE id = $1,并且,大多数Todo对象都属于同一个用户,我们需要优化这些代码!

+

Dataloader

+

我们需要对查询分组,并且排除重复的查询。Dataloader就能完成这个工作,facebook 给出了一个请求范围的批处理和缓存解决方案。

+

下面是使用DataLoader来优化查询请求的例子:

+
use async_graphql::*;
+use async_graphql::dataloader::*;
+use itertools::Itertools;
+use std::sync::Arc;
+
+struct UserNameLoader {
+    pool: sqlx::Pool<Postgres>,
+}
+
+impl Loader<u64> for UserNameLoader {
+    type Value = String;
+    type Error = Arc<sqlx::Error>;
+
+    async fn load(&self, keys: &[u64]) -> Result<HashMap<u64, Self::Value>, Self::Error> {
+        let query = format!("SELECT name FROM user WHERE id IN ({})", keys.iter().join(","));
+        Ok(sqlx::query_as(query)
+            .fetch(&self.pool)
+            .map_ok(|name: String| name)
+            .map_err(Arc::new)
+            .try_collect().await?)
+    }
+}
+
+struct User {
+    id: u64,
+}
+
+#[Object]
+impl User {
+    async fn name(&self, ctx: &Context<'_>) -> Result<String> {
+        let loader = ctx.data_unchecked::<DataLoader<UserNameLoader>>();
+        let name: Option<String> = loader.load_one(self.id).await?;
+        name.ok_or_else(|| "Not found".into())
+    }
+}
+

要在 ctx 中获取 UserNameLoader,您必须将其和任务生成器(例如 async_std::task::spawn)注册到 Schema 中:

+
let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
+    .data(DataLoader::new(
+        UserNameLoader,
+        async_std::task::spawn, // 或者 `tokio::spawn`
+    ))
+    .finish();
+

最终只需要两个查询语句,就查询出了我们想要的结果!

+
SELECT id, todo, user_id FROM todo
+SELECT name FROM user WHERE id IN (1, 2, 3, 4)
+
+

同一个 Loader 支持多种数据类型

+

你可以为同一个Loader实现多种数据类型,就像下面这样:

+
struct PostgresLoader {
+    pool: sqlx::Pool<Postgres>,
+}
+
+impl Loader<UserId> for PostgresLoader {
+    type Value = User;
+    type Error = Arc<sqlx::Error>;
+
+    async fn load(&self, keys: &[UserId]) -> Result<HashMap<UserId, Self::Value>, Self::Error> {
+        // 从数据库中加载 User
+    }
+}
+
+impl Loader<TodoId> for PostgresLoader {
+    type Value = Todo;
+    type Error = sqlx::Error;
+
+    async fn load(&self, keys: &[TodoId]) -> Result<HashMap<TodoId, Self::Value>, Self::Error> {
+        // 从数据库中加载 Todo
+    }
+}
+

自定义指令

+

Async-graphql可以很方便的自定义指令,这可以扩展 GraphQL 的行为。

+

创建一个自定义指令,需要实现 CustomDirective trait,然后用Directive宏生成一个工厂函数,该函数接收指令的参数并返回指令的实例。

+

目前Async-graphql仅支持添加FIELD位置的指令。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct ConcatDirective {
+    value: String,
+}
+
+#[async_trait::async_trait]
+impl CustomDirective for ConcatDirective {
+    async fn resolve_field(&self, _ctx: &Context<'_>, resolve: ResolveFut<'_>) -> ServerResult<Option<Value>> {
+        resolve.await.map(|value| {
+            value.map(|value| match value {
+                Value::String(str) => Value::String(str + &self.value),
+                _ => value,
+            })
+        })
+    }
+}
+
+#[Directive(location = "Field")]
+fn concat(value: String) -> impl CustomDirective {
+    ConcatDirective { value }
+}
+}
+

创建模式时注册指令:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+struct Query;
+#[Object]
+impl Query { async fn version(&self) -> &str { "1.0" } }
+struct ConcatDirective { value: String, }
+#[async_trait::async_trait]
+impl CustomDirective for ConcatDirective {
+  async fn resolve_field(&self, _ctx: &Context<'_>, resolve: ResolveFut<'_>) -> ServerResult<Option<Value>> { todo!() }
+}
+#[Directive(location = "Field")]
+fn concat(value: String) -> impl CustomDirective { ConcatDirective { value } }
+let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
+    .directive(concat)
+    .finish();
+}
+

Apollo Federation 集成

+

Apollo Federation是一个GraphQL网关,它可以组合多个 GraphQL 服务,允许每服务仅实现它负责的那一部分数据,参考官方文档

+

Async-graphql可以完全支持Apollo Federation的所有功能,但需要对Schema定义做一些小小的改造。

+
    +
  • +

    async_graphql::Objectasync_graphql::Interfaceextends属性声明这个类别是一个已有类型的扩充。

    +
  • +
  • +

    字段的external属性声明这个字段定义来自其它服务。

    +
  • +
  • +

    字段的provides属性用于要求网关提供的字段集。

    +
  • +
  • +

    字段的requires属性表示解析该字段值需要依赖该类型的字段集。

    +
  • +
+

实体查找函数

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct User { id: ID }
+struct Query;
+
+#[Object]
+impl Query {
+    #[graphql(entity)]
+    async fn find_user_by_id(&self, id: ID) -> User {
+        User { id }
+    }
+
+    #[graphql(entity)]
+    async fn find_user_by_id_with_username(&self, #[graphql(key)] id: ID, username: String) -> User {
+        User { id }
+    }
+
+    #[graphql(entity)]
+    async fn find_user_by_id_and_username(&self, id: ID, username: String) -> User {
+        User { id }
+    }
+}
+}
+

注意这三个查找函数的不同,他们都是查找 User 对象。

+
    +
  • +

    find_user_by_id

    +

    使用id查找User对象,User对象的 key 是id

    +
  • +
  • +

    find_user_by_id_with_username

    +

    使用id查找User对象,User对象的 key 是id,并且请求User对象的username字段值。

    +
  • +
  • +

    find_user_by_id_and_username

    +

    使用idusername查找User对象,User对象的 key 是idusername

    +
  • +
+

完整的例子请参考 https://github.com/async-graphql/examples/tree/master/federation

+

定义复合主键

+

一个主键可以包含多个字段,什么包含嵌套字段,你可以用InputObject来实现一个嵌套字段的 Key 类型。

+

下面的例子中User对象的主键是key { a b }

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct User { id: i32 }
+#[derive(InputObject)]
+struct NestedKey {
+  a: i32,
+  b: i32,
+}
+
+struct Query;
+
+#[Object]
+impl Query {
+  #[graphql(entity)]
+  async fn find_user_by_key(&self, key: NestedKey) -> User {
+    User { id: key.a }
+  }
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/query_and_mutation.html b/zh-CN/query_and_mutation.html new file mode 100644 index 000000000..0c9208f25 --- /dev/null +++ b/zh-CN/query_and_mutation.html @@ -0,0 +1,264 @@ + + + + + + 查询和变更 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

查询和变更

+

查询根对象

+

查询根对象是一个 GraphQL 对象,定义类似其它对象。查询对象的所有字段 Resolver 函数是并发执行的。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+#[derive(SimpleObject)]
+struct User { a: i32 }
+
+struct Query;
+
+#[Object]
+impl Query {
+    async fn user(&self, username: String) -> Result<Option<User>> {
+        // 在数据库中查找用户
+       todo!()
+    }
+}
+
+}
+

变更根对象

+

变更根对象也是一个 GraphQL,但变更根对象的执行是顺序的,只有第一个变更执行完成之后才会执行下一个。

+

下面的变更根对象提供用户注册和登录操作:

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Mutation;
+
+#[Object]
+impl Mutation {
+    async fn signup(&self, username: String, password: String) -> Result<bool> {
+        // 用户注册
+       todo!()
+    }
+
+    async fn login(&self, username: String, password: String) -> Result<String> {
+        // 用户登录并生成 token
+       todo!()
+    }
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/quickstart.html b/zh-CN/quickstart.html new file mode 100644 index 000000000..68cb01d63 --- /dev/null +++ b/zh-CN/quickstart.html @@ -0,0 +1,269 @@ + + + + + + 快速开始 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

快速开始

+

添加依赖

+
[dependencies]
+async-graphql = "4.0"
+async-graphql-actix-web = "4.0" # 如果你需要集成到 Actix-web
+async-graphql-warp = "4.0" # 如果你需要集成到 Warp
+async-graphql-tide = "4.0" # 如果你需要集成到 Tide
+
+

写一个 Schema

+

一个 GraphQL 的 Schema 包含一个必须的查询 (Query) 根对象,可选的变更 (Mutation) 根对象和可选的订阅 (Subscription) 根对象,这些对象类型都是用 Rust 语言的结构来描述它们,结构的字段对应 GraphQL 对象的字段。

+

Async-graphql 实现了常用数据类型到 GraphQL 类型的映射,例如i32, f64, Option<T>, Vec<T>等。同时,你也能够扩展这些基础类型,基础数据类型在 GraphQL 里面称为标量。

+

下面是一个简单的例子,我们只提供一个查询,返回ab的和。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+
+#[Object]
+impl Query {
+    /// Returns the sum of a and b
+    async fn add(&self, a: i32, b: i32) -> i32 {
+        a + b
+    }
+}
+
+}
+

执行查询

+

在我们这个例子里面,只有 Query,没有 Mutation 和 Subscription,所以我们用EmptyMutationEmptySubscription来创建 Schema,然后调用Schema::execute来执行查询。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+struct Query;
+#[Object]
+impl Query {
+  async fn version(&self) -> &str { "1.0" }    
+}
+async fn other() {
+let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
+let res = schema.execute("{ add(a: 10, b: 20) }").await;
+}
+}
+

把查询结果输出为 JSON

+
let json = serde_json::to_string(&res);
+

和 Web Server 的集成

+

请参考 https://github.com/async-graphql/examples。

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/searcher.js b/zh-CN/searcher.js new file mode 100644 index 000000000..5f7a7be95 --- /dev/null +++ b/zh-CN/searcher.js @@ -0,0 +1,529 @@ +'use strict'; + +/* global Mark, elasticlunr, path_to_root */ + +window.search = window.search || {}; +(function search() { + // Search functionality + // + // You can use !hasFocus() to prevent keyhandling in your key + // event handlers while the user is typing their search. + + if (!Mark || !elasticlunr) { + return; + } + + // eslint-disable-next-line max-len + // IE 11 Compatibility from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith + if (!String.prototype.startsWith) { + String.prototype.startsWith = function(search, pos) { + return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search; + }; + } + + const search_wrap = document.getElementById('search-wrapper'), + searchbar = document.getElementById('searchbar'), + searchresults = document.getElementById('searchresults'), + searchresults_outer = document.getElementById('searchresults-outer'), + searchresults_header = document.getElementById('searchresults-header'), + searchicon = document.getElementById('search-toggle'), + content = document.getElementById('content'), + + // SVG text elements don't render if inside a tag. + mark_exclude = ['text'], + marker = new Mark(content), + URL_SEARCH_PARAM = 'search', + URL_MARK_PARAM = 'highlight'; + + let current_searchterm = '', + doc_urls = [], + search_options = { + bool: 'AND', + expand: true, + fields: { + title: {boost: 1}, + body: {boost: 1}, + breadcrumbs: {boost: 0}, + }, + }, + searchindex = null, + results_options = { + teaser_word_count: 30, + limit_results: 30, + }, + teaser_count = 0; + + function hasFocus() { + return searchbar === document.activeElement; + } + + function removeChildren(elem) { + while (elem.firstChild) { + elem.removeChild(elem.firstChild); + } + } + + // Helper to parse a url into its building blocks. + function parseURL(url) { + const a = document.createElement('a'); + a.href = url; + return { + source: url, + protocol: a.protocol.replace(':', ''), + host: a.hostname, + port: a.port, + params: (function() { + const ret = {}; + const seg = a.search.replace(/^\?/, '').split('&'); + for (const part of seg) { + if (!part) { + continue; + } + const s = part.split('='); + ret[s[0]] = s[1]; + } + return ret; + })(), + file: (a.pathname.match(/\/([^/?#]+)$/i) || ['', ''])[1], + hash: a.hash.replace('#', ''), + path: a.pathname.replace(/^([^/])/, '/$1'), + }; + } + + // Helper to recreate a url string from its building blocks. + function renderURL(urlobject) { + let url = urlobject.protocol + '://' + urlobject.host; + if (urlobject.port !== '') { + url += ':' + urlobject.port; + } + url += urlobject.path; + let joiner = '?'; + for (const prop in urlobject.params) { + if (Object.prototype.hasOwnProperty.call(urlobject.params, prop)) { + url += joiner + prop + '=' + urlobject.params[prop]; + joiner = '&'; + } + } + if (urlobject.hash !== '') { + url += '#' + urlobject.hash; + } + return url; + } + + // Helper to escape html special chars for displaying the teasers + const escapeHTML = (function() { + const MAP = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '\'': ''', + }; + const repl = function(c) { + return MAP[c]; + }; + return function(s) { + return s.replace(/[&<>'"]/g, repl); + }; + })(); + + function formatSearchMetric(count, searchterm) { + if (count === 1) { + return count + ' search result for \'' + searchterm + '\':'; + } else if (count === 0) { + return 'No search results for \'' + searchterm + '\'.'; + } else { + return count + ' search results for \'' + searchterm + '\':'; + } + } + + function formatSearchResult(result, searchterms) { + const teaser = makeTeaser(escapeHTML(result.doc.body), searchterms); + teaser_count++; + + // The ?URL_MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor + const url = doc_urls[result.ref].split('#'); + if (url.length === 1) { // no anchor found + url.push(''); + } + + // encodeURIComponent escapes all chars that could allow an XSS except + // for '. Due to that we also manually replace ' with its url-encoded + // representation (%27). + const encoded_search = encodeURIComponent(searchterms.join(' ')).replace(/'/g, '%27'); + + return '' + + result.doc.breadcrumbs + '' + '' + teaser + ''; + } + + function makeTeaser(body, searchterms) { + // The strategy is as follows: + // First, assign a value to each word in the document: + // Words that correspond to search terms (stemmer aware): 40 + // Normal words: 2 + // First word in a sentence: 8 + // Then use a sliding window with a constant number of words and count the + // sum of the values of the words within the window. Then use the window that got the + // maximum sum. If there are multiple maximas, then get the last one. + // Enclose the terms in . + const stemmed_searchterms = searchterms.map(function(w) { + return elasticlunr.stemmer(w.toLowerCase()); + }); + const searchterm_weight = 40; + const weighted = []; // contains elements of ["word", weight, index_in_document] + // split in sentences, then words + const sentences = body.toLowerCase().split('. '); + let index = 0; + let value = 0; + let searchterm_found = false; + for (const sentenceindex in sentences) { + const words = sentences[sentenceindex].split(' '); + value = 8; + for (const wordindex in words) { + const word = words[wordindex]; + if (word.length > 0) { + for (const searchtermindex in stemmed_searchterms) { + if (elasticlunr.stemmer(word).startsWith( + stemmed_searchterms[searchtermindex]) + ) { + value = searchterm_weight; + searchterm_found = true; + } + } + weighted.push([word, value, index]); + value = 2; + } + index += word.length; + index += 1; // ' ' or '.' if last word in sentence + } + index += 1; // because we split at a two-char boundary '. ' + } + + if (weighted.length === 0) { + return body; + } + + const window_weight = []; + const window_size = Math.min(weighted.length, results_options.teaser_word_count); + + let cur_sum = 0; + for (let wordindex = 0; wordindex < window_size; wordindex++) { + cur_sum += weighted[wordindex][1]; + } + window_weight.push(cur_sum); + for (let wordindex = 0; wordindex < weighted.length - window_size; wordindex++) { + cur_sum -= weighted[wordindex][1]; + cur_sum += weighted[wordindex + window_size][1]; + window_weight.push(cur_sum); + } + + let max_sum_window_index = 0; + if (searchterm_found) { + let max_sum = 0; + // backwards + for (let i = window_weight.length - 1; i >= 0; i--) { + if (window_weight[i] > max_sum) { + max_sum = window_weight[i]; + max_sum_window_index = i; + } + } + } else { + max_sum_window_index = 0; + } + + // add around searchterms + const teaser_split = []; + index = weighted[max_sum_window_index][2]; + for (let i = max_sum_window_index; i < max_sum_window_index + window_size; i++) { + const word = weighted[i]; + if (index < word[2]) { + // missing text from index to start of `word` + teaser_split.push(body.substring(index, word[2])); + index = word[2]; + } + if (word[1] === searchterm_weight) { + teaser_split.push(''); + } + index = word[2] + word[0].length; + teaser_split.push(body.substring(word[2], index)); + if (word[1] === searchterm_weight) { + teaser_split.push(''); + } + } + + return teaser_split.join(''); + } + + function init(config) { + results_options = config.results_options; + search_options = config.search_options; + doc_urls = config.doc_urls; + searchindex = elasticlunr.Index.load(config.index); + + // Set up events + searchicon.addEventListener('click', () => { + searchIconClickHandler(); + }, false); + searchbar.addEventListener('keyup', () => { + searchbarKeyUpHandler(); + }, false); + document.addEventListener('keydown', e => { + globalKeyHandler(e); + }, false); + // If the user uses the browser buttons, do the same as if a reload happened + window.onpopstate = () => { + doSearchOrMarkFromUrl(); + }; + // Suppress "submit" events so the page doesn't reload when the user presses Enter + document.addEventListener('submit', e => { + e.preventDefault(); + }, false); + + // If reloaded, do the search or mark again, depending on the current url parameters + doSearchOrMarkFromUrl(); + + // Exported functions + config.hasFocus = hasFocus; + } + + function unfocusSearchbar() { + // hacky, but just focusing a div only works once + const tmp = document.createElement('input'); + tmp.setAttribute('style', 'position: absolute; opacity: 0;'); + searchicon.appendChild(tmp); + tmp.focus(); + tmp.remove(); + } + + // On reload or browser history backwards/forwards events, parse the url and do search or mark + function doSearchOrMarkFromUrl() { + // Check current URL for search request + const url = parseURL(window.location.href); + if (Object.prototype.hasOwnProperty.call(url.params, URL_SEARCH_PARAM) + && url.params[URL_SEARCH_PARAM] !== '') { + showSearch(true); + searchbar.value = decodeURIComponent( + (url.params[URL_SEARCH_PARAM] + '').replace(/\+/g, '%20')); + searchbarKeyUpHandler(); // -> doSearch() + } else { + showSearch(false); + } + + if (Object.prototype.hasOwnProperty.call(url.params, URL_MARK_PARAM)) { + const words = decodeURIComponent(url.params[URL_MARK_PARAM]).split(' '); + marker.mark(words, { + exclude: mark_exclude, + }); + + const markers = document.querySelectorAll('mark'); + const hide = () => { + for (let i = 0; i < markers.length; i++) { + markers[i].classList.add('fade-out'); + window.setTimeout(() => { + marker.unmark(); + }, 300); + } + }; + + for (let i = 0; i < markers.length; i++) { + markers[i].addEventListener('click', hide); + } + } + } + + // Eventhandler for keyevents on `document` + function globalKeyHandler(e) { + if (e.altKey || + e.ctrlKey || + e.metaKey || + e.shiftKey || + e.target.type === 'textarea' || + e.target.type === 'text' || + !hasFocus() && /^(?:input|select|textarea)$/i.test(e.target.nodeName) + ) { + return; + } + + if (e.key === 'Escape') { + e.preventDefault(); + searchbar.classList.remove('active'); + setSearchUrlParameters('', + searchbar.value.trim() !== '' ? 'push' : 'replace'); + if (hasFocus()) { + unfocusSearchbar(); + } + showSearch(false); + marker.unmark(); + } else if (!hasFocus() && (e.key === 'S' || e.key === '/')) { + e.preventDefault(); + showSearch(true); + window.scrollTo(0, 0); + searchbar.select(); + } else if (hasFocus() && (e.key === 'ArrowDown' + || e.key === 'Enter')) { + e.preventDefault(); + const first = searchresults.firstElementChild; + if (first !== null) { + unfocusSearchbar(); + first.classList.add('focus'); + if (e.key === 'Enter') { + window.location.assign(first.querySelector('a')); + } + } + } else if (!hasFocus() && (e.key === 'ArrowDown' + || e.key === 'ArrowUp' + || e.key === 'Enter')) { + // not `:focus` because browser does annoying scrolling + const focused = searchresults.querySelector('li.focus'); + if (!focused) { + return; + } + e.preventDefault(); + if (e.key === 'ArrowDown') { + const next = focused.nextElementSibling; + if (next) { + focused.classList.remove('focus'); + next.classList.add('focus'); + } + } else if (e.key === 'ArrowUp') { + focused.classList.remove('focus'); + const prev = focused.previousElementSibling; + if (prev) { + prev.classList.add('focus'); + } else { + searchbar.select(); + } + } else { // Enter + window.location.assign(focused.querySelector('a')); + } + } + } + + function showSearch(yes) { + if (yes) { + search_wrap.classList.remove('hidden'); + searchicon.setAttribute('aria-expanded', 'true'); + } else { + search_wrap.classList.add('hidden'); + searchicon.setAttribute('aria-expanded', 'false'); + const results = searchresults.children; + for (let i = 0; i < results.length; i++) { + results[i].classList.remove('focus'); + } + } + } + + function showResults(yes) { + if (yes) { + searchresults_outer.classList.remove('hidden'); + } else { + searchresults_outer.classList.add('hidden'); + } + } + + // Eventhandler for search icon + function searchIconClickHandler() { + if (search_wrap.classList.contains('hidden')) { + showSearch(true); + window.scrollTo(0, 0); + searchbar.select(); + } else { + showSearch(false); + } + } + + // Eventhandler for keyevents while the searchbar is focused + function searchbarKeyUpHandler() { + const searchterm = searchbar.value.trim(); + if (searchterm !== '') { + searchbar.classList.add('active'); + doSearch(searchterm); + } else { + searchbar.classList.remove('active'); + showResults(false); + removeChildren(searchresults); + } + + setSearchUrlParameters(searchterm, 'push_if_new_search_else_replace'); + + // Remove marks + marker.unmark(); + } + + // Update current url with ?URL_SEARCH_PARAM= parameter, remove ?URL_MARK_PARAM and + // `#heading-anchor`. `action` can be one of "push", "replace", + // "push_if_new_search_else_replace" and replaces or pushes a new browser history item. + // "push_if_new_search_else_replace" pushes if there is no `?URL_SEARCH_PARAM=abc` yet. + function setSearchUrlParameters(searchterm, action) { + const url = parseURL(window.location.href); + const first_search = !Object.prototype.hasOwnProperty.call(url.params, URL_SEARCH_PARAM); + + if (searchterm !== '' || action === 'push_if_new_search_else_replace') { + url.params[URL_SEARCH_PARAM] = searchterm; + delete url.params[URL_MARK_PARAM]; + url.hash = ''; + } else { + delete url.params[URL_MARK_PARAM]; + delete url.params[URL_SEARCH_PARAM]; + } + // A new search will also add a new history item, so the user can go back + // to the page prior to searching. A updated search term will only replace + // the url. + if (action === 'push' || action === 'push_if_new_search_else_replace' && first_search ) { + history.pushState({}, document.title, renderURL(url)); + } else if (action === 'replace' || + action === 'push_if_new_search_else_replace' && + !first_search + ) { + history.replaceState({}, document.title, renderURL(url)); + } + } + + function doSearch(searchterm) { + // Don't search the same twice + if (current_searchterm === searchterm) { + return; + } else { + current_searchterm = searchterm; + } + + if (searchindex === null) { + return; + } + + // Do the actual search + const results = searchindex.search(searchterm, search_options); + const resultcount = Math.min(results.length, results_options.limit_results); + + // Display search metrics + searchresults_header.innerText = formatSearchMetric(resultcount, searchterm); + + // Clear and insert results + const searchterms = searchterm.split(' '); + removeChildren(searchresults); + for (let i = 0; i < resultcount ; i++) { + const resultElem = document.createElement('li'); + resultElem.innerHTML = formatSearchResult(results[i], searchterms); + searchresults.appendChild(resultElem); + } + + // Display results + showResults(true); + } + + function loadScript(url, id) { + const script = document.createElement('script'); + script.src = url; + script.id = id; + script.onload = () => init(window.search); + script.onerror = error => { + console.error(`Failed to load \`${url}\`: ${error}`); + }; + document.head.append(script); + } + + loadScript(path_to_root + 'searchindex.js', 'search-index'); + +})(window.search); diff --git a/zh-CN/searchindex.js b/zh-CN/searchindex.js new file mode 100644 index 000000000..5eb5dd6fc --- /dev/null +++ b/zh-CN/searchindex.js @@ -0,0 +1 @@ +window.search = JSON.parse('{"doc_urls":["introduction.html#介绍","introduction.html#为什么我要开发-async-graphql","quickstart.html#快速开始","quickstart.html#添加依赖","quickstart.html#写一个-schema","quickstart.html#执行查询","quickstart.html#把查询结果输出为-json","quickstart.html#和-web-server-的集成","typesystem.html#类型系统","define_simple_object.html#简单对象-simpleobject","define_simple_object.html#泛型","define_simple_object.html#复杂字段","define_simple_object.html#同时用于输入和输出","define_complex_object.html#对象-object","context.html#查询上下文-context","context.html#存储数据","context.html#schema-数据","context.html#请求数据","context.html#http-头","context.html#selection--lookahead","error_handling.html#错误处理","merging_objects.html#合并对象-mergedobject","merging_objects.html#为同一类型实现多次-object","merging_objects.html#合并订阅","derived_fields.html#派生字段","derived_fields.html#包装类型","derived_fields.html#example","define_enum.html#枚举-enum","define_enum.html#封装外部枚举类型","define_interface.html#接口-interface","define_interface.html#手工注册接口类型","define_union.html#联合-union","define_union.html#展平嵌套联合","define_input_object.html#输入对象-inputobject","define_input_object.html#泛型","default_value.html#默认值","default_value.html#对象字段参数","default_value.html#接口字段参数","default_value.html#输入对象-inputobject","define_schema.html#定义模式-schema","query_and_mutation.html#查询和变更","query_and_mutation.html#查询根对象","query_and_mutation.html#变更根对象","subscription.html#订阅","utilities.html#实用功能","field_guard.html#字段守卫-field-guard","field_guard.html#从参数中获取值","input_value_validators.html#输入值校验器","input_value_validators.html#校验列表成员","input_value_validators.html#自定义校验器","cache_control.html#查询缓存控制","cursor_connections.html#游标连接-cursor-connections","error_extensions.html#示例","error_extensions.html#一般概念","error_extensions.html#errorextensions","error_extensions.html#为自定义错误实现-errorextensions","error_extensions.html#resultext","error_extensions.html#链式调用","error_extensions.html#缺陷","apollo_tracing.html#apollo-tracing-支持","depth_and_complexity.html#查询的深度和复杂度","depth_and_complexity.html#昂贵的查询","depth_and_complexity.html#限制查询的深度","depth_and_complexity.html#限制查询的复杂度","depth_and_complexity.html#自定义字段的复杂度","visibility.html#在内省中隐藏内容","extensions.html#扩展","extensions_inner_working.html#如何定义扩展","extensions_inner_working.html#一句话解释什么是中间件","extensions_inner_working.html#查询的处理","extensions_inner_working.html#请求","extensions_inner_working.html#准备查询","extensions_inner_working.html#解析查询","extensions_inner_working.html#校验","extensions_inner_working.html#执行","extensions_inner_working.html#resolve","extensions_inner_working.html#订阅","extensions_available.html#可用的扩展列表","extensions_available.html#analyzer","extensions_available.html#apollo-persisted-queries","extensions_available.html#apollo-tracing","extensions_available.html#apollo-studio","extensions_available.html#logger","extensions_available.html#opentelemetry","extensions_available.html#tracing","integrations.html#集成到-webserver","integrations_to_poem.html#poem","integrations_to_poem.html#请求例子","integrations_to_poem.html#订阅例子","integrations_to_poem.html#更多例子","integrations_to_warp.html#warp","integrations_to_warp.html#请求例子","integrations_to_warp.html#订阅例子","integrations_to_warp.html#更多例子","integrations_to_actix_web.html#actix-web","integrations_to_actix_web.html#请求例子","integrations_to_actix_web.html#订阅例子","integrations_to_actix_web.html#更多例子","advanced_topics.html#高级主题","custom_scalars.html#自定义标量","custom_scalars.html#使用scalar宏定义标量","dataloader.html#优化查询解决-n1-问题","dataloader.html#查询解析","dataloader.html#dataloader","dataloader.html#同一个-loader-支持多种数据类型","custom_directive.html#自定义指令","apollo_federation.html#apollo-federation-集成","apollo_federation.html#实体查找函数","apollo_federation.html#定义复合主键"],"index":{"documentStore":{"docInfo":{"0":{"body":12,"breadcrumbs":0,"title":0},"1":{"body":5,"breadcrumbs":2,"title":2},"10":{"body":80,"breadcrumbs":1,"title":0},"100":{"body":32,"breadcrumbs":1,"title":1},"101":{"body":1,"breadcrumbs":2,"title":1},"102":{"body":134,"breadcrumbs":1,"title":0},"103":{"body":90,"breadcrumbs":2,"title":1},"104":{"body":41,"breadcrumbs":2,"title":1},"105":{"body":92,"breadcrumbs":0,"title":0},"106":{"body":8,"breadcrumbs":4,"title":2},"107":{"body":62,"breadcrumbs":2,"title":0},"108":{"body":34,"breadcrumbs":2,"title":0},"11":{"body":25,"breadcrumbs":1,"title":0},"12":{"body":15,"breadcrumbs":1,"title":0},"13":{"body":63,"breadcrumbs":2,"title":1},"14":{"body":0,"breadcrumbs":3,"title":1},"15":{"body":23,"breadcrumbs":2,"title":0},"16":{"body":30,"breadcrumbs":3,"title":1},"17":{"body":67,"breadcrumbs":2,"title":0},"18":{"body":62,"breadcrumbs":3,"title":1},"19":{"body":44,"breadcrumbs":4,"title":2},"2":{"body":0,"breadcrumbs":0,"title":0},"20":{"body":33,"breadcrumbs":1,"title":0},"21":{"body":0,"breadcrumbs":3,"title":1},"22":{"body":66,"breadcrumbs":3,"title":1},"23":{"body":47,"breadcrumbs":2,"title":0},"24":{"body":74,"breadcrumbs":1,"title":0},"25":{"body":10,"breadcrumbs":1,"title":0},"26":{"body":50,"breadcrumbs":2,"title":1},"27":{"body":31,"breadcrumbs":2,"title":1},"28":{"body":78,"breadcrumbs":1,"title":0},"29":{"body":105,"breadcrumbs":2,"title":1},"3":{"body":21,"breadcrumbs":0,"title":0},"30":{"body":70,"breadcrumbs":1,"title":0},"31":{"body":59,"breadcrumbs":2,"title":1},"32":{"body":58,"breadcrumbs":1,"title":0},"33":{"body":34,"breadcrumbs":2,"title":1},"34":{"body":80,"breadcrumbs":1,"title":0},"35":{"body":0,"breadcrumbs":0,"title":0},"36":{"body":48,"breadcrumbs":0,"title":0},"37":{"body":69,"breadcrumbs":0,"title":0},"38":{"body":23,"breadcrumbs":1,"title":1},"39":{"body":2,"breadcrumbs":2,"title":1},"4":{"body":36,"breadcrumbs":1,"title":1},"40":{"body":0,"breadcrumbs":1,"title":0},"41":{"body":23,"breadcrumbs":1,"title":0},"42":{"body":29,"breadcrumbs":1,"title":0},"43":{"body":44,"breadcrumbs":1,"title":0},"44":{"body":0,"breadcrumbs":0,"title":0},"45":{"body":92,"breadcrumbs":2,"title":2},"46":{"body":50,"breadcrumbs":0,"title":0},"47":{"body":52,"breadcrumbs":0,"title":0},"48":{"body":20,"breadcrumbs":0,"title":0},"49":{"body":52,"breadcrumbs":0,"title":0},"5":{"body":32,"breadcrumbs":0,"title":0},"50":{"body":44,"breadcrumbs":0,"title":0},"51":{"body":79,"breadcrumbs":2,"title":2},"52":{"body":3,"breadcrumbs":0,"title":0},"53":{"body":29,"breadcrumbs":0,"title":0},"54":{"body":29,"breadcrumbs":1,"title":1},"55":{"body":91,"breadcrumbs":1,"title":1},"56":{"body":25,"breadcrumbs":1,"title":1},"57":{"body":50,"breadcrumbs":0,"title":0},"58":{"body":36,"breadcrumbs":0,"title":0},"59":{"body":27,"breadcrumbs":4,"title":2},"6":{"body":2,"breadcrumbs":1,"title":1},"60":{"body":1,"breadcrumbs":0,"title":0},"61":{"body":26,"breadcrumbs":0,"title":0},"62":{"body":27,"breadcrumbs":0,"title":0},"63":{"body":28,"breadcrumbs":0,"title":0},"64":{"body":27,"breadcrumbs":0,"title":0},"65":{"body":41,"breadcrumbs":0,"title":0},"66":{"body":2,"breadcrumbs":0,"title":0},"67":{"body":9,"breadcrumbs":0,"title":0},"68":{"body":14,"breadcrumbs":0,"title":0},"69":{"body":0,"breadcrumbs":0,"title":0},"7":{"body":2,"breadcrumbs":2,"title":2},"70":{"body":51,"breadcrumbs":0,"title":0},"71":{"body":33,"breadcrumbs":0,"title":0},"72":{"body":44,"breadcrumbs":0,"title":0},"73":{"body":29,"breadcrumbs":0,"title":0},"74":{"body":32,"breadcrumbs":0,"title":0},"75":{"body":34,"breadcrumbs":1,"title":1},"76":{"body":33,"breadcrumbs":0,"title":0},"77":{"body":3,"breadcrumbs":0,"title":0},"78":{"body":5,"breadcrumbs":1,"title":1},"79":{"body":43,"breadcrumbs":3,"title":3},"8":{"body":4,"breadcrumbs":0,"title":0},"80":{"body":15,"breadcrumbs":2,"title":2},"81":{"body":13,"breadcrumbs":2,"title":2},"82":{"body":5,"breadcrumbs":1,"title":1},"83":{"body":7,"breadcrumbs":1,"title":1},"84":{"body":8,"breadcrumbs":1,"title":1},"85":{"body":28,"breadcrumbs":2,"title":1},"86":{"body":0,"breadcrumbs":3,"title":1},"87":{"body":28,"breadcrumbs":2,"title":0},"88":{"body":29,"breadcrumbs":2,"title":0},"89":{"body":2,"breadcrumbs":2,"title":0},"9":{"body":22,"breadcrumbs":2,"title":1},"90":{"body":10,"breadcrumbs":3,"title":1},"91":{"body":52,"breadcrumbs":2,"title":0},"92":{"body":53,"breadcrumbs":2,"title":0},"93":{"body":2,"breadcrumbs":2,"title":0},"94":{"body":7,"breadcrumbs":5,"title":2},"95":{"body":39,"breadcrumbs":3,"title":0},"96":{"body":39,"breadcrumbs":3,"title":0},"97":{"body":3,"breadcrumbs":3,"title":0},"98":{"body":0,"breadcrumbs":0,"title":0},"99":{"body":29,"breadcrumbs":0,"title":0}},"docs":{"0":{"body":"Async-graphql是用 Rust 语言实现的 GraphQL 服务端库。它完全兼容 GraphQL 规范以及绝大部分的扩展功能,类型安全并且高性能。 你可以用 Rust 语言的方式来定义 Schema,过程宏会自动生成 GraphQL 查询的框架代码,没有扩展 Rust 的语法,意味着 Rustfmt 可以正常使用,我很看重这一点,这也是为什么我会开发Async-graphql的原因之一。","breadcrumbs":"介绍 » 介绍","id":"0","title":"介绍"},"1":{"body":"我喜欢 GraphQL 和 Rust,之前我一直用Juniper,它解决了我用 Rust 实现 GraphQL 服务器的问题,但也有一些遗憾,其中最重要的是它当时不支持 async/await,所以我决定做一个给自己用。","breadcrumbs":"介绍 » 为什么我要开发 Async-graphql?","id":"1","title":"为什么我要开发 Async-graphql?"},"10":{"body":"如果你希望其它类型能够重用SimpleObject,则可以定义泛型的SimpleObject,并指定具体的类型。 在下面的示例中,创建了两种SimpleObject类型: # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(SimpleObject)]\\n# struct SomeType { a: i32 }\\n# #[derive(SimpleObject)]\\n# struct SomeOtherType { a: i32 }\\n#[derive(SimpleObject)]\\n#[graphql(concrete(name = \\"SomeName\\", params(SomeType)))]\\n#[graphql(concrete(name = \\"SomeOtherName\\", params(SomeOtherType)))]\\npub struct SomeGenericObject { field1: Option, field2: String\\n} 注意:每个泛型参数必须实现OutputType,如上所示。 生成的 SDL 如下: type SomeName { field1: SomeType field2: String!\\n} type SomeOtherName { field1: SomeOtherType field2: String!\\n} 在其它Object中使用具体的泛型类型: # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(SimpleObject)]\\n# struct SomeType { a: i32 }\\n# #[derive(SimpleObject)]\\n# struct SomeOtherType { a: i32 }\\n# #[derive(SimpleObject)]\\n# #[graphql(concrete(name = \\"SomeName\\", params(SomeType)))]\\n# #[graphql(concrete(name = \\"SomeOtherName\\", params(SomeOtherType)))]\\n# pub struct SomeGenericObject {\\n# field1: Option,\\n# field2: String,\\n# }\\n#[derive(SimpleObject)]\\npub struct YetAnotherObject { a: SomeGenericObject, b: SomeGenericObject,\\n} 你可以将多个通用类型传递给params(),并用逗号分隔。","breadcrumbs":"类型系统 » 简单对象 (SimpleObject) » 泛型","id":"10","title":"泛型"},"100":{"body":"如果你的类型实现了serde :: Serialize和serde :: Deserialize,那么可以使用此宏更简单地定义标量。 # extern crate async_graphql;\\n# extern crate serde;\\n# use async_graphql::*;\\n# use serde::{Serialize, Deserialize};\\n# use std::collections::HashMap;\\n#[derive(Serialize, Deserialize)]\\nstruct MyValue { a: i32, b: HashMap, } scalar!(MyValue); // 重命名为 `MV`.\\n// scalar!(MyValue, \\"MV\\"); // 重命名为 `MV` 并且添加描述。\\n// scalar!(MyValue, \\"MV\\", \\"This is my value\\");","breadcrumbs":"高级主题 » 自定义标量 » 使用scalar!宏定义标量","id":"100","title":"使用scalar!宏定义标量"},"101":{"body":"您是否注意到某些 GraphQL 查询需要执行数百个数据库查询,这些查询通常包含重复的数据,让我们来看看为什么以及如何修复它。","breadcrumbs":"高级主题 » 优化查询(解决 N+1 问题) » 优化查询(解决 N+1 问题)","id":"101","title":"优化查询(解决 N+1 问题)"},"102":{"body":"想象一下,如果您有一个简单的查询,例如: query { todos { users { name } } } 实现User的 resolver 代码如下: struct User { id: u64,\\n} #[Object]\\nimpl User { async fn name(&self, ctx: &Context<\'_>) -> Result { let pool = ctx.data_unchecked::>(); let (name,): (String,) = sqlx::query_as(\\"SELECT name FROM user WHERE id = $1\\") .bind(self.id) .fetch_one(pool) .await?; Ok(name) }\\n} 执行查询将调用Todos的 resolver,该 resolver 执行SELECT * FROM todo并返回 N 个Todo对象。然后对每个Todo对象同时调用User的 resolver 执行SELECT name FROM user where id = $1。 例如: SELECT id, todo, user_id FROM todo\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1\\nSELECT name FROM user WHERE id = $1 执行了多次SELECT name FROM user WHERE id = $1,并且,大多数Todo对象都属于同一个用户,我们需要优化这些代码!","breadcrumbs":"高级主题 » 优化查询(解决 N+1 问题) » 查询解析","id":"102","title":"查询解析"},"103":{"body":"我们需要对查询分组,并且排除重复的查询。Dataloader就能完成这个工作, facebook 给出了一个请求范围的批处理和缓存解决方案。 下面是使用DataLoader来优化查询请求的例子: use async_graphql::*;\\nuse async_graphql::dataloader::*;\\nuse itertools::Itertools;\\nuse std::sync::Arc; struct UserNameLoader { pool: sqlx::Pool,\\n} impl Loader for UserNameLoader { type Value = String; type Error = Arc; async fn load(&self, keys: &[u64]) -> Result, Self::Error> { let query = format!(\\"SELECT name FROM user WHERE id IN ({})\\", keys.iter().join(\\",\\")); Ok(sqlx::query_as(query) .fetch(&self.pool) .map_ok(|name: String| name) .map_err(Arc::new) .try_collect().await?) }\\n} struct User { id: u64,\\n} #[Object]\\nimpl User { async fn name(&self, ctx: &Context<\'_>) -> Result { let loader = ctx.data_unchecked::>(); let name: Option = loader.load_one(self.id).await?; name.ok_or_else(|| \\"Not found\\".into()) }\\n} 要在 ctx 中获取 UserNameLoader,您必须将其和任务生成器(例如 async_std::task::spawn)注册到 Schema 中: let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription) .data(DataLoader::new( UserNameLoader, async_std::task::spawn, // 或者 `tokio::spawn` )) .finish(); 最终只需要两个查询语句,就查询出了我们想要的结果! SELECT id, todo, user_id FROM todo\\nSELECT name FROM user WHERE id IN (1, 2, 3, 4)","breadcrumbs":"高级主题 » 优化查询(解决 N+1 问题) » Dataloader","id":"103","title":"Dataloader"},"104":{"body":"你可以为同一个Loader实现多种数据类型,就像下面这样: struct PostgresLoader { pool: sqlx::Pool,\\n} impl Loader for PostgresLoader { type Value = User; type Error = Arc; async fn load(&self, keys: &[UserId]) -> Result, Self::Error> { // 从数据库中加载 User }\\n} impl Loader for PostgresLoader { type Value = Todo; type Error = sqlx::Error; async fn load(&self, keys: &[TodoId]) -> Result, Self::Error> { // 从数据库中加载 Todo }\\n}","breadcrumbs":"高级主题 » 优化查询(解决 N+1 问题) » 同一个 Loader 支持多种数据类型","id":"104","title":"同一个 Loader 支持多种数据类型"},"105":{"body":"Async-graphql可以很方便的自定义指令,这可以扩展 GraphQL 的行为。 创建一个自定义指令,需要实现 CustomDirective trait,然后用Directive宏生成一个工厂函数,该函数接收指令的参数并返回指令的实例。 目前Async-graphql仅支持添加FIELD位置的指令。 # extern crate async_graphql;\\n# use async_graphql::*;\\nstruct ConcatDirective { value: String,\\n} #[async_trait::async_trait]\\nimpl CustomDirective for ConcatDirective { async fn resolve_field(&self, _ctx: &Context<\'_>, resolve: ResolveFut<\'_>) -> ServerResult> { resolve.await.map(|value| { value.map(|value| match value { Value::String(str) => Value::String(str + &self.value), _ => value, }) }) }\\n} #[Directive(location = \\"Field\\")]\\nfn concat(value: String) -> impl CustomDirective { ConcatDirective { value }\\n} 创建模式时注册指令: # extern crate async_graphql;\\n# use async_graphql::*;\\n# struct Query;\\n# #[Object]\\n# impl Query { async fn version(&self) -> &str { \\"1.0\\" } }\\n# struct ConcatDirective { value: String, }\\n# #[async_trait::async_trait]\\n# impl CustomDirective for ConcatDirective {\\n# async fn resolve_field(&self, _ctx: &Context<\'_>, resolve: ResolveFut<\'_>) -> ServerResult> { todo!() }\\n# }\\n# #[Directive(location = \\"Field\\")]\\n# fn concat(value: String) -> impl CustomDirective { ConcatDirective { value } }\\nlet schema = Schema::build(Query, EmptyMutation, EmptySubscription) .directive(concat) .finish();","breadcrumbs":"高级主题 » 自定义指令 » 自定义指令","id":"105","title":"自定义指令"},"106":{"body":"Apollo Federation是一个GraphQL网关,它可以组合多个 GraphQL 服务,允许每服务仅实现它负责的那一部分数据,参考 官方文档 。 Async-graphql可以完全支持Apollo Federation的所有功能,但需要对Schema定义做一些小小的改造。 async_graphql::Object和async_graphql::Interface的extends属性声明这个类别是一个已有类型的扩充。 字段的external属性声明这个字段定义来自其它服务。 字段的provides属性用于要求网关提供的字段集。 字段的requires属性表示解析该字段值需要依赖该类型的字段集。","breadcrumbs":"高级主题 » Apollo Federation 集成 » Apollo Federation 集成","id":"106","title":"Apollo Federation 集成"},"107":{"body":"# extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(SimpleObject)]\\n# struct User { id: ID }\\nstruct Query; #[Object]\\nimpl Query { #[graphql(entity)] async fn find_user_by_id(&self, id: ID) -> User { User { id } } #[graphql(entity)] async fn find_user_by_id_with_username(&self, #[graphql(key)] id: ID, username: String) -> User { User { id } } #[graphql(entity)] async fn find_user_by_id_and_username(&self, id: ID, username: String) -> User { User { id } }\\n} 注意这三个查找函数的不同,他们都是查找 User 对象。 find_user_by_id 使用id查找User对象,User对象的 key 是id。 find_user_by_id_with_username 使用id查找User对象,User对象的 key 是id,并且请求User对象的username字段值。 find_user_by_id_and_username 使用id和username查找User对象,User对象的 key 是id和username。 完整的例子请参考 https://github.com/async-graphql/examples/tree/master/federation","breadcrumbs":"高级主题 » Apollo Federation 集成 » 实体查找函数","id":"107","title":"实体查找函数"},"108":{"body":"一个主键可以包含多个字段,什么包含嵌套字段,你可以用InputObject来实现一个嵌套字段的 Key 类型。 下面的例子中User对象的主键是key { a b }。 # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(SimpleObject)]\\n# struct User { id: i32 }\\n#[derive(InputObject)]\\nstruct NestedKey { a: i32, b: i32,\\n} struct Query; #[Object]\\nimpl Query { #[graphql(entity)] async fn find_user_by_key(&self, key: NestedKey) -> User { User { id: key.a } }\\n}","breadcrumbs":"高级主题 » Apollo Federation 集成 » 定义复合主键","id":"108","title":"定义复合主键"},"11":{"body":"有时 GraphQL 对象的大多数字段仅返回结构成员的值,但是少数字段需要计算。通常我们使用Object宏来定义这样一个 GraphQL 对象。 用ComplexObject宏可以更漂亮的完成这件事,我们可以使用SimpleObject宏来定义 一些简单的字段,并使用ComplexObject宏来定义其他一些需要计算的字段。 # extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(SimpleObject)]\\n#[graphql(complex)] // 注意:如果你希望 ComplexObject 宏生效,complex 属性是必须的\\nstruct MyObj { a: i32, b: i32,\\n} #[ComplexObject]\\nimpl MyObj { async fn c(&self) -> i32 { self.a + self.b }\\n}","breadcrumbs":"类型系统 » 简单对象 (SimpleObject) » 复杂字段","id":"11","title":"复杂字段"},"12":{"body":"# extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(SimpleObject, InputObject)]\\n#[graphql(input_name = \\"MyObjInput\\")] // 注意:你必须用 input_name 属性为输入类型定义一个新的名称,否则将产生一个运行时错误。\\nstruct MyObj { a: i32, b: i32,\\n}","breadcrumbs":"类型系统 » 简单对象 (SimpleObject) » 同时用于输入和输出","id":"12","title":"同时用于输入和输出"},"13":{"body":"和简单对象不同,对象必须为所有的字段定义 Resolver 函数,Resolver 函数定义在 impl 块中。 一个 Resolver 函数必须是异步的,它的第一个参数必须是&self,第二个参数是可选的Context,接下来是字段的参数。 Resolver 函数用于计算字段的值,你可以执行一个数据库查询,并返回查询结果。 函数的返回值是字段的类型 ,你也可以返回一个async_graphql::Result类型,这样能够返回一个错误,这个错误信息将输出到查询结果中。 在查询数据库时,你可能需要一个数据库连接池对象,这个对象是个全局的,你可以在创建 Schema 的时候,用SchemaBuilder::data函数设置Schema数据,用Context::data函数设置Context数据。下面的value_from_db字段展示了如何从Context中获取一个数据库连接。 # extern crate async_graphql;\\n# struct Data { pub name: String }\\n# struct DbConn {}\\n# impl DbConn {\\n# fn query_something(&self, id: i64) -> std::result::Result { Ok(Data {name:\\"\\".into()})}\\n# }\\n# struct DbPool {}\\n# impl DbPool {\\n# fn take(&self) -> DbConn { DbConn {} } # }\\nuse async_graphql::*; struct MyObject { value: i32,\\n} #[Object]\\nimpl MyObject { async fn value(&self) -> String { self.value.to_string() } async fn value_from_db( &self, ctx: &Context<\'_>, #[graphql(desc = \\"Id of object\\")] id: i64 ) -> Result { let conn = ctx.data::()?.take(); Ok(conn.query_something(id)?.name) }\\n}","breadcrumbs":"类型系统 » 对象 (Object) » 对象 (Object)","id":"13","title":"对象 (Object)"},"14":{"body":"Context的主要目标是获取附加到Schema的全局数据或者与正在处理的实际查询相关的数据。","breadcrumbs":"类型系统 » 对象 (Object) » 查询上下文 (Context) » 查询上下文 (Context)","id":"14","title":"查询上下文 (Context)"},"15":{"body":"在Context中你可以存放全局数据,例如环境变量、数据库连接池,以及你在每个查询中可能需要的任何内容。 数据必须实现Send和Sync。 你可以通过调用ctx.data::()来获取查询中的数据。 主意:如果 Resolver 函数的返回值是从Context中借用的,则需要明确说明参数的生命周期。 下面的例子展示了如何从Context中借用数据。 # extern crate async_graphql;\\nuse async_graphql::*; struct Query; #[Object]\\nimpl Query { async fn borrow_from_context_data<\'ctx>( &self, ctx: &Context<\'ctx> ) -> Result<&\'ctx String> { ctx.data::() }\\n}","breadcrumbs":"类型系统 » 对象 (Object) » 查询上下文 (Context) » 存储数据","id":"15","title":"存储数据"},"16":{"body":"你可以在创建Schema时将数据放入上下文中,这对于不会更改的数据非常有用,例如连接池。 # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(Default,SimpleObject)]\\n# struct Query { version: i32}\\n# struct EnvStruct;\\n# let env_struct = EnvStruct;\\n# struct S3Object;\\n# let s3_storage = S3Object;\\n# struct DBConnection;\\n# let db_core = DBConnection;\\nlet schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription) .data(env_struct) .data(s3_storage) .data(db_core) .finish();","breadcrumbs":"类型系统 » 对象 (Object) » 查询上下文 (Context) » Schema 数据","id":"16","title":"Schema 数据"},"17":{"body":"你可以在执行请求时将数据放入上下文中,它对于身份验证数据很有用。 一个使用warp的小例子: # extern crate async_graphql;\\n# extern crate async_graphql_warp;\\n# extern crate warp;\\n# use async_graphql::*;\\n# use warp::{Filter, Reply};\\n# use std::convert::Infallible;\\n# #[derive(Default, SimpleObject)]\\n# struct Query { name: String }\\n# struct AuthInfo { pub token: Option }\\n# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();\\n# let schema_filter = async_graphql_warp::graphql(schema);\\nlet graphql_post = warp::post() .and(warp::path(\\"graphql\\")) .and(warp::header::optional(\\"Authorization\\")) .and(schema_filter) .and_then( |auth: Option, (schema, mut request): (Schema, async_graphql::Request)| async move { // Do something to get auth data from the header let your_auth_data = AuthInfo { token: auth }; let response = schema .execute( request .data(your_auth_data) ).await; Ok::<_, Infallible>(async_graphql_warp::GraphQLResponse::from(response)) });","breadcrumbs":"类型系统 » 对象 (Object) » 查询上下文 (Context) » 请求数据","id":"17","title":"请求数据"},"18":{"body":"使用Context你还可以插入或添加 HTTP 头。 # extern crate async_graphql;\\n# extern crate http;\\n# use ::http::header::ACCESS_CONTROL_ALLOW_ORIGIN;\\n# use async_graphql::*;\\n# struct Query;\\n#[Object]\\nimpl Query { async fn greet(&self, ctx: &Context<\'_>) -> String { // Headers can be inserted using the `http` constants let was_in_headers = ctx.insert_http_header(ACCESS_CONTROL_ALLOW_ORIGIN, \\"*\\"); // They can also be inserted using &str let was_in_headers = ctx.insert_http_header(\\"Custom-Header\\", \\"1234\\"); // If multiple headers with the same key are `inserted` then the most recent // one overwrites the previous. If you want multiple headers for the same key, use // `append_http_header` for subsequent headers let was_in_headers = ctx.append_http_header(\\"Custom-Header\\", \\"Hello World\\"); String::from(\\"Hello world\\") }\\n}","breadcrumbs":"类型系统 » 对象 (Object) » 查询上下文 (Context) » HTTP 头","id":"18","title":"HTTP 头"},"19":{"body":"有时你想知道子查询中请求了哪些字段用于优化数据处理,则可以使用ctx.field()读取查询中的字段,它将提供一个SelectionField,允许你在当前字段和子字段之间导航。 如果要跨查询或子查询执行搜索,则不必使用 SelectionField 手动执行此操作,可以使用 ctx.look_ahead() 来执行选择。 # extern crate async_graphql;\\nuse async_graphql::*; #[derive(SimpleObject)]\\nstruct Detail { c: i32, d: i32,\\n} #[derive(SimpleObject)]\\nstruct MyObj { a: i32, b: i32, detail: Detail,\\n} struct Query; #[Object]\\nimpl Query { async fn obj(&self, ctx: &Context<\'_>) -> MyObj { if ctx.look_ahead().field(\\"a\\").exists() { // This is a query like `obj { a }` } else if ctx.look_ahead().field(\\"detail\\").field(\\"c\\").exists() { // This is a query like `obj { detail { c } }` } else { // This query doesn\'t have `a` } unimplemented!() }\\n}","breadcrumbs":"类型系统 » 对象 (Object) » 查询上下文 (Context) » Selection / LookAhead","id":"19","title":"Selection / LookAhead"},"2":{"body":"","breadcrumbs":"快速开始 » 快速开始","id":"2","title":"快速开始"},"20":{"body":"Resolver 函数可以返回一个 Result 类型,以下是 Result 的定义: type Result = std::result::Result; 任何错误都能够被转换为Error,并且你还能扩展标准的错误信息。 下面是一个例子,解析一个输入的字符串到整数,当解析失败时返回错误,并且附加额外的错误信息。 # extern crate async_graphql;\\n# use std::num::ParseIntError;\\nuse async_graphql::*; struct Query; #[Object]\\nimpl Query { async fn parse_with_extensions(&self, input: String) -> Result { Ok(\\"234a\\" .parse() .map_err(|err: ParseIntError| err.extend_with(|_, e| e.set(\\"code\\", 400)))?) }\\n}","breadcrumbs":"类型系统 » 对象 (Object) » 错误处理 » 错误处理","id":"20","title":"错误处理"},"21":{"body":"","breadcrumbs":"类型系统 » 对象 (Object) » 合并对象 (MergedObject) » 合并对象 (MergedObject)","id":"21","title":"合并对象 (MergedObject)"},"22":{"body":"通常我们在 Rust 中可以为同一类型创建多个实现,但由于过程宏的限制,无法为同一个类型创建多个 Object 实现。例如,下面的代码将无法通过编译。 #[Object]\\nimpl MyObject { async fn field1(&self) -> i32 { todo!() }\\n} #[Object]\\nimpl MyObject { async fn field2(&self) -> i32 { todo!() }\\n} 用 #[derive(MergedObject)] 宏允许你合并多个独立的 Object 为一个。 提示: 每个#[Object]需要一个唯一的名称,即使在一个MergedObject内,所以确保每个对象有单独的名称。 # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(SimpleObject)]\\n# struct User { a: i32 }\\n# #[derive(SimpleObject)]\\n# struct Movie { a: i32 }\\n#[derive(Default)]\\nstruct UserQuery; #[Object]\\nimpl UserQuery { async fn users(&self) -> Vec { todo!() }\\n} #[derive(Default)]\\nstruct MovieQuery; #[Object]\\nimpl MovieQuery { async fn movies(&self) -> Vec { todo!() }\\n} #[derive(MergedObject, Default)]\\nstruct Query(UserQuery, MovieQuery); let schema = Schema::new( Query::default(), EmptyMutation, EmptySubscription\\n); ⚠️ 合并的对象无法在 Interface 中使用。","breadcrumbs":"类型系统 » 对象 (Object) » 合并对象 (MergedObject) » 为同一类型实现多次 Object","id":"22","title":"为同一类型实现多次 Object"},"23":{"body":"和MergedObject一样,你可以派生MergedSubscription来合并单独的#[Subscription]块。 像合并对象一样,每个订阅块都需要一个唯一的名称。 # extern crate async_graphql;\\n# use async_graphql::*;\\n# use futures_util::stream::{Stream};\\n# #[derive(Default,SimpleObject)]\\n# struct Query { a: i32 }\\n#[derive(Default)]\\nstruct Subscription1; #[Subscription]\\nimpl Subscription1 { async fn events1(&self) -> impl Stream { futures_util::stream::iter(0..10) }\\n} #[derive(Default)]\\nstruct Subscription2; #[Subscription]\\nimpl Subscription2 { async fn events2(&self) -> impl Stream { futures_util::stream::iter(10..20) }\\n} #[derive(MergedSubscription, Default)]\\nstruct Subscription(Subscription1, Subscription2); let schema = Schema::new( Query::default(), EmptyMutation, Subscription::default()\\n);","breadcrumbs":"类型系统 » 对象 (Object) » 合并对象 (MergedObject) » 合并订阅","id":"23","title":"合并订阅"},"24":{"body":"有时两个字段有一样的查询逻辑,仅仅是输出的类型不同,在 async-graphql 中,你可以为它创建派生字段。 在以下例子中,你已经有一个duration_rfc2822字段输出RFC2822格式的时间格式,然后复用它派生一个新的date_rfc3339字段。 # extern crate chrono;\\n# use chrono::Utc;\\n# extern crate async_graphql;\\n# use async_graphql::*;\\nstruct DateRFC3339(chrono::DateTime);\\nstruct DateRFC2822(chrono::DateTime); #[Scalar]\\nimpl ScalarType for DateRFC3339 { fn parse(value: Value) -> InputValueResult { todo!() } fn to_value(&self) -> Value { Value::String(self.0.to_rfc3339()) }\\n} #[Scalar]\\nimpl ScalarType for DateRFC2822 { fn parse(value: Value) -> InputValueResult { todo!() } fn to_value(&self) -> Value { Value::String(self.0.to_rfc2822()) }\\n} impl From for DateRFC3339 { fn from(value: DateRFC2822) -> Self { DateRFC3339(value.0) }\\n} struct Query; #[Object]\\nimpl Query { #[graphql(derived(name = \\"date_rfc3339\\", into = \\"DateRFC3339\\"))] async fn duration_rfc2822(&self, arg: String) -> DateRFC2822 { todo!() }\\n} 它将呈现为如下 GraphQL: type Query { duration_rfc2822(arg: String): DateRFC2822! duration_rfc3339(arg: String): DateRFC3339!\\n}","breadcrumbs":"类型系统 » 对象 (Object) » 派生字段 » 派生字段","id":"24","title":"派生字段"},"25":{"body":"因为 孤儿规则 ,以下代码无法通过编译: impl From> for Vec { ...\\n} 因此,你将无法为现有的包装类型结构(如Vec或Option)生成派生字段。 但是当你为 T 实现了 From 后,你可以为 Vec 实现 From>,为 Option 实现 From>. 使用 with 参数来定义一个转换函数,而不是用 Into::into。","breadcrumbs":"类型系统 » 对象 (Object) » 派生字段 » 包装类型","id":"25","title":"包装类型"},"26":{"body":"# extern crate serde;\\n# use serde::{Serialize, Deserialize};\\n# extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(Serialize, Deserialize, Clone)]\\nstruct ValueDerived(String); #[derive(Serialize, Deserialize, Clone)]\\nstruct ValueDerived2(String); scalar!(ValueDerived);\\nscalar!(ValueDerived2); impl From for ValueDerived2 { fn from(value: ValueDerived) -> Self { ValueDerived2(value.0) }\\n} fn option_to_option>(value: Option) -> Option { value.map(|x| x.into())\\n} #[derive(SimpleObject)]\\nstruct TestObj { #[graphql(derived(owned, name = \\"value2\\", into = \\"Option\\", with = \\"option_to_option\\"))] pub value1: Option,\\n}","breadcrumbs":"类型系统 » 对象 (Object) » 派生字段 » Example","id":"26","title":"Example"},"27":{"body":"定义枚举相当简单,直接给出一个例子。 Async-graphql 会自动把枚举项的名称转换为 GraphQL 标准的大写加下划线形式,你也可以用name属性自已定义名称。 # extern crate async_graphql;\\nuse async_graphql::*; /// One of the films in the Star Wars Trilogy\\n#[derive(Enum, Copy, Clone, Eq, PartialEq)]\\npub enum Episode { /// Released in 1977. NewHope, /// Released in 1980. Empire, /// Released in 1983. #[graphql(name=\\"AAA\\")] Jedi,\\n}","breadcrumbs":"类型系统 » 枚举 (Enum) » 枚举 (Enum)","id":"27","title":"枚举 (Enum)"},"28":{"body":"Rust 的 孤儿规则 要求特质或您要实现特质的类型必须在相同的板条箱中定义,因此你不能向 GraphQL 公开外部枚举类型。为了创建Enum类型,一种常见的解决方法是创建一个新的与现有远程枚举类型同等的枚举。 # extern crate async_graphql;\\n# mod remote_crate { pub enum RemoteEnum { A, B, C } }\\nuse async_graphql::*; /// Provides parity with a remote enum type\\n#[derive(Enum, Copy, Clone, Eq, PartialEq)]\\npub enum LocalEnum { A, B, C,\\n} /// Conversion interface from remote type to our local GraphQL enum type\\nimpl From for LocalEnum { fn from(e: remote_crate::RemoteEnum) -> Self { match e { remote_crate::RemoteEnum::A => Self::A, remote_crate::RemoteEnum::B => Self::B, remote_crate::RemoteEnum::C => Self::C, } }\\n} 该过程很繁琐,需要多个步骤才能使本地枚举和远程枚举保持同步。Async_graphql提供了一个方便的功能,可在派生Enum之后通过附加属性生成 LocalEnum 的From 以及相反的From for remote_crate::RemoteEnum: # extern crate async_graphql;\\n# use async_graphql::*;\\n# mod remote_crate { pub enum RemoteEnum { A, B, C } }\\n#[derive(Enum, Copy, Clone, Eq, PartialEq)]\\n#[graphql(remote = \\"remote_crate::RemoteEnum\\")]\\nenum LocalEnum { A, B, C,\\n}","breadcrumbs":"类型系统 » 枚举 (Enum) » 封装外部枚举类型","id":"28","title":"封装外部枚举类型"},"29":{"body":"接口用于抽象具有特定字段集合的对象,Async-graphql内部实现实际上是一个包装器,包装器转发接口上定义的 Resolver 函数到实现该接口的对象,所以接口类型所包含的字段类型,参数都必须和实现该接口的对象完全匹配。 Async-graphql自动实现了对象到接口的转换,把一个对象类型转换为接口类型只需要调用Into::into。 接口字段的name属性表示转发的 Resolver 函数,并且它将被转换为驼峰命名作为字段名称。 如果你需要自定义 GraphQL 接口字段名称,可以同时使用name和method属性。 当name和method属性同时存在时,name是 GraphQL 接口字段名,而method是 Resolver 函数名。 当只有name存在时,转换为驼峰命名后的name是 GraphQL 接口字段名,而name是 Resolver 函数名。 # extern crate async_graphql;\\nuse async_graphql::*; struct Circle { radius: f32,\\n} #[Object]\\nimpl Circle { async fn area(&self) -> f32 { std::f32::consts::PI * self.radius * self.radius } async fn scale(&self, s: f32) -> Shape { Circle { radius: self.radius * s }.into() } #[graphql(name = \\"short_description\\")] async fn short_description(&self) -> String { \\"Circle\\".to_string() }\\n} struct Square { width: f32,\\n} #[Object]\\nimpl Square { async fn area(&self) -> f32 { self.width * self.width } async fn scale(&self, s: f32) -> Shape { Square { width: self.width * s }.into() } #[graphql(name = \\"short_description\\")] async fn short_description(&self) -> String { \\"Square\\".to_string() }\\n} #[derive(Interface)]\\n#[graphql( field(name = \\"area\\", ty = \\"f32\\"), field(name = \\"scale\\", ty = \\"Shape\\", arg(name = \\"s\\", ty = \\"f32\\")), field(name = \\"short_description\\", method = \\"short_description\\", ty = \\"String\\")\\n)]\\nenum Shape { Circle(Circle), Square(Square),\\n}","breadcrumbs":"类型系统 » 接口 (Interface) » 接口 (Interface)","id":"29","title":"接口 (Interface)"},"3":{"body":"[dependencies]\\nasync-graphql = \\"4.0\\"\\nasync-graphql-actix-web = \\"4.0\\" # 如果你需要集成到 Actix-web\\nasync-graphql-warp = \\"4.0\\" # 如果你需要集成到 Warp\\nasync-graphql-tide = \\"4.0\\" # 如果你需要集成到 Tide","breadcrumbs":"快速开始 » 添加依赖","id":"3","title":"添加依赖"},"30":{"body":"Async-graphql在初始化阶段从Schema开始遍历并注册所有被直接或者间接引用的类型,如果某个接口没有被引用到,那么它将不会存在于注册表中,就像下面的例子, 即使MyObject实现了MyInterface,但由于Schema中并没有引用MyInterface,类型注册表中将不会存在MyInterface类型的信息。 # extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(Interface)]\\n#[graphql( field(name = \\"name\\", ty = \\"String\\"),\\n)]\\nenum MyInterface { MyObject(MyObject),\\n} #[derive(SimpleObject)]\\nstruct MyObject { name: String,\\n} struct Query; #[Object]\\nimpl Query { async fn obj(&self) -> MyObject { todo!() }\\n} type MySchema = Schema; 你需要在构造 Schema 时手工注册MyInterface类型: # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(Interface)]\\n# #[graphql(field(name = \\"name\\", ty = \\"String\\"))]\\n# enum MyInterface { MyObject(MyObject) }\\n# #[derive(SimpleObject)]\\n# struct MyObject { name: String, }\\n# struct Query;\\n# #[Object]\\n# impl Query { async fn version(&self) -> &str { \\"1.0\\" } } Schema::build(Query, EmptyMutation, EmptySubscription) .register_output_type::() .finish();","breadcrumbs":"类型系统 » 接口 (Interface) » 手工注册接口类型","id":"30","title":"手工注册接口类型"},"31":{"body":"联合的定义和接口非常像, 但不允许定义字段 。这两个类型的实现原理也差不多,对于Async-graphql来说,联合类型是接口类型的子集。 下面把接口定义的例子做一个小小的修改,去掉字段的定义。 # extern crate async_graphql;\\nuse async_graphql::*; struct Circle { radius: f32,\\n} #[Object]\\nimpl Circle { async fn area(&self) -> f32 { std::f32::consts::PI * self.radius * self.radius } async fn scale(&self, s: f32) -> Shape { Circle { radius: self.radius * s }.into() }\\n} struct Square { width: f32,\\n} #[Object]\\nimpl Square { async fn area(&self) -> f32 { self.width * self.width } async fn scale(&self, s: f32) -> Shape { Square { width: self.width * s }.into() }\\n} #[derive(Union)]\\nenum Shape { Circle(Circle), Square(Square),\\n}","breadcrumbs":"类型系统 » 联合 (Union) » 联合 (Union)","id":"31","title":"联合 (Union)"},"32":{"body":"GraphQL 的有个限制是Union类型内不能包含其它联合类型。所有成员必须为Object。 位置支持嵌套Union,我们可以用#graphql(flatten),是它们合并到上级Union类型。 # extern crate async_graphql;\\n#[derive(async_graphql::Union)]\\npub enum TopLevelUnion { A(A), // 除非我们使用 `flatten` 属性,否则将无法编译 #[graphql(flatten)] B(B),\\n} #[derive(async_graphql::SimpleObject)]\\npub struct A { a: i32, // ...\\n} #[derive(async_graphql::Union)]\\npub enum B { C(C), D(D),\\n} #[derive(async_graphql::SimpleObject)]\\npub struct C { c: i32, // ...\\n} #[derive(async_graphql::SimpleObject)]\\npub struct D { d: i32, // ...\\n} 上面的示例将顶级Union转换为以下等效形式: # extern crate async_graphql;\\n# #[derive(async_graphql::SimpleObject)]\\n# struct A { a: i32 }\\n# #[derive(async_graphql::SimpleObject)]\\n# struct C { c: i32 }\\n# #[derive(async_graphql::SimpleObject)]\\n# struct D { d: i32 }\\n#[derive(async_graphql::Union)]\\npub enum TopLevelUnion { A(A), C(C), D(D),\\n}","breadcrumbs":"类型系统 » 联合 (Union) » 展平嵌套联合","id":"32","title":"展平嵌套联合"},"33":{"body":"你可以定义一个对象作为参数类型,GraphQL 称之为Input Object,输入对象的定义方式和 简单对象 很像,不同的是,简单对象只能用于输出,而输入对象只能用于输入。 你也通过可选的#[graphql]属性来给字段添加描述,重命名。 # extern crate async_graphql;\\n# #[derive(SimpleObject)]\\n# struct User { a: i32 }\\nuse async_graphql::*; #[derive(InputObject)]\\nstruct Coordinate { latitude: f64, longitude: f64,\\n} struct Mutation; #[Object]\\nimpl Mutation { async fn users_at_location(&self, coordinate: Coordinate, radius: f64) -> Vec { // 将坐标写入数据库 // ...\\n# todo!() }\\n}","breadcrumbs":"类型系统 » 输入对象 (InputObject) » 输入对象 (InputObject)","id":"33","title":"输入对象 (InputObject)"},"34":{"body":"如果你希望其它类型能够重用InputObject,则可以定义泛型的InputObject,并指定具体的类型。 在下面的示例中,创建了两种InputObject类型: # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(InputObject)]\\n# struct SomeType { a: i32 }\\n# #[derive(InputObject)]\\n# struct SomeOtherType { a: i32 }\\n#[derive(InputObject)]\\n#[graphql(concrete(name = \\"SomeName\\", params(SomeType)))]\\n#[graphql(concrete(name = \\"SomeOtherName\\", params(SomeOtherType)))]\\npub struct SomeGenericInput { field1: Option, field2: String\\n} 注意:每个泛型参数必须实现InputType,如上所示。 生成的 SDL 如下: input SomeName { field1: SomeType field2: String!\\n} input SomeOtherName { field1: SomeOtherType field2: String!\\n} 在其它InputObject中使用具体的泛型类型: # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(InputObject)]\\n# struct SomeType { a: i32 }\\n# #[derive(InputObject)]\\n# struct SomeOtherType { a: i32 }\\n# #[derive(InputObject)]\\n# #[graphql(concrete(name = \\"SomeName\\", params(SomeType)))]\\n# #[graphql(concrete(name = \\"SomeOtherName\\", params(SomeOtherType)))]\\n# pub struct SomeGenericInput {\\n# field1: Option,\\n# field2: String\\n# }\\n#[derive(InputObject)]\\npub struct YetAnotherInput { a: SomeGenericInput, b: SomeGenericInput,\\n} 你可以将多个通用类型传递给params(),并用逗号分隔。","breadcrumbs":"类型系统 » 输入对象 (InputObject) » 泛型","id":"34","title":"泛型"},"35":{"body":"你可以为输入值类型定义默认值,下面展示了在不同类型上默认值的定义方法。","breadcrumbs":"类型系统 » 默认值 » 默认值","id":"35","title":"默认值"},"36":{"body":"# extern crate async_graphql;\\nuse async_graphql::*; struct Query; fn my_default() -> i32 { 30\\n} #[Object]\\nimpl Query { // value 参数的默认值为 0,它会调用 i32::default() async fn test1(&self, #[graphql(default)] value: i32) -> i32 { todo!() } // value 参数的默认值为 10 async fn test2(&self, #[graphql(default = 10)] value: i32) -> i32 { todo!() } // value 参数的默认值使用 my_default 函数的返回结果,值为 30 async fn test3(&self, #[graphql(default_with = \\"my_default()\\")] value: i32) -> i32 { todo!() }\\n}","breadcrumbs":"类型系统 » 默认值 » 对象字段参数","id":"36","title":"对象字段参数"},"37":{"body":"# extern crate async_graphql;\\n# fn my_default() -> i32 { 5 }\\n# struct MyObj;\\n# #[Object]\\n# impl MyObj {\\n# async fn test1(&self, value: i32) -> i32 { todo!() }\\n# async fn test2(&self, value: i32) -> i32 { todo!() }\\n# async fn test3(&self, value: i32) -> i32 { todo!() }\\n# }\\nuse async_graphql::*; #[derive(Interface)]\\n#[graphql( field(name = \\"test1\\", ty = \\"i32\\", arg(name = \\"value\\", ty = \\"i32\\", default)), field(name = \\"test2\\", ty = \\"i32\\", arg(name = \\"value\\", ty = \\"i32\\", default = 10)), field(name = \\"test3\\", ty = \\"i32\\", arg(name = \\"value\\", ty = \\"i32\\", default_with = \\"my_default()\\")),\\n)]\\nenum MyInterface { MyObj(MyObj),\\n}","breadcrumbs":"类型系统 » 默认值 » 接口字段参数","id":"37","title":"接口字段参数"},"38":{"body":"# extern crate async_graphql;\\n# fn my_default() -> i32 { 5 }\\nuse async_graphql::*; #[derive(InputObject)]\\nstruct MyInputObject { #[graphql(default)] value1: i32, #[graphql(default = 10)] value2: i32, #[graphql(default_with = \\"my_default()\\")] value3: i32,\\n}","breadcrumbs":"类型系统 » 默认值 » 输入对象 (InputObject)","id":"38","title":"输入对象 (InputObject)"},"39":{"body":"在定义了基本的类型之后,需要定义一个模式把他们组合起来,模式由三种类型组成,查询对象,变更对象和订阅对象,其中变更对象和订阅对象是可选的。 当模式创建时,Async-graphql会遍历所有对象图,并注册所有类型。这意味着,如果定义了 GraphQL 对象但从未引用,那么此对象就不会暴露在模式中。","breadcrumbs":"定义模式 (Schema) » 定义模式 (Schema)","id":"39","title":"定义模式 (Schema)"},"4":{"body":"一个 GraphQL 的 Schema 包含一个必须的查询 (Query) 根对象,可选的变更 (Mutation) 根对象和可选的订阅 (Subscription) 根对象,这些对象类型都是用 Rust 语言的结构来描述它们,结构的字段对应 GraphQL 对象的字段。 Async-graphql 实现了常用数据类型到 GraphQL 类型的映射,例如i32, f64, Option, Vec等。同时,你也能够 扩展这些基础类型 ,基础数据类型在 GraphQL 里面称为标量。 下面是一个简单的例子,我们只提供一个查询,返回a和b的和。 # extern crate async_graphql;\\nuse async_graphql::*; struct Query; #[Object]\\nimpl Query { /// Returns the sum of a and b async fn add(&self, a: i32, b: i32) -> i32 { a + b }\\n}","breadcrumbs":"快速开始 » 写一个 Schema","id":"4","title":"写一个 Schema"},"40":{"body":"","breadcrumbs":"定义模式 (Schema) » 查询和变更 » 查询和变更","id":"40","title":"查询和变更"},"41":{"body":"查询根对象是一个 GraphQL 对象,定义类似其它对象。查询对象的所有字段 Resolver 函数是并发执行的。 # extern crate async_graphql;\\nuse async_graphql::*;\\n# #[derive(SimpleObject)]\\n# struct User { a: i32 } struct Query; #[Object]\\nimpl Query { async fn user(&self, username: String) -> Result> { // 在数据库中查找用户\\n# todo!() }\\n}","breadcrumbs":"定义模式 (Schema) » 查询和变更 » 查询根对象","id":"41","title":"查询根对象"},"42":{"body":"变更根对象也是一个 GraphQL,但变更根对象的执行是顺序的,只有第一个变更执行完成之后才会执行下一个。 下面的变更根对象提供用户注册和登录操作: # extern crate async_graphql;\\nuse async_graphql::*; struct Mutation; #[Object]\\nimpl Mutation { async fn signup(&self, username: String, password: String) -> Result { // 用户注册\\n# todo!() } async fn login(&self, username: String, password: String) -> Result { // 用户登录并生成 token\\n# todo!() }\\n}","breadcrumbs":"定义模式 (Schema) » 查询和变更 » 变更根对象","id":"42","title":"变更根对象"},"43":{"body":"订阅根对象和其它根对象定义稍有不同,它的 Resolver 函数总是返回一个 Stream 或者Result,而字段参数通常作为数据的筛选条件。 下面的例子订阅一个整数流,它每秒产生一个整数,参数step指定了整数的步长,默认为 1。 # extern crate async_graphql;\\n# use std::time::Duration;\\n# use async_graphql::futures_util::stream::Stream;\\n# use async_graphql::futures_util::StreamExt;\\n# extern crate tokio_stream;\\n# extern crate tokio;\\nuse async_graphql::*; struct Subscription; #[Subscription]\\nimpl Subscription { async fn integers(&self, #[graphql(default = 1)] step: i32) -> impl Stream { let mut value = 0; tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(Duration::from_secs(1))) .map(move |_| { value += step; value }) }\\n}","breadcrumbs":"定义模式 (Schema) » 订阅 » 订阅","id":"43","title":"订阅"},"44":{"body":"","breadcrumbs":"实用功能 » 实用功能","id":"44","title":"实用功能"},"45":{"body":"你可以为Object, SimpleObject, ComplexObject和Subscription的字段定义守卫,它将在调用字段的 Resolver 函数前执行,如果失败则返回一个错误。 # extern crate async_graphql;\\n# use async_graphql::*;\\n#[derive(Eq, PartialEq, Copy, Clone)]\\nenum Role { Admin, Guest,\\n} struct RoleGuard { role: Role,\\n} impl RoleGuard { fn new(role: Role) -> Self { Self { role } }\\n} impl Guard for RoleGuard { async fn check(&self, ctx: &Context<\'_>) -> Result<()> { if ctx.data_opt::() == Some(&self.role) { Ok(()) } else { Err(\\"Forbidden\\".into()) } }\\n} 用guard属性使用它: # extern crate async_graphql;\\n# use async_graphql::*;\\n# #[derive(Eq, PartialEq, Copy, Clone)]\\n# enum Role { Admin, Guest, }\\n# struct RoleGuard { role: Role, }\\n# impl RoleGuard { fn new(role: Role) -> Self { Self { role } } }\\n# impl Guard for RoleGuard { async fn check(&self, ctx: &Context<\'_>) -> Result<()> { todo!() } }\\n#[derive(SimpleObject)]\\nstruct Query { /// 只允许 Admin 访问 #[graphql(guard = \\"RoleGuard::new(Role::Admin)\\")] value1: i32, /// 允许 Admin 或者 Guest 访问 #[graphql(guard = \\"RoleGuard::new(Role::Admin).or(RoleGuard::new(Role::Guest))\\")] value2: i32,\\n}","breadcrumbs":"实用功能 » 字段守卫 » 字段守卫 (Field Guard)","id":"45","title":"字段守卫 (Field Guard)"},"46":{"body":"有时候守卫需要从字段参数中获取值,你需要像下面这样在创建守卫时传递该参数值: # extern crate async_graphql;\\n# use async_graphql::*;\\nstruct EqGuard { expect: i32, actual: i32,\\n} impl EqGuard { fn new(expect: i32, actual: i32) -> Self { Self { expect, actual } }\\n} impl Guard for EqGuard { async fn check(&self, _ctx: &Context<\'_>) -> Result<()> { if self.expect != self.actual { Err(\\"Forbidden\\".into()) } else { Ok(()) } }\\n} struct Query; #[Object]\\nimpl Query { #[graphql(guard = \\"EqGuard::new(100, value)\\")] async fn get(&self, value: i32) -> i32 { value }\\n}","breadcrumbs":"实用功能 » 字段守卫 » 从参数中获取值","id":"46","title":"从参数中获取值"},"47":{"body":"Async-graphql内置了一些常用的校验器,你可以在对象字段的参数或者InputObject的字段上使用它们。 maximum=N 指定数字不能大于N minimum=N 指定数字不能小于N multiple_of=N 指定数字必须是N的倍数 max_items=N 指定列表的长度不能大于N min_items=N 指定列表的长度不能小于N max_length=N 字符串的长度不能大于N min_length=N 字符串的长度不能小于N chars_max_length=N 字符串中 unicode 字符的的数量不能小于N chars_min_length=N 字符串中 unicode 字符的的数量不能大于N email 有效的 email url 有效的 url ip 有效的 ip 地址 regex=RE 匹配正则表达式 # extern crate async_graphql;\\nuse async_graphql::*; struct Query; #[Object]\\nimpl Query { /// name 参数的长度必须大于等于 5,小于等于 10 async fn input(&self, #[graphql(validator(min_length = 5, max_length = 10))] name: String) -> Result {\\n# todo!() }\\n}","breadcrumbs":"实用功能 » 输入值校验器 » 输入值校验器","id":"47","title":"输入值校验器"},"48":{"body":"你可以打开list属性表示校验器作用于一个列表内的所有成员: # extern crate async_graphql;\\nuse async_graphql::*; struct Query; #[Object]\\nimpl Query { async fn input(&self, #[graphql(validator(list, max_length = 10))] names: Vec) -> Result {\\n# todo!() }\\n}","breadcrumbs":"实用功能 » 输入值校验器 » 校验列表成员","id":"48","title":"校验列表成员"},"49":{"body":"# extern crate async_graphql;\\n# use async_graphql::*;\\nstruct MyValidator { expect: i32,\\n} impl MyValidator { pub fn new(n: i32) -> Self { MyValidator { expect: n } }\\n} impl CustomValidator for MyValidator { fn check(&self, value: &i32) -> Result<(), InputValueError> { if *value == self.expect { Ok(()) } else { Err(InputValueError::custom(format!(\\"expect 100, actual {}\\", value))) } }\\n} struct Query; #[Object]\\nimpl Query { /// n 的值必须等于 100 async fn value( &self, #[graphql(validator(custom = \\"MyValidator::new(100)\\"))] n: i32, ) -> i32 { n }\\n}","breadcrumbs":"实用功能 » 输入值校验器 » 自定义校验器","id":"49","title":"自定义校验器"},"5":{"body":"在我们这个例子里面,只有 Query,没有 Mutation 和 Subscription,所以我们用EmptyMutation和EmptySubscription来创建 Schema,然后调用Schema::execute来执行查询。 # extern crate async_graphql;\\n# use async_graphql::*;\\n#\\n# struct Query;\\n# #[Object]\\n# impl Query {\\n# async fn version(&self) -> &str { \\"1.0\\" } # }\\n# async fn other() {\\nlet schema = Schema::new(Query, EmptyMutation, EmptySubscription);\\nlet res = schema.execute(\\"{ add(a: 10, b: 20) }\\").await;\\n# }","breadcrumbs":"快速开始 » 执行查询","id":"5","title":"执行查询"},"50":{"body":"生产环境下通常依赖缓存来提高性能。 一个 GraphQL 查询会调用多个 Resolver 函数,每个 Resolver 函数都能够具有不同的缓存定义。有的可能缓存几秒钟,有的可能缓存几个小时,有的可能所有用户都相同,有的可能每个会话都不同。 Async-graphql提供一种机制允许定义结果的缓存时间和作用域。 你可以在 对象 上定义缓存参数,也可以在 字段 上定义,下面的例子展示了缓存控制参数的两种用法。 你可以用max_age参数来控制缓存时长(单位是秒),也可以用public和private来控制缓存的作用域,当你不指定时,作用域默认是public。 Async-graphql查询时会合并所有缓存控制指令的结果,max_age取最小值。如果任何对象或者字段的作用域为private,则其结果的作用域为private,否则为public。 我们可以从查询结果QueryResponse中获取缓存控制合并结果,并且调用CacheControl::value来获取对应的 HTTP 头。 # extern crate async_graphql;\\n# use async_graphql::*;\\n# struct Query;\\n#[Object(cache_control(max_age = 60))]\\nimpl Query { #[graphql(cache_control(max_age = 30))] async fn value1(&self) -> i32 { 1 } #[graphql(cache_control(private))] async fn value2(&self) -> i32 { 2 } async fn value3(&self) -> i32 { 3 }\\n} 下面是不同的查询对应不同缓存控制结果: # max_age=30\\n{ value1 } # max_age=30, private\\n{ value1 value2 } # max_age=60\\n{ value3 }","breadcrumbs":"实用功能 » 查询缓存控制 » 查询缓存控制","id":"50","title":"查询缓存控制"},"51":{"body":"Relay 定义了一套游标连接规范,以提供一致性的分页查询方式,具体的规范文档请参考 GraphQL Cursor Connections Specification 。 在Async-graphql中定义一个游标连接非常简单,你只需要调用 connection::query 函数,并在闭包中查询数据。 下面是一个简单的获取连续整数的数据源: # extern crate async_graphql;\\nuse async_graphql::*;\\nuse async_graphql::types::connection::*; struct Query; #[Object]\\nimpl Query { async fn numbers(&self, after: Option, before: Option, first: Option, last: Option, ) -> Result> { query(after, before, first, last, |after, before, first, last| async move { let mut start = after.map(|after| after + 1).unwrap_or(0); let mut end = before.unwrap_or(10000); if let Some(first) = first { end = (start + first).min(end); } if let Some(last) = last { start = if last > end - start { end } else { end - last }; } let mut connection = Connection::new(start > 0, end < 10000); connection.edges.extend( (start..end).into_iter().map(|n| Edge::with_additional_fields(n, n as i32, EmptyFields) )); Ok::<_, async_graphql::Error>(connection) }).await }\\n}","breadcrumbs":"实用功能 » 游标连接 » 游标连接 (Cursor Connections)","id":"51","title":"游标连接 (Cursor Connections)"},"52":{"body":"# 错误扩展 引用 graphql-spec GraphQL 服务可以通过扩展提供错误的附加条目。 该条目(如果设置)必须是一个映射作为其值,用于附加错误的其它信息。 我建议您查看此 错误扩展示例 作为快速入门。","breadcrumbs":"实用功能 » 错误扩展 » 示例","id":"52","title":"示例"},"53":{"body":"在Async-graphql中,所有面向用户的错误都强制转换为Error类型,默认情况下会提供 由std:::fmt::Display暴露的错误消息。但是,Error实际上提供了一个额外的可以扩展错误的信息。 Resolver 函数类似这样: # extern crate async_graphql;\\n# use async_graphql::*;\\n# struct Query;\\n# #[Object]\\n# impl Query {\\nasync fn parse_with_extensions(&self) -> Result { Err(Error::new(\\"MyMessage\\").extend_with(|_, e| e.set(\\"details\\", \\"CAN_NOT_FETCH\\")))\\n}\\n# } 然后可以返回如下响应: { \\"errors\\": [ { \\"message\\": \\"MyMessage\\", \\"locations\\": [ ... ], \\"path\\": [ ... ], \\"extensions\\": { \\"details\\": \\"CAN_NOT_FETCH\\", } } ]\\n}","breadcrumbs":"实用功能 » 错误扩展 » 一般概念","id":"53","title":"一般概念"},"54":{"body":"手动构造新的Error很麻烦。这就是为什么Async-graphql提供 两个方便特性,可将您的错误转换为适当的Error扩展。 扩展任何错误的最简单方法是对错误调用extend_with。 这将把任何错误转换为具有给定扩展信息的Error。 # extern crate async_graphql;\\n# use async_graphql::*;\\n# struct Query;\\nuse std::num::ParseIntError;\\n# #[Object]\\n# impl Query {\\nasync fn parse_with_extensions(&self) -> Result { Ok(\\"234a\\" .parse() .map_err(|err: ParseIntError| err.extend_with(|_err, e| e.set(\\"code\\", 404)))?)\\n}\\n# }","breadcrumbs":"实用功能 » 错误扩展 » ErrorExtensions","id":"54","title":"ErrorExtensions"},"55":{"body":"你也可以给自己的错误类型实现ErrorExtensions: # extern crate async_graphql;\\n# extern crate thiserror;\\n# use async_graphql::*;\\n#[derive(Debug, thiserror::Error)]\\npub enum MyError { #[error(\\"Could not find resource\\")] NotFound, #[error(\\"ServerError\\")] ServerError(String), #[error(\\"No Extensions\\")] ErrorWithoutExtensions,\\n} impl ErrorExtensions for MyError { // lets define our base extensions fn extend(&self) -> Error { Error::new(format!(\\"{}\\", self)).extend_with(|err, e| match self { MyError::NotFound => e.set(\\"code\\", \\"NOT_FOUND\\"), MyError::ServerError(reason) => e.set(\\"reason\\", reason.clone()), MyError::ErrorWithoutExtensions => {} }) }\\n} 您只需要对错误调用extend即可将错误与其提供的扩展信息一起传递,或者通过extend_with进一步扩展错误信息。 # extern crate async_graphql;\\n# extern crate thiserror;\\n# use async_graphql::*;\\n# #[derive(Debug, thiserror::Error)]\\n# pub enum MyError {\\n# #[error(\\"Could not find resource\\")]\\n# NotFound,\\n# # #[error(\\"ServerError\\")]\\n# ServerError(String),\\n# # #[error(\\"No Extensions\\")]\\n# ErrorWithoutExtensions,\\n# }\\n# struct Query;\\n# #[Object]\\n# impl Query {\\nasync fn parse_with_extensions_result(&self) -> Result { // Err(MyError::NotFound.extend()) // OR Err(MyError::NotFound.extend_with(|_, e| e.set(\\"on_the_fly\\", \\"some_more_info\\")))\\n}\\n# } { \\"errors\\": [ { \\"message\\": \\"NotFound\\", \\"locations\\": [ ... ], \\"path\\": [ ... ], \\"extensions\\": { \\"code\\": \\"NOT_FOUND\\", \\"on_the_fly\\": \\"some_more_info\\" } } ]\\n}","breadcrumbs":"实用功能 » 错误扩展 » 为自定义错误实现 ErrorExtensions","id":"55","title":"为自定义错误实现 ErrorExtensions"},"56":{"body":"这个特质使您可以直接在结果上调用extend_err。因此上面的代码不再那么冗长。 # // @todo figure out why this example does not compile!\\n# extern crate async_graphql;\\nuse async_graphql::*;\\n# struct Query;\\n# #[Object]\\n# impl Query {\\nasync fn parse_with_extensions(&self) -> Result { Ok(\\"234a\\" .parse() .extend_err(|_, e| e.set(\\"code\\", 404))?)\\n}\\n# }","breadcrumbs":"实用功能 » 错误扩展 » ResultExt","id":"56","title":"ResultExt"},"57":{"body":"由于对所有&E where E: std::fmt::Display实现了ErrorExtensions和ResultsExt,我们可以将扩展链接在一起。 # extern crate async_graphql;\\nuse async_graphql::*;\\n# struct Query;\\n# #[Object]\\n# impl Query {\\nasync fn parse_with_extensions(&self) -> Result { match \\"234a\\".parse() { Ok(n) => Ok(n), Err(e) => Err(e .extend_with(|_, e| e.set(\\"code\\", 404)) .extend_with(|_, e| e.set(\\"details\\", \\"some more info..\\")) // keys may also overwrite previous keys... .extend_with(|_, e| e.set(\\"code\\", 500))), }\\n}\\n# } 响应: { \\"errors\\": [ { \\"message\\": \\"MyMessage\\", \\"locations\\": [ ... ], \\"path\\": [ ... ], \\"extensions\\": { \\"details\\": \\"some more info...\\", \\"code\\": 500, } } ]\\n}","breadcrumbs":"实用功能 » 错误扩展 » 链式调用","id":"57","title":"链式调用"},"58":{"body":"Rust 的稳定版本还未提供特化功能,这就是为什么ErrorExtensions为&E where E: std::fmt::Display实现,代替E:std::fmt::Display通过提供一些特化功能。 Autoref-based stable specialization . 缺点是下面的代码 不能 编译: async fn parse_with_extensions_result(&self) -> Result { // the trait `error::ErrorExtensions` is not implemented // for `std::num::ParseIntError` \\"234a\\".parse().extend_err(|_, e| e.set(\\"code\\", 404))\\n} 但这可以通过编译: async fn parse_with_extensions_result(&self) -> Result { // does work because ErrorExtensions is implemented for &ParseIntError \\"234a\\" .parse() .map_err(|ref e: ParseIntError| e.extend_with(|_, e| e.set(\\"code\\", 404)))\\n}","breadcrumbs":"实用功能 » 错误扩展 » 缺陷","id":"58","title":"缺陷"},"59":{"body":"Apollo Tracing提供了查询每个步骤的性能分析结果,它是一个Schema扩展,性能分析结果保存在QueryResponse中。 启用Apollo Tracing扩展需要在创建Schema的时候添加该扩展。 # extern crate async_graphql;\\nuse async_graphql::*;\\nuse async_graphql::extensions::ApolloTracing; # struct Query;\\n# #[Object]\\n# impl Query { async fn version(&self) -> &str { \\"1.0\\" } } let schema = Schema::build(Query, EmptyMutation, EmptySubscription) .extension(ApolloTracing) // 启用 ApolloTracing 扩展 .finish();","breadcrumbs":"实用功能 » Apollo Tracing 支持 » Apollo Tracing 支持","id":"59","title":"Apollo Tracing 支持"},"6":{"body":"let json = serde_json::to_string(&res);","breadcrumbs":"快速开始 » 把查询结果输出为 JSON","id":"6","title":"把查询结果输出为 JSON"},"60":{"body":"⚠️GraphQL 提供了非常灵活的查询方法,但在客户端上滥用复杂的查询可能造成风险,限制查询语句的深度和复杂度可以减轻这种风险。","breadcrumbs":"实用功能 » 查询的深度和复杂度 » 查询的深度和复杂度","id":"60","title":"查询的深度和复杂度"},"61":{"body":"考虑一种允许列出博客文章的架构。每个博客帖子也与其他帖子相关。 type Query { posts(count: Int = 10): [Post!]!\\n} type Post { title: String! text: String! related(count: Int = 10): [Post!]!\\n} 创建一个会引起很大响应的查询不是很困难: { posts(count: 100) { related(count: 100) { related(count: 100) { related(count: 100) { title } } } }\\n} 响应的大小随related字段的每个其他级别呈指数增长。幸运的是,Async-graphql提供了一种防止此类查询的方法。","breadcrumbs":"实用功能 » 查询的深度和复杂度 » 昂贵的查询","id":"61","title":"昂贵的查询"},"62":{"body":"查询的深度是字段嵌套的层数,下面是一个深度为3的查询。 { a { b { c } }\\n} 在创建Schema的时候可以限制深度,如果查询语句超过这个限制,则会出错并且返回Query is nested too deep.消息。 # extern crate async_graphql;\\n# use async_graphql::*;\\n# struct Query;\\n# #[Object]\\n# impl Query { async fn version(&self) -> &str { \\"1.0\\" } }\\nlet schema = Schema::build(Query, EmptyMutation, EmptySubscription) .limit_depth(5) // 限制最大深度为 5 .finish();","breadcrumbs":"实用功能 » 查询的深度和复杂度 » 限制查询的深度","id":"62","title":"限制查询的深度"},"63":{"body":"复杂度是查询语句中字段的数量,每个字段的复杂度默认为1,下面是一个复杂度为6的查询。 { a b c { d { e f } }\\n} 在创建Schema的时候可以限制复杂度,如果查询语句超过这个限制,则会出错并且返回Query is too complex.。 # extern crate async_graphql;\\n# use async_graphql::*;\\n# struct Query;\\n# #[Object]\\n# impl Query { async fn version(&self) -> &str { \\"1.0\\" } }\\nlet schema = Schema::build(Query, EmptyMutation, EmptySubscription) .limit_complexity(5) // 限制复杂度为 5 .finish();","breadcrumbs":"实用功能 » 查询的深度和复杂度 » 限制查询的复杂度","id":"63","title":"限制查询的复杂度"},"64":{"body":"针对非列表类型和列表类型的字段,有两种自定义复杂度的方法。 下面的代码中,value字段的复杂度为5。而values字段的复杂度为count * child_complexity,child_complexity是一个特殊的变量,表示子查询的复杂度, count是字段的参数,这个表达式用于计算values字段的复杂度,并且返回值的类型必须是usize。 # extern crate async_graphql;\\n# use async_graphql::*;\\nstruct Query; #[Object]\\nimpl Query { #[graphql(complexity = 5)] async fn value(&self) -> i32 { todo!() } #[graphql(complexity = \\"count * child_complexity\\")] async fn values(&self, count: usize) -> i32 { todo!() }\\n} 注意:计算复杂度是在验证阶段完成而不是在执行阶段,所以你不用担心超限的查询语句会导致查询只执行一部分。","breadcrumbs":"实用功能 » 查询的深度和复杂度 » 自定义字段的复杂度","id":"64","title":"自定义字段的复杂度"},"65":{"body":"默认情况下,所有类型,字段在内省中都是可见的。但可能你希望根据不同的用户来隐藏一些信息,避免引起不必要的误会。你可以在类型或者字段上添加visible属性来做到。 # extern crate async_graphql;\\nuse async_graphql::*; #[derive(SimpleObject)]\\nstruct MyObj { // 这个字段将在内省中可见 a: i32, // 这个字段在内省中总是隐藏 #[graphql(visible = false)] b: i32, // 这个字段调用 `is_admin` 函数,如果函数的返回值为 `true` 则可见 #[graphql(visible = \\"is_admin\\")] c: i32, } #[derive(Enum, Copy, Clone, Eq, PartialEq)]\\nenum MyEnum { // 这个项目将在内省中可见 A, // 这个项目在内省中总是隐藏 #[graphql(visible = false)] B, // 这个项目调用 `is_admin` 函数,如果函数的返回值为 `true` 则可见 #[graphql(visible = \\"is_admin\\")] C,\\n} struct IsAdmin(bool); fn is_admin(ctx: &Context<\'_>) -> bool { ctx.data_unchecked::().0\\n}","breadcrumbs":"实用功能 » 在内省中隐藏内容 » 在内省中隐藏内容","id":"65","title":"在内省中隐藏内容"},"66":{"body":"async-graphql 允许你不修改核心代码就能扩展它功能。","breadcrumbs":"扩展 » 扩展","id":"66","title":"扩展"},"67":{"body":"async-graphql 扩展是通过实现 Extension trait 来定义的。 Extension trait 允许你将自定义代码插入到执行 GraphQL 查询的步骤中。 Extensions 很像来自其他框架的中间件,使用它们时要小心:当你使用扩展时 它对每个 GraphQL 请求生效 。","breadcrumbs":"扩展 » 扩展如何工作 » 如何定义扩展","id":"67","title":"如何定义扩展"},"68":{"body":"让我们了解什么是中间件: async fn middleware(&self, ctx: &ExtensionContext<\'_>, next: NextMiddleware<\'_>) -> MiddlewareResult { // 你的中间件代码 /* * 调用 next.run 函数执行下个中间件的逻辑 */ next.run(ctx).await\\n} 如你所见,middleware 只是在末尾调用 next 函数的函数。但我们也可以在开头使用 next.run 来实现中间件。这就是它变得棘手的地方:根据你放置逻辑的位置以及next.run调用的位置,你的逻辑将不会具有相同的执行顺序。 根据你代码,你需要在 next.run 调用之前或之后处理它。如果你需要更多关于中间件的信息,网上有很多。","breadcrumbs":"扩展 » 扩展如何工作 » 一句话解释什么是中间件","id":"68","title":"一句话解释什么是中间件"},"69":{"body":"查询的每个阶段都有回调,你将能够基于这些回调创建扩展。","breadcrumbs":"扩展 » 扩展如何工作 » 查询的处理","id":"69","title":"查询的处理"},"7":{"body":"请参考 https://github.com/async-graphql/examples。","breadcrumbs":"快速开始 » 和 Web Server 的集成","id":"7","title":"和 Web Server 的集成"},"70":{"body":"首先,当我们收到一个请求时,如果它不是订阅,第一个被调用的函数将是 request,它在传入请求时调用,并输出结果给客户端。 Default implementation for request: # extern crate async_graphql;\\n# use async_graphql::*;\\n# use async_graphql::extensions::*;\\n# struct MyMiddleware;\\n# #[async_trait::async_trait]\\n# impl Extension for MyMiddleware {\\nasync fn request(&self, ctx: &ExtensionContext<\'_>, next: NextRequest<\'_>) -> Response { next.run(ctx).await\\n}\\n# } 根据你放置逻辑代码的位置,它将在正在查询执行的开头或结尾执行。 # extern crate async_graphql;\\n# use async_graphql::*;\\n# use async_graphql::extensions::*;\\n# struct MyMiddleware;\\n# #[async_trait::async_trait]\\n# impl Extension for MyMiddleware {\\nasync fn request(&self, ctx: &ExtensionContext<\'_>, next: NextRequest<\'_>) -> Response { // 此处的代码将在执行 prepare_request 之前运行。 let result = next.run(ctx).await; // 此处的代码将在把结果发送给客户端之前执行 result\\n}\\n# }","breadcrumbs":"扩展 » 扩展如何工作 » 请求","id":"70","title":"请求"},"71":{"body":"在 request 之后,将调用prepare_request,你可以在此处对请求做一些转换。 # extern crate async_graphql;\\n# use async_graphql::*;\\n# use async_graphql::*;\\n# use async_graphql::extensions::*;\\n# struct MyMiddleware;\\n# #[async_trait::async_trait]\\n# impl Extension for MyMiddleware {\\nasync fn prepare_request( &self, ctx: &ExtensionContext<\'_>, request: Request, next: NextPrepareRequest<\'_>,\\n) -> ServerResult { // 此处的代码在 prepare_request 之前执行 let result = next.run(ctx, request).await; // 此处的代码在 prepare_request 之后执行 result\\n}\\n# }","breadcrumbs":"扩展 » 扩展如何工作 » 准备查询","id":"71","title":"准备查询"},"72":{"body":"parse_query 将解析查询语句并生成 GraphQL ExecutableDocument,并且检查查询是否遵循 GraphQL 规范。通常,async-graphql 遵循最后一个稳定的规范(October2021)。 # extern crate async_graphql;\\n# use async_graphql::*;\\n# use async_graphql::extensions::*;\\n# use async_graphql::parser::types::ExecutableDocument;\\n# struct MyMiddleware;\\n# #[async_trait::async_trait]\\n# impl Extension for MyMiddleware {\\n/// Called at parse query.\\nasync fn parse_query( &self, ctx: &ExtensionContext<\'_>, // The raw query query: &str, // The variables variables: &Variables, next: NextParseQuery<\'_>,\\n) -> ServerResult { next.run(ctx, query, variables).await\\n}\\n# }","breadcrumbs":"扩展 » 扩展如何工作 » 解析查询","id":"72","title":"解析查询"},"73":{"body":"validation 步骤将执行查询校验(取决于你指定的 validation_mode),并向客户端提供有关查询无效的原因。 # extern crate async_graphql;\\n# use async_graphql::*;\\n# use async_graphql::extensions::*;\\n# struct MyMiddleware;\\n# #[async_trait::async_trait]\\n# impl Extension for MyMiddleware {\\n/// Called at validation query.\\nasync fn validation( &self, ctx: &ExtensionContext<\'_>, next: NextValidation<\'_>,\\n) -> Result> { next.run(ctx).await\\n}\\n# }","breadcrumbs":"扩展 » 扩展如何工作 » 校验","id":"73","title":"校验"},"74":{"body":"execution 步骤是一个很大的步骤,它将并发执行Query,或者顺序执行Mutation。 # extern crate async_graphql;\\n# use async_graphql::*;\\n# use async_graphql::extensions::*;\\n# struct MyMiddleware;\\n# #[async_trait::async_trait]\\n# impl Extension for MyMiddleware {\\n/// Called at execute query.\\nasync fn execute( &self, ctx: &ExtensionContext<\'_>, operation_name: Option<&str>, next: NextExecute<\'_>,\\n) -> Response { // 此处的代码在执行完整查询之前执行 let result = next.run(ctx, operation_name).await; // 此处的代码在执行完整查询之后执行 result\\n}\\n# }","breadcrumbs":"扩展 » 扩展如何工作 » 执行","id":"74","title":"执行"},"75":{"body":"为每个字段执行resolve. # extern crate async_graphql;\\n# use async_graphql::*;\\n# use async_graphql::extensions::*;\\n# struct MyMiddleware;\\n# #[async_trait::async_trait]\\n# impl Extension for MyMiddleware { /// Called at resolve field.\\nasync fn resolve( &self, ctx: &ExtensionContext<\'_>, info: ResolveInfo<\'_>, next: NextResolve<\'_>,\\n) -> ServerResult> { // resolve 字段之前 let result = next.run(ctx, info).await; // resolve 字段之后 result\\n}\\n# }","breadcrumbs":"扩展 » 扩展如何工作 » resolve","id":"75","title":"resolve"},"76":{"body":"subscribe的行为和request很像,只是专门用于订阅查询。 # extern crate async_graphql;\\n# use async_graphql::*;\\n# use async_graphql::extensions::*;\\n# use futures_util::stream::BoxStream;\\n# struct MyMiddleware;\\n# #[async_trait::async_trait]\\n# impl Extension for MyMiddleware {\\n/// Called at subscribe request.\\nfn subscribe<\'s>( &self, ctx: &ExtensionContext<\'_>, stream: BoxStream<\'s, Response>, next: NextSubscribe<\'_>,\\n) -> BoxStream<\'s, Response> { next.run(ctx, stream)\\n}\\n# }","breadcrumbs":"扩展 » 扩展如何工作 » 订阅","id":"76","title":"订阅"},"77":{"body":"async-graphql 中有很多可用的扩展用于增强你的 GraphQL 服务器。","breadcrumbs":"扩展 » 可用的扩展列表 » 可用的扩展列表","id":"77","title":"可用的扩展列表"},"78":{"body":"Available in the repository Analyzer 扩展将在每个响应的扩展中输出 complexity 和 depth 字段。","breadcrumbs":"扩展 » 可用的扩展列表 » Analyzer","id":"78","title":"Analyzer"},"79":{"body":"Available in the repository 要提高大型查询的性能,你可以启用此扩展,每个查询语句都将与一个唯一 ID 相关联,因此客户端可以直接发送此 ID 查询以减少请求的大小。 这个扩展不会强迫你使用一些缓存策略,你可以选择你想要的缓存策略,你只需要实现 CacheStorage trait: # extern crate async_graphql;\\n# use async_graphql::*;\\n#[async_trait::async_trait]\\npub trait CacheStorage: Send + Sync + Clone + \'static { /// Load the query by `key`. async fn get(&self, key: String) -> Option; /// Save the query by `key`. async fn set(&self, key: String, query: String);\\n} References: Apollo doc - Persisted Queries","breadcrumbs":"扩展 » 可用的扩展列表 » Apollo Persisted Queries","id":"79","title":"Apollo Persisted Queries"},"8":{"body":"Async-graphql包含 GraphQL 类型到 Rust 类型的完整实现,并且非常易于使用。","breadcrumbs":"类型系统 » 类型系统","id":"8","title":"类型系统"},"80":{"body":"Available in the repository Apollo Tracing 扩展用于在响应中包含此查询分析数据。此扩展程序遵循旧的且现已弃用的 Apollo Tracing Spec 。 如果你想支持更新的 Apollo Reporting Protocol,推荐使用 async-graphql Apollo studio extension 。","breadcrumbs":"扩展 » 可用的扩展列表 » Apollo Tracing","id":"80","title":"Apollo Tracing"},"81":{"body":"Available at async-graphql/async_graphql_apollo_studio_extension async-graphql 提供了实现官方 Apollo Specification 的扩展,位于 async-graphql-extension- apollo-tracing 和 crates.io 。","breadcrumbs":"扩展 » 可用的扩展列表 » Apollo Studio","id":"81","title":"Apollo Studio"},"82":{"body":"Available in the repository Logger 是一个简单的扩展,允许你向 async-graphql 添加一些日志记录功能。这也是学习如何创建自己的扩展的一个很好的例子。","breadcrumbs":"扩展 » 可用的扩展列表 » Logger","id":"82","title":"Logger"},"83":{"body":"Available in the repository OpenTelemetry 扩展提供 opentelemetry crate 的集成,以允许你的应用程序从 async-graphql 捕获分布式跟踪和指标。","breadcrumbs":"扩展 » 可用的扩展列表 » OpenTelemetry","id":"83","title":"OpenTelemetry"},"84":{"body":"Available in the repository Tracing 扩展提供 tracing crate 的集成,允许您向 async-graphql 添加一些跟踪功能,有点像Logger 扩展。","breadcrumbs":"扩展 » 可用的扩展列表 » Tracing","id":"84","title":"Tracing"},"85":{"body":"Async-graphql提供了对一些常用 Web Server 的集成支持。 Poem async-graphql-poem Actix-web async-graphql-actix-web Warp async-graphql-warp Axum async-graphql-axum Rocket async-graphql-rocket 即使你目前使用的 Web Server 不在上面的列表中,自己实现类似的功能也相当的简单。","breadcrumbs":"集成到 WebServer » 集成到 WebServer","id":"85","title":"集成到 WebServer"},"86":{"body":"","breadcrumbs":"集成到 WebServer » Poem » Poem","id":"86","title":"Poem"},"87":{"body":"# extern crate async_graphql_poem;\\n# extern crate async_graphql;\\n# extern crate poem;\\n# use async_graphql::*;\\n# #[derive(Default, SimpleObject)]\\n# struct Query { a: i32 }\\n# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();\\nuse poem::Route;\\nuse async_graphql_poem::GraphQL; let app = Route::new() .at(\\"/ws\\", GraphQL::new(schema));","breadcrumbs":"集成到 WebServer » Poem » 请求例子","id":"87","title":"请求例子"},"88":{"body":"# extern crate async_graphql_poem;\\n# extern crate async_graphql;\\n# extern crate poem;\\n# use async_graphql::*;\\n# #[derive(Default, SimpleObject)]\\n# struct Query { a: i32 }\\n# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();\\nuse poem::{get, Route};\\nuse async_graphql_poem::GraphQLSubscription; let app = Route::new() .at(\\"/ws\\", get(GraphQLSubscription::new(schema)));","breadcrumbs":"集成到 WebServer » Poem » 订阅例子","id":"88","title":"订阅例子"},"89":{"body":"https://github.com/async-graphql/examples/tree/master/poem","breadcrumbs":"集成到 WebServer » Poem » 更多例子","id":"89","title":"更多例子"},"9":{"body":"简单对象是把 Rust 结构的所有字段都直接映射到 GraphQL 对象,不支持定义单独的 Resolver 函数。 下面的例子定义了一个名称为 MyObject 的对象,包含字段a和b,c由于标记为#[graphql(skip)],所以不会映射到 GraphQL。 # extern crate async_graphql;\\nuse async_graphql::*; #[derive(SimpleObject)]\\nstruct MyObject { /// Value a a: i32, /// Value b b: i32, #[graphql(skip)] c: i32,\\n}","breadcrumbs":"类型系统 » 简单对象 (SimpleObject) » 简单对象 (SimpleObject)","id":"9","title":"简单对象 (SimpleObject)"},"90":{"body":"Async-graphql-warp提供了两个Filter,graphql和graphql_subscription。 graphql用于执行Query和Mutation请求,它提取 GraphQL 请求,然后输出一个包含async_graphql::Schema和async_graphql::Request元组,你可以在之后组合其它 Filter,或者直接调用Schema::execute执行查询。 graphql_subscription用于实现基于 Web Socket 的订阅,它输出warp::Reply。","breadcrumbs":"集成到 WebServer » Warp » Warp","id":"90","title":"Warp"},"91":{"body":"# extern crate async_graphql_warp;\\n# extern crate async_graphql;\\n# extern crate warp;\\n# use async_graphql::*;\\n# use std::convert::Infallible;\\n# use warp::Filter;\\n# struct QueryRoot;\\n# #[Object]\\n# impl QueryRoot { async fn version(&self) -> &str { \\"1.0\\" } }\\n# async fn other() {\\ntype MySchema = Schema; let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription);\\nlet filter = async_graphql_warp::graphql(schema).and_then(|(schema, request): (MySchema, async_graphql::Request)| async move { // 执行查询 let resp = schema.execute(request).await; // 返回结果 Ok::<_, Infallible>(async_graphql_warp::GraphQLResponse::from(resp))\\n});\\nwarp::serve(filter).run(([0, 0, 0, 0], 8000)).await;\\n# }","breadcrumbs":"集成到 WebServer » Warp » 请求例子","id":"91","title":"请求例子"},"92":{"body":"# extern crate async_graphql_warp;\\n# extern crate async_graphql;\\n# extern crate warp;\\n# use async_graphql::*;\\n# use futures_util::stream::{Stream, StreamExt};\\n# use std::convert::Infallible;\\n# use warp::Filter;\\n# struct SubscriptionRoot;\\n# #[Subscription]\\n# impl SubscriptionRoot {\\n# async fn tick(&self) -> impl Stream {\\n# futures_util::stream::iter(0..10)\\n# }\\n# }\\n# struct QueryRoot;\\n# #[Object]\\n# impl QueryRoot { async fn version(&self) -> &str { \\"1.0\\" } }\\n# async fn other() {\\nlet schema = Schema::new(QueryRoot, EmptyMutation, SubscriptionRoot);\\nlet filter = async_graphql_warp::graphql_subscription(schema);\\nwarp::serve(filter).run(([0, 0, 0, 0], 8000)).await;\\n# }","breadcrumbs":"集成到 WebServer » Warp » 订阅例子","id":"92","title":"订阅例子"},"93":{"body":"https://github.com/async-graphql/examples/tree/master/warp","breadcrumbs":"集成到 WebServer » Warp » 更多例子","id":"93","title":"更多例子"},"94":{"body":"Async-graphql-actix-web提供了GraphQLRequest提取器用于提取GraphQL请求,和GraphQLResponse用于输出GraphQL响应。 GraphQLSubscription用于创建一个支持 Web Socket 订阅的 Actor。","breadcrumbs":"集成到 WebServer » Actix-web » Actix-web","id":"94","title":"Actix-web"},"95":{"body":"你需要把 Schema 传入actix_web::App作为全局数据。 # extern crate async_graphql_actix_web;\\n# extern crate async_graphql;\\n# extern crate actix_web;\\n# use async_graphql::*;\\n# #[derive(Default,SimpleObject)]\\n# struct Query { a: i32 }\\n# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();\\nuse actix_web::{web, HttpRequest, HttpResponse};\\nuse async_graphql_actix_web::{GraphQLRequest, GraphQLResponse};\\nasync fn index( schema: web::Data>, request: GraphQLRequest,\\n) -> web::Json { web::Json(schema.execute(request.into_inner()).await.into())\\n}","breadcrumbs":"集成到 WebServer » Actix-web » 请求例子","id":"95","title":"请求例子"},"96":{"body":"# extern crate async_graphql_actix_web;\\n# extern crate async_graphql;\\n# extern crate actix_web;\\n# use async_graphql::*;\\n# #[derive(Default,SimpleObject)]\\n# struct Query { a: i32 }\\n# let schema = Schema::build(Query::default(), EmptyMutation, EmptySubscription).finish();\\nuse actix_web::{web, HttpRequest, HttpResponse};\\nuse async_graphql_actix_web::GraphQLSubscription;\\nasync fn index_ws( schema: web::Data>, req: HttpRequest, payload: web::Payload,\\n) -> actix_web::Result { GraphQLSubscription::new(Schema::clone(&*schema)).start(&req, payload)\\n}","breadcrumbs":"集成到 WebServer » Actix-web » 订阅例子","id":"96","title":"订阅例子"},"97":{"body":"https://github.com/async-graphql/examples/tree/master/actix-web","breadcrumbs":"集成到 WebServer » Actix-web » 更多例子","id":"97","title":"更多例子"},"98":{"body":"","breadcrumbs":"高级主题 » 高级主题","id":"98","title":"高级主题"},"99":{"body":"Async-graphql已经内置了绝大部分常用的标量类型,同时你也能自定义标量。 实现Async-graphql::Scalar即可自定义一个标量,你只需要实现一个解析函数和输出函数。 下面的例子定义一个 64 位整数标量,但它的输入输出都是字符串。 (Async-graphql已经内置了对 64 位整数的支持,正是采用字符串作为输入输出) # extern crate async_graphql;\\nuse async_graphql::*; struct StringNumber(i64); #[Scalar]\\nimpl ScalarType for StringNumber { fn parse(value: Value) -> InputValueResult { if let Value::String(value) = &value { // 解析整数 Ok(value.parse().map(StringNumber)?) } else { // 类型不匹配 Err(InputValueError::expected_type(value)) } } fn to_value(&self) -> Value { Value::String(self.0.to_string()) }\\n}","breadcrumbs":"高级主题 » 自定义标量 » 自定义标量","id":"99","title":"自定义标量"}},"length":109,"save":true},"fields":["title","body","breadcrumbs"],"index":{"body":{"root":{"0":{"df":5,"docs":{"36":{"tf":1.0},"43":{"tf":1.0},"51":{"tf":1.0},"91":{"tf":1.7320508075688772},"92":{"tf":1.7320508075688772}}},"1":{")":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"0":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},".":{"0":{"df":8,"docs":{"105":{"tf":1.0},"30":{"tf":1.0},"5":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0}}},"df":0,"docs":{}},"0":{"0":{"0":{"0":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}},"df":2,"docs":{"49":{"tf":1.4142135623730951},"61":{"tf":2.0}}},"df":7,"docs":{"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.0},"47":{"tf":1.4142135623730951},"48":{"tf":1.0},"5":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"2":{"3":{"4":{"df":1,"docs":{"18":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"7":{"7":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"8":{"0":{"df":1,"docs":{"27":{"tf":1.0}}},"3":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":4,"docs":{"102":{"tf":4.242640687119285},"103":{"tf":1.0},"43":{"tf":1.4142135623730951},"50":{"tf":1.0}}},"2":{"0":{"df":1,"docs":{"5":{"tf":1.0}}},"3":{"4":{"a":{"\\"":{".":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"57":{"tf":1.0}},"e":{"(":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"103":{"tf":1.0},"50":{"tf":1.0}}},"3":{"0":{"df":2,"docs":{"36":{"tf":1.4142135623730951},"50":{"tf":1.0}}},"df":3,"docs":{"103":{"tf":1.0},"50":{"tf":1.0},"62":{"tf":1.0}}},"4":{".":{"0":{"df":1,"docs":{"3":{"tf":2.0}}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"20":{"tf":1.0}}},"4":{"df":4,"docs":{"54":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"103":{"tf":1.0}}},"5":{"0":{"0":{"df":1,"docs":{"57":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":6,"docs":{"37":{"tf":1.0},"38":{"tf":1.0},"47":{"tf":1.4142135623730951},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0}}},"6":{"0":{"df":1,"docs":{"50":{"tf":1.0}}},"4":{"df":1,"docs":{"99":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"8":{"0":{"0":{"0":{")":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"91":{"tf":1.0},"92":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":2,"docs":{"105":{"tf":1.4142135623730951},"46":{"tf":1.0}}}}},"df":2,"docs":{"105":{"tf":1.0},"43":{"tf":1.0}}},"a":{"(":{"a":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"b":{":":{":":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":1,"docs":{"95":{"tf":1.0}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"96":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}},"{":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"b":{"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.0}}},"df":0,"docs":{}}}},"df":3,"docs":{"3":{"tf":1.4142135623730951},"85":{"tf":1.4142135623730951},"94":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"94":{"tf":1.0}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"46":{"tf":1.7320508075688772},"49":{"tf":1.0}}}},"df":0,"docs":{}}}},"d":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"4":{"tf":1.0}}}}}}},"a":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"45":{"tf":2.0}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"|":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"df":0,"docs":{},"z":{"df":1,"docs":{"78":{"tf":1.4142135623730951}}}}}},"d":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{":":{":":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"(":{"\\"":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"\\"":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":5,"docs":{"106":{"tf":1.4142135623730951},"59":{"tf":1.7320508075688772},"79":{"tf":1.4142135623730951},"80":{"tf":2.23606797749979},"81":{"tf":1.7320508075688772}},"t":{"df":0,"docs":{},"r":{"a":{"c":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"p":{"df":2,"docs":{"87":{"tf":1.0},"88":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"18":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"r":{"c":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"x":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"a":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"29":{"tf":1.0}}},"df":0,"docs":{}},"g":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"29":{"tf":1.0},"37":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"df":1,"docs":{"24":{"tf":1.0}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"/":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"103":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{">":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":7,"docs":{"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"c":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"43":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"43":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"72":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"17":{"tf":1.0},"91":{"tf":1.0}}}}}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"_":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"b":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"96":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"{":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"95":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"87":{"tf":1.0}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"88":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":2,"docs":{"87":{"tf":1.0},"88":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{")":{".":{"a":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"|":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"91":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"92":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":3,"docs":{"17":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":68,"docs":{"10":{"tf":2.0},"100":{"tf":1.4142135623730951},"103":{"tf":1.0},"105":{"tf":2.0},"107":{"tf":1.4142135623730951},"108":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"13":{"tf":1.4142135623730951},"15":{"tf":1.4142135623730951},"16":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"26":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"28":{"tf":2.0},"29":{"tf":1.4142135623730951},"30":{"tf":2.0},"31":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"34":{"tf":2.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"4":{"tf":1.4142135623730951},"41":{"tf":1.4142135623730951},"42":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"45":{"tf":2.0},"46":{"tf":1.4142135623730951},"47":{"tf":1.4142135623730951},"48":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"50":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"53":{"tf":1.4142135623730951},"54":{"tf":1.4142135623730951},"55":{"tf":2.0},"56":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"59":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"64":{"tf":1.4142135623730951},"65":{"tf":1.4142135623730951},"70":{"tf":2.0},"71":{"tf":1.7320508075688772},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"74":{"tf":1.4142135623730951},"75":{"tf":1.4142135623730951},"76":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951},"87":{"tf":1.4142135623730951},"88":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951},"91":{"tf":1.4142135623730951},"92":{"tf":1.4142135623730951},"95":{"tf":1.4142135623730951},"96":{"tf":1.4142135623730951},"99":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"d":{":":{":":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"103":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{":":{":":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":9,"docs":{"105":{"tf":1.4142135623730951},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0},"79":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":75,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.0},"102":{"tf":1.0},"103":{"tf":1.4142135623730951},"104":{"tf":1.4142135623730951},"105":{"tf":2.23606797749979},"106":{"tf":1.0},"107":{"tf":1.7320508075688772},"108":{"tf":1.0},"11":{"tf":1.0},"13":{"tf":1.4142135623730951},"15":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":2.0},"23":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"27":{"tf":1.0},"29":{"tf":2.8284271247461903},"3":{"tf":2.0},"30":{"tf":1.7320508075688772},"31":{"tf":2.23606797749979},"33":{"tf":1.0},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772},"39":{"tf":1.0},"4":{"tf":1.4142135623730951},"41":{"tf":1.0},"42":{"tf":1.4142135623730951},"43":{"tf":1.0},"45":{"tf":1.4142135623730951},"46":{"tf":1.4142135623730951},"47":{"tf":1.4142135623730951},"48":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.4142135623730951},"50":{"tf":2.23606797749979},"51":{"tf":1.7320508075688772},"53":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.4142135623730951},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"66":{"tf":1.0},"67":{"tf":1.0},"68":{"tf":1.0},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.4142135623730951},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"77":{"tf":1.0},"79":{"tf":1.4142135623730951},"8":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.7320508075688772},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"85":{"tf":2.449489742783178},"90":{"tf":1.0},"91":{"tf":1.7320508075688772},"92":{"tf":1.7320508075688772},"94":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}},"t":{"(":{"\\"":{"/":{"df":0,"docs":{},"w":{"df":2,"docs":{"87":{"tf":1.0},"88":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"17":{"tf":1.7320508075688772}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"58":{"tf":1.0}}}}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":7,"docs":{"78":{"tf":1.0},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0}}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"102":{"tf":1.0},"17":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0}}}}},"df":0,"docs":{}},"x":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"85":{"tf":1.4142135623730951}}}}}},"b":{"(":{"b":{"df":1,"docs":{"32":{"tf":1.0}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":2,"docs":{"55":{"tf":1.0},"58":{"tf":1.0}}}}},"df":15,"docs":{"10":{"tf":1.0},"100":{"tf":1.0},"108":{"tf":1.4142135623730951},"11":{"tf":1.0},"12":{"tf":1.0},"19":{"tf":1.0},"28":{"tf":2.0},"32":{"tf":1.0},"34":{"tf":1.0},"4":{"tf":1.7320508075688772},"5":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"51":{"tf":1.7320508075688772}},"e":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"1":{"0":{"0":{"0":{"0":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"n":{"d":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"102":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"65":{"tf":1.0}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"<":{"\'":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":1,"docs":{"15":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"x":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"<":{"\'":{"df":1,"docs":{"76":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"c":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"11":{"tf":1.0}}}}}}},"c":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"a":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"79":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":5,"docs":{"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0}}}},"n":{"_":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"53":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":7,"docs":{"19":{"tf":1.4142135623730951},"28":{"tf":2.0},"32":{"tf":2.0},"62":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.4142135623730951},"9":{"tf":1.0}},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"x":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":3,"docs":{"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"49":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"l":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"64":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"24":{"tf":1.0}}}}}}},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":2,"docs":{"29":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}},"e":{"\\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"(":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":6,"docs":{"26":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"65":{"tf":1.0},"79":{"tf":1.0}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":2,"docs":{"55":{"tf":1.0},"57":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"56":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":3,"docs":{"11":{"tf":1.0},"63":{"tf":1.0},"78":{"tf":1.0}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"11":{"tf":1.4142135623730951}},"和":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"45":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"t":{"(":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"105":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"105":{"tf":2.449489742783178}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.7320508075688772}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"51":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"<":{"\'":{"_":{"df":9,"docs":{"102":{"tf":1.0},"103":{"tf":1.0},"105":{"tf":1.4142135623730951},"13":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"65":{"tf":1.0}}},"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":1,"docs":{"15":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"14":{"tf":1.0},"15":{"tf":1.0},"18":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"28":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"33":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"i":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"65":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"64":{"tf":1.4142135623730951}}}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":69,"docs":{"10":{"tf":1.4142135623730951},"100":{"tf":1.4142135623730951},"105":{"tf":1.4142135623730951},"107":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"15":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.7320508075688772},"18":{"tf":1.4142135623730951},"19":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.4142135623730951},"26":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.4142135623730951},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.7320508075688772},"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":2.0},"56":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0},"79":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"87":{"tf":1.7320508075688772},"88":{"tf":1.7320508075688772},"9":{"tf":1.0},"91":{"tf":1.7320508075688772},"92":{"tf":1.7320508075688772},"95":{"tf":1.7320508075688772},"96":{"tf":1.7320508075688772},"99":{"tf":1.0}},"s":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":1,"docs":{"81":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"x":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{"\\"":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{":":{":":{"<":{"d":{"b":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{">":{"(":{")":{"?":{".":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"15":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"15":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{":":{":":{"<":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"45":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"d":{":":{":":{"<":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"103":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"a":{"d":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{">":{"(":{")":{".":{"0":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"<":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"df":1,"docs":{"102":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{"\\"":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"_":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"_":{"a":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"(":{")":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"\\"":{"a":{"\\"":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"\\"":{")":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"\\"":{"c":{"\\"":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":15,"docs":{"102":{"tf":1.0},"103":{"tf":1.4142135623730951},"13":{"tf":1.0},"15":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"45":{"tf":1.4142135623730951},"68":{"tf":1.0},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"51":{"tf":1.4142135623730951}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"105":{"tf":2.23606797749979}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"<":{"df":0,"docs":{},"i":{"3":{"2":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"d":{"(":{"d":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"t":{"a":{"(":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"103":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"b":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"16":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"s":{"3":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"16":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":2,"docs":{"13":{"tf":1.0},"17":{"tf":1.0}},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"103":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"c":{"3":{"3":{"3":{"9":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"c":{"2":{"8":{"2":{"2":{"(":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"24":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{"3":{"3":{"9":{"(":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{".":{"0":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":1,"docs":{"24":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"b":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"16":{"tf":1.0}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":2.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}}}}},"df":3,"docs":{"19":{"tf":1.0},"32":{"tf":2.0},"63":{"tf":1.0}},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"62":{"tf":1.0}}}},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"df":4,"docs":{"22":{"tf":1.0},"23":{"tf":1.0},"37":{"tf":1.4142135623730951},"70":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"55":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"3":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"78":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"32":{"tf":2.449489742783178}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"32":{"tf":1.7320508075688772}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{",":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"16":{"tf":1.0},"23":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":5,"docs":{"17":{"tf":1.0},"22":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"87":{"tf":1.0},"88":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"65":{"tf":1.0}}}}},"q":{"df":1,"docs":{"45":{"tf":1.4142135623730951}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"108":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":2.6457513110645907},"38":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"a":{"c":{"df":3,"docs":{"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"23":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"100":{"tf":1.0},"26":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":14,"docs":{"10":{"tf":2.6457513110645907},"107":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"19":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"26":{"tf":1.0},"30":{"tf":1.4142135623730951},"33":{"tf":1.0},"41":{"tf":1.0},"45":{"tf":1.0},"65":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"100":{"tf":1.7320508075688772},"26":{"tf":1.7320508075688772}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":3,"docs":{"19":{"tf":2.0},"53":{"tf":1.0},"57":{"tf":1.0}}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"105":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"105":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"o":{"c":{"df":1,"docs":{"79":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"\'":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"c":{"2":{"8":{"2":{"2":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"24":{"tf":1.0}}}}}}},"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"24":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{"3":{"3":{"9":{"(":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"24":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"(":{"\\"":{"c":{"df":0,"docs":{},"o":{"d":{"df":6,"docs":{"20":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":2,"docs":{"53":{"tf":1.0},"57":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"55":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"55":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"a":{"d":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"n":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":9,"docs":{"20":{"tf":1.0},"28":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.0},"57":{"tf":2.23606797749979},"58":{"tf":2.0},"63":{"tf":1.0}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"47":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"27":{"tf":1.0}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"51":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":17,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.4142135623730951},"22":{"tf":1.0},"23":{"tf":1.0},"30":{"tf":1.4142135623730951},"5":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"91":{"tf":1.4142135623730951},"92":{"tf":1.0},"95":{"tf":1.4142135623730951},"96":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":13,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"22":{"tf":1.0},"30":{"tf":1.4142135623730951},"5":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"91":{"tf":1.4142135623730951},"95":{"tf":1.0},"96":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{")":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":5,"docs":{"17":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"n":{"d":{"df":1,"docs":{"51":{"tf":2.449489742783178}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":10,"docs":{"27":{"tf":1.4142135623730951},"28":{"tf":2.449489742783178},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"32":{"tf":1.7320508075688772},"37":{"tf":1.0},"45":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"65":{"tf":1.0}}}},"v":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}}},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}},"q":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"65":{"tf":1.0}},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"d":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"1":{"0":{"0":{"df":1,"docs":{"46":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":1,"docs":{"46":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"r":{"(":{"\\"":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"i":{"d":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"\\"":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":2,"docs":{"45":{"tf":1.0},"46":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":1,"docs":{"57":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"\\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"\\"":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"53":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"!":{"(":{"\\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"49":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"99":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}}},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"55":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"55":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"20":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"54":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"\\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}}}}}}}}}}}}},"df":0,"docs":{}},":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"58":{"tf":1.0}}}}}}}}}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"55":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":7,"docs":{"103":{"tf":1.0},"104":{"tf":1.4142135623730951},"20":{"tf":1.0},"53":{"tf":1.4142135623730951},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"57":{"tf":1.0}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":3,"docs":{"54":{"tf":1.0},"55":{"tf":1.7320508075688772},"58":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"为":{"&":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}}}}}}}}}}}}}},"很":{"df":0,"docs":{},"麻":{"df":0,"docs":{},"烦":{"df":0,"docs":{},"。":{"df":0,"docs":{},"这":{"df":0,"docs":{},"就":{"df":0,"docs":{},"是":{"df":0,"docs":{},"为":{"df":0,"docs":{},"什":{"df":0,"docs":{},"么":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"54":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"1":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"23":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"23":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"26":{"tf":1.0},"56":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"72":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":2,"docs":{"17":{"tf":1.0},"74":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"46":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"55":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"56":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"57":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":1,"docs":{"54":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":13,"docs":{"53":{"tf":1.0},"55":{"tf":2.0},"57":{"tf":1.0},"67":{"tf":1.7320508075688772},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"c":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"<":{"\'":{"_":{"df":8,"docs":{"68":{"tf":1.0},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"n":{"df":68,"docs":{"10":{"tf":1.4142135623730951},"100":{"tf":1.4142135623730951},"105":{"tf":1.4142135623730951},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"15":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.7320508075688772},"18":{"tf":1.4142135623730951},"19":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.4142135623730951},"26":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.4142135623730951},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.7320508075688772},"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":2.0},"56":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0},"79":{"tf":1.0},"87":{"tf":1.7320508075688772},"88":{"tf":1.7320508075688772},"9":{"tf":1.0},"91":{"tf":1.7320508075688772},"92":{"tf":1.7320508075688772},"95":{"tf":1.7320508075688772},"96":{"tf":1.7320508075688772},"99":{"tf":1.0}}}}}}}},"f":{"3":{"2":{"df":2,"docs":{"29":{"tf":2.8284271247461903},"31":{"tf":2.449489742783178}}},"df":0,"docs":{}},"6":{"4":{"df":2,"docs":{"33":{"tf":1.7320508075688772},"4":{"tf":1.0}}},"df":0,"docs":{}},"a":{"c":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"103":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":1,"docs":{"65":{"tf":1.4142135623730951}}}}},"df":1,"docs":{"63":{"tf":1.0}},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"是":{"df":0,"docs":{},"一":{"df":0,"docs":{},"个":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"106":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"的":{"df":0,"docs":{},"所":{"df":0,"docs":{},"有":{"df":0,"docs":{},"功":{"df":0,"docs":{},"能":{"df":0,"docs":{},",":{"df":0,"docs":{},"但":{"df":0,"docs":{},"需":{"df":0,"docs":{},"要":{"df":0,"docs":{},"对":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"106":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}}}}}}}},"df":1,"docs":{"106":{"tf":1.0}}}}},"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"103":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"102":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":3,"docs":{"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"37":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"1":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"22":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"10":{"tf":2.0},"34":{"tf":2.0}}},"2":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"22":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"10":{"tf":2.0},"34":{"tf":2.0}}},"df":3,"docs":{"105":{"tf":1.4142135623730951},"45":{"tf":1.0},"75":{"tf":1.0}}},"df":0,"docs":{}}},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"56":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"m":{"df":1,"docs":{"27":{"tf":1.0}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"91":{"tf":1.0},"92":{"tf":1.0}},",":{"df":0,"docs":{},"或":{"df":0,"docs":{},"者":{"df":0,"docs":{},"直":{"df":0,"docs":{},"接":{"df":0,"docs":{},"调":{"df":0,"docs":{},"用":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"90":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}}}},"n":{"d":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"b":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"107":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"a":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"107":{"tf":1.0}},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"107":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"107":{"tf":1.0}},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"107":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"107":{"tf":1.0}}},"df":0,"docs":{}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"108":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":1,"docs":{"55":{"tf":1.4142135623730951}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":7,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"16":{"tf":1.0},"30":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{")":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":1,"docs":{"51":{"tf":2.0}}}}}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"32":{"tf":1.0}}}}}}},"df":0,"docs":{}},"n":{"df":61,"docs":{"102":{"tf":1.0},"103":{"tf":1.4142135623730951},"104":{"tf":1.4142135623730951},"105":{"tf":2.23606797749979},"107":{"tf":1.7320508075688772},"108":{"tf":1.0},"11":{"tf":1.0},"13":{"tf":2.0},"15":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":2.0},"23":{"tf":1.4142135623730951},"24":{"tf":2.449489742783178},"26":{"tf":1.4142135623730951},"28":{"tf":1.0},"29":{"tf":2.449489742783178},"30":{"tf":1.4142135623730951},"31":{"tf":2.0},"33":{"tf":1.0},"36":{"tf":2.0},"37":{"tf":2.0},"38":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.4142135623730951},"43":{"tf":1.0},"45":{"tf":2.0},"46":{"tf":1.7320508075688772},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951},"50":{"tf":1.7320508075688772},"51":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.4142135623730951},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"65":{"tf":1.0},"68":{"tf":1.0},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0},"79":{"tf":1.4142135623730951},"91":{"tf":1.4142135623730951},"92":{"tf":1.7320508075688772},"95":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.4142135623730951}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"!":{"(":{"\\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"d":{"\\"":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"103":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"(":{"df":1,"docs":{"28":{"tf":1.0}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":2,"docs":{"24":{"tf":1.0},"26":{"tf":1.0}}}}},"df":0,"docs":{}}},"<":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"c":{"2":{"8":{"2":{"2":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"u":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":1.0}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"t":{">":{">":{"(":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"26":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"u":{"df":1,"docs":{"25":{"tf":1.0}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"c":{"<":{"df":0,"docs":{},"u":{"df":1,"docs":{"25":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{":":{":":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"76":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{"0":{".":{".":{"1":{"0":{"df":2,"docs":{"23":{"tf":1.0},"92":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{"0":{".":{".":{"2":{"0":{"df":1,"docs":{"23":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"{":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"23":{"tf":1.0},"92":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"46":{"tf":1.0},"79":{"tf":1.0}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"88":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"(":{"c":{"a":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"(":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"x":{"_":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"50":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"50":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":2,"docs":{"11":{"tf":1.0},"64":{"tf":1.4142135623730951}}}}}}},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"10":{"tf":2.0},"34":{"tf":2.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"36":{"tf":1.0},"38":{"tf":1.0}}}}}}},"df":3,"docs":{"36":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"43":{"tf":1.0}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"d":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"24":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"26":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"s":{"c":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"107":{"tf":1.7320508075688772},"108":{"tf":1.0}}}}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"30":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"32":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"45":{"tf":1.4142135623730951},"46":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"12":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"107":{"tf":1.0}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"29":{"tf":1.4142135623730951}},"e":{"=":{"\\"":{"a":{"a":{"a":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"9":{"tf":1.0}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"49":{"tf":1.0}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"48":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"47":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"65":{"tf":2.0}}}}}},"/":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"_":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"81":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"7":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"/":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"97":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"107":{"tf":1.0}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"89":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":1,"docs":{"93":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"87":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"90":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":40,"docs":{"0":{"tf":2.23606797749979},"1":{"tf":1.7320508075688772},"101":{"tf":1.0},"105":{"tf":1.4142135623730951},"106":{"tf":1.0},"11":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"29":{"tf":2.0},"3":{"tf":2.0},"30":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":2.23606797749979},"41":{"tf":1.0},"50":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"52":{"tf":1.4142135623730951},"54":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.7320508075688772},"72":{"tf":1.7320508075688772},"77":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"80":{"tf":1.0},"81":{"tf":1.4142135623730951},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"85":{"tf":2.449489742783178},"9":{"tf":1.4142135623730951},"90":{"tf":1.4142135623730951},"94":{"tf":1.0},"99":{"tf":1.0}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"95":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"95":{"tf":1.0}}}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"94":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{":":{":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"&":{"*":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{")":{")":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":1,"docs":{"96":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"仅":{"df":0,"docs":{},"支":{"df":0,"docs":{},"持":{"df":0,"docs":{},"添":{"df":0,"docs":{},"加":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"105":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}}},"可":{"df":0,"docs":{},"以":{"df":0,"docs":{},"完":{"df":0,"docs":{},"全":{"df":0,"docs":{},"支":{"df":0,"docs":{},"持":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":1,"docs":{"106":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}}}},"用":{"df":0,"docs":{},"于":{"df":0,"docs":{},"执":{"df":0,"docs":{},"行":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"和":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"90":{"tf":1.0}}}}}}}}}}}}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"u":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"45":{"tf":2.0},"46":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"45":{"tf":1.7320508075688772}}}}}}},"h":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"100":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"17":{"tf":1.0},"18":{"tf":2.449489742783178}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{":":{":":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"_":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":2,"docs":{"18":{"tf":2.0},"50":{"tf":1.0}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.0}}}}}}}}},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":5,"docs":{"107":{"tf":1.0},"7":{"tf":1.0},"89":{"tf":1.0},"93":{"tf":1.0},"97":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"i":{"3":{"2":{":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":32,"docs":{"10":{"tf":2.0},"100":{"tf":1.4142135623730951},"108":{"tf":1.7320508075688772},"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"13":{"tf":1.0},"16":{"tf":1.0},"19":{"tf":2.0},"22":{"tf":2.0},"23":{"tf":1.7320508075688772},"32":{"tf":2.449489742783178},"33":{"tf":1.0},"34":{"tf":2.0},"36":{"tf":2.6457513110645907},"37":{"tf":3.605551275463989},"38":{"tf":2.0},"4":{"tf":2.0},"41":{"tf":1.0},"43":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"46":{"tf":2.449489742783178},"49":{"tf":2.23606797749979},"50":{"tf":1.7320508075688772},"51":{"tf":1.4142135623730951},"64":{"tf":1.4142135623730951},"65":{"tf":1.7320508075688772},"87":{"tf":1.0},"88":{"tf":1.0},"9":{"tf":1.7320508075688772},"92":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0}}},"df":0,"docs":{}},"6":{"4":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"d":{"df":6,"docs":{"102":{"tf":4.58257569495584},"103":{"tf":2.0},"107":{"tf":3.4641016151377544},"108":{"tf":1.4142135623730951},"13":{"tf":1.7320508075688772},"79":{"tf":1.4142135623730951}},"和":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"107":{"tf":1.0}},"e":{"df":0,"docs":{},"查":{"df":0,"docs":{},"找":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"对":{"df":0,"docs":{},"象":{"df":0,"docs":{},",":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"107":{"tf":1.0}}}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}},"查":{"df":0,"docs":{},"找":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"对":{"df":0,"docs":{},"象":{"df":0,"docs":{},",":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"107":{"tf":1.4142135623730951}}}}}}}}}}}}},",":{"df":0,"docs":{},"并":{"df":0,"docs":{},"且":{"df":0,"docs":{},"请":{"df":0,"docs":{},"求":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"对":{"df":0,"docs":{},"象":{"df":0,"docs":{},"的":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"107":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}}}}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":55,"docs":{"102":{"tf":1.0},"103":{"tf":1.4142135623730951},"104":{"tf":1.4142135623730951},"105":{"tf":2.23606797749979},"107":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"13":{"tf":2.0},"15":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":2.0},"23":{"tf":2.0},"24":{"tf":2.0},"25":{"tf":1.0},"26":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"33":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.4142135623730951},"45":{"tf":2.0},"46":{"tf":1.7320508075688772},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.7320508075688772},"5":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.7320508075688772},"99":{"tf":1.0}},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"58":{"tf":1.4142135623730951},"70":{"tf":1.0}}}}}}}}}},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"_":{"df":0,"docs":{},"w":{"df":1,"docs":{"96":{"tf":1.0}}}},"df":1,"docs":{"95":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{">":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":1,"docs":{"91":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"o":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"75":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"57":{"tf":1.4142135623730951},"75":{"tf":1.0}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"47":{"tf":1.0},"48":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"12":{"tf":1.0}}}},"df":0,"docs":{}}},"df":3,"docs":{"20":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.4142135623730951}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"12":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.4142135623730951},"38":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"34":{"tf":1.7320508075688772}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"<":{"df":0,"docs":{},"i":{"3":{"2":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"99":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}}}}},"t":{"df":1,"docs":{"61":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"43":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"f":{"a":{"c":{"df":3,"docs":{"22":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"o":{":":{":":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"25":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"p":{"df":1,"docs":{"47":{"tf":1.4142135623730951}}},"s":{"_":{"a":{"d":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"(":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":1,"docs":{"65":{"tf":1.0}}}}},"df":0,"docs":{}},"df":1,"docs":{"65":{"tf":2.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"d":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"(":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"65":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"103":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"j":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{".":{"a":{"df":1,"docs":{"108":{"tf":1.0}}},"df":0,"docs":{}},"df":7,"docs":{"103":{"tf":1.0},"104":{"tf":1.4142135623730951},"107":{"tf":1.7320508075688772},"108":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"79":{"tf":2.0}},"s":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{")":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"103":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":2.449489742783178}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"55":{"tf":1.0}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"(":{"5":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"5":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"o":{"a":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"79":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"i":{"d":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"<":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"u":{"6":{"4":{"df":1,"docs":{"103":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"c":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"28":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":2.0}}}}}}},"t":{"df":3,"docs":{"53":{"tf":1.0},"55":{"tf":1.0},"57":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"82":{"tf":1.4142135623730951},"84":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"42":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"43":{"tf":1.0}}}}}},"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"(":{"a":{"df":0,"docs":{},"r":{"c":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"103":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"|":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":2,"docs":{"20":{"tf":1.0},"54":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"58":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"k":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"103":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"c":{"df":0,"docs":{},"h":{"df":4,"docs":{"105":{"tf":1.0},"28":{"tf":1.0},"55":{"tf":1.0},"57":{"tf":1.0}}}},"df":0,"docs":{}},"x":{"_":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"=":{"3":{"0":{"df":1,"docs":{"50":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"6":{"0":{"df":1,"docs":{"50":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":2,"docs":{"47":{"tf":1.0},"48":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"21":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":3,"docs":{"53":{"tf":1.0},"55":{"tf":1.0},"57":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"29":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"i":{"d":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"68":{"tf":1.0}},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"68":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"o":{"d":{"df":1,"docs":{"28":{"tf":1.4142135623730951}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"57":{"tf":1.4142135623730951}}}},"v":{"df":0,"docs":{},"e":{"df":3,"docs":{"17":{"tf":1.0},"51":{"tf":1.0},"91":{"tf":1.0}}},"i":{"df":1,"docs":{"22":{"tf":1.0}},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.7320508075688772}}}}}}},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"22":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"18":{"tf":1.4142135623730951}},"e":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"t":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"33":{"tf":1.4142135623730951},"4":{"tf":1.0},"42":{"tf":1.4142135623730951},"5":{"tf":1.0}}}},"df":3,"docs":{"17":{"tf":1.0},"43":{"tf":1.0},"51":{"tf":1.7320508075688772}}}},"v":{"df":1,"docs":{"100":{"tf":2.0}}},"y":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"36":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"65":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"55":{"tf":1.0}}}}}}}}}}}}}}}}}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"55":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"55":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"55":{"tf":1.7320508075688772}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"38":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"a":{"c":{"df":2,"docs":{"30":{"tf":1.7320508075688772},"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"53":{"tf":1.0},"57":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{"d":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":7,"docs":{"70":{"tf":2.0},"71":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"74":{"tf":1.4142135623730951},"75":{"tf":1.4142135623730951},"76":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"b":{"df":0,"docs":{},"j":{"(":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":5,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"19":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"65":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"(":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":4,"docs":{"13":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"30":{"tf":1.7320508075688772},"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"12":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":2,"docs":{"30":{"tf":1.0},"91":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"1":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":1,"docs":{"49":{"tf":2.0}}},"df":0,"docs":{}},"u":{"df":1,"docs":{"100":{"tf":1.0}}}}},"df":0,"docs":{}}}},"n":{"+":{"1":{"df":1,"docs":{"101":{"tf":1.0}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"102":{"tf":1.0},"103":{"tf":1.0}}}}}}},"df":0,"docs":{}},".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":1,"docs":{"103":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},":":{"\\"":{"\\"":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":9,"docs":{"102":{"tf":4.58257569495584},"103":{"tf":2.0},"13":{"tf":1.0},"17":{"tf":1.0},"26":{"tf":1.0},"29":{"tf":1.4142135623730951},"30":{"tf":2.0},"47":{"tf":1.4142135623730951},"48":{"tf":1.0}},"和":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"29":{"tf":1.0}},"属":{"df":0,"docs":{},"性":{"df":0,"docs":{},"同":{"df":0,"docs":{},"时":{"df":0,"docs":{},"存":{"df":0,"docs":{},"在":{"df":0,"docs":{},"时":{"df":0,"docs":{},",":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}},"存":{"df":0,"docs":{},"在":{"df":0,"docs":{},"时":{"df":0,"docs":{},",":{"df":0,"docs":{},"转":{"df":0,"docs":{},"换":{"df":0,"docs":{},"为":{"df":0,"docs":{},"驼":{"df":0,"docs":{},"峰":{"df":0,"docs":{},"命":{"df":0,"docs":{},"名":{"df":0,"docs":{},"后":{"df":0,"docs":{},"的":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}}}}}}}},"df":4,"docs":{"102":{"tf":1.0},"47":{"tf":3.0},"49":{"tf":2.0},"51":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}},"e":{"d":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"108":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"w":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"46":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"df":1,"docs":{"49":{"tf":1.0}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"45":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"x":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"(":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"68":{"tf":1.0},"70":{"tf":1.4142135623730951},"73":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":5,"docs":{"71":{"tf":1.0},"72":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0}}}}},"df":0,"docs":{}},"df":1,"docs":{"68":{"tf":1.7320508075688772}}}}}},"df":8,"docs":{"68":{"tf":1.4142135623730951},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"<":{"\'":{"_":{"df":1,"docs":{"74":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"m":{"df":0,"docs":{},"i":{"d":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"<":{"\'":{"_":{"df":1,"docs":{"68":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"<":{"\'":{"_":{"df":1,"docs":{"72":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"<":{"\'":{"_":{"df":1,"docs":{"71":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"<":{"\'":{"_":{"df":1,"docs":{"70":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"<":{"\'":{"_":{"df":1,"docs":{"75":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"e":{"<":{"\'":{"_":{"df":1,"docs":{"76":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"\'":{"_":{"df":1,"docs":{"73":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"55":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}}}},"u":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"51":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"o":{"b":{"df":0,"docs":{},"j":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"19":{"tf":1.0},"30":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"19":{"tf":1.4142135623730951}},"e":{"c":{"df":0,"docs":{},"t":{"(":{"c":{"a":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"(":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"x":{"_":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"50":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":40,"docs":{"10":{"tf":1.0},"102":{"tf":1.0},"103":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"13":{"tf":1.7320508075688772},"15":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":2.6457513110645907},"24":{"tf":1.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"36":{"tf":1.0},"37":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0}}}},"df":0,"docs":{}}}},"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"2":{"0":{"2":{"1":{"df":1,"docs":{"72":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"k":{"(":{"\\"":{"2":{"3":{"4":{"a":{"df":3,"docs":{"20":{"tf":1.0},"54":{"tf":1.0},"56":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{"df":0,"docs":{},"i":{"d":{")":{"?":{".":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"102":{"tf":1.0}}}},"df":1,"docs":{"57":{"tf":1.4142135623730951}}},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"x":{":":{":":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"a":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"103":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{")":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"b":{"df":1,"docs":{"99":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},":":{":":{"<":{"_":{"df":3,"docs":{"17":{"tf":1.0},"51":{"tf":1.0},"91":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"45":{"tf":1.0},"46":{"tf":1.0},"49":{"tf":1.0}}},"n":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"55":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":2,"docs":{"18":{"tf":1.0},"27":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"83":{"tf":1.7320508075688772}}}}}}}}}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"74":{"tf":1.0}},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"74":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"74":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"3":{"2":{"df":1,"docs":{"51":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":4,"docs":{"103":{"tf":1.0},"17":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"79":{"tf":1.0}}}}},"t":{"df":5,"docs":{"10":{"tf":1.4142135623730951},"25":{"tf":1.0},"26":{"tf":1.0},"34":{"tf":1.4142135623730951},"4":{"tf":1.0}}},"u":{"df":1,"docs":{"26":{"tf":1.0}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}},"e":{"d":{"2":{"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"26":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"t":{"df":1,"docs":{"26":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"56":{"tf":1.0}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"10":{"tf":1.7320508075688772}}}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"18":{"tf":1.0},"57":{"tf":1.0}}}}}}}}}},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"10":{"tf":1.0},"34":{"tf":1.0}},"s":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951}}}}}}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"28":{"tf":1.0}}}}},"s":{"df":5,"docs":{"20":{"tf":1.0},"54":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"72":{"tf":1.0}},"e":{"(":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"99":{"tf":1.0}}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"72":{"tf":1.4142135623730951}}}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":5,"docs":{"20":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"55":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"20":{"tf":1.0},"54":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}}}}}}}}},"t":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"65":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"42":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"53":{"tf":1.0},"55":{"tf":1.0},"57":{"tf":1.0}}}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"96":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":1.4142135623730951}}}}}}}},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"87":{"tf":1.0}}}}}},"{":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"88":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":4,"docs":{"85":{"tf":1.4142135623730951},"86":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0}}}},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"102":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.7320508075688772}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"104":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"s":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"70":{"tf":1.0},"71":{"tf":1.7320508075688772}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":2,"docs":{"18":{"tf":1.0},"57":{"tf":1.0}}}}}}},"i":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"50":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"80":{"tf":1.0}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"106":{"tf":1.0},"28":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"b":{"df":11,"docs":{"10":{"tf":1.7320508075688772},"13":{"tf":1.0},"17":{"tf":1.0},"26":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.7320508075688772},"32":{"tf":2.449489742783178},"34":{"tf":1.7320508075688772},"49":{"tf":1.0},"55":{"tf":1.4142135623730951},"79":{"tf":1.0}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":43,"docs":{"102":{"tf":1.0},"103":{"tf":1.0},"105":{"tf":1.4142135623730951},"107":{"tf":1.4142135623730951},"108":{"tf":1.4142135623730951},"15":{"tf":1.4142135623730951},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.4142135623730951},"19":{"tf":2.23606797749979},"20":{"tf":1.4142135623730951},"23":{"tf":1.0},"24":{"tf":1.7320508075688772},"30":{"tf":2.0},"36":{"tf":1.4142135623730951},"4":{"tf":1.7320508075688772},"41":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.4142135623730951},"47":{"tf":1.4142135623730951},"48":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951},"5":{"tf":1.7320508075688772},"50":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"53":{"tf":1.4142135623730951},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"59":{"tf":1.4142135623730951},"61":{"tf":1.0},"62":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"64":{"tf":1.4142135623730951},"72":{"tf":2.0},"73":{"tf":1.0},"74":{"tf":1.0},"79":{"tf":2.23606797749979},"87":{"tf":1.0},"88":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0}}},"y":{"(":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.0}}}}}}}}}}}},":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":2,"docs":{"22":{"tf":1.0},"23":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":2,"docs":{"91":{"tf":1.4142135623730951},"92":{"tf":1.4142135623730951}}}}}}}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"u":{"df":3,"docs":{"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"33":{"tf":1.0}}}}},"df":0,"docs":{},"w":{"df":1,"docs":{"72":{"tf":1.0}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"55":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"df":1,"docs":{"5":{"tf":1.0}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"79":{"tf":1.0}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"=":{"df":0,"docs":{},"r":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{":":{":":{"<":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"a":{"c":{"df":1,"docs":{"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":2.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"y":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.4142135623730951}},"e":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{":":{":":{"a":{"df":1,"docs":{"28":{"tf":1.0}}},"b":{"df":1,"docs":{"28":{"tf":1.0}}},"c":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},">":{"df":0,"docs":{},"以":{"df":0,"docs":{},"及":{"df":0,"docs":{},"相":{"df":0,"docs":{},"反":{"df":0,"docs":{},"的":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"<":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":1,"docs":{"28":{"tf":1.7320508075688772}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"28":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":1.4142135623730951}}}}}}}}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"17":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"80":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":6,"docs":{"78":{"tf":1.0},"79":{"tf":1.0},"80":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0}}}}}}}}}},"q":{"df":1,"docs":{"96":{"tf":1.0}},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"70":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"71":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":6,"docs":{"17":{"tf":1.4142135623730951},"70":{"tf":1.4142135623730951},"71":{"tf":1.7320508075688772},"76":{"tf":1.0},"91":{"tf":1.0},"95":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":13,"docs":{"102":{"tf":2.0},"105":{"tf":1.4142135623730951},"13":{"tf":2.0},"15":{"tf":1.0},"20":{"tf":1.0},"29":{"tf":2.0},"41":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"50":{"tf":1.4142135623730951},"53":{"tf":1.0},"75":{"tf":2.449489742783178},"9":{"tf":1.0}},"e":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"105":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"105":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"<":{"\'":{"_":{"df":1,"docs":{"105":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"<":{"\'":{"_":{"df":1,"docs":{"75":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"p":{"df":1,"docs":{"91":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":4,"docs":{"17":{"tf":1.0},"70":{"tf":1.4142135623730951},"74":{"tf":1.0},"76":{"tf":1.4142135623730951}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"&":{"\'":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":1,"docs":{"15":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"42":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"<":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"u":{"6":{"4":{"df":1,"docs":{"103":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"i":{"3":{"2":{"df":9,"docs":{"20":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"41":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":4,"docs":{"102":{"tf":1.0},"103":{"tf":1.0},"13":{"tf":1.0},"42":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"43":{"tf":1.0}}}},"df":0,"docs":{}}}}},"t":{"df":1,"docs":{"20":{"tf":1.0}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"73":{"tf":1.0}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":8,"docs":{"20":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"49":{"tf":1.0},"70":{"tf":1.4142135623730951},"71":{"tf":1.4142135623730951},"74":{"tf":1.4142135623730951},"75":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"4":{"tf":1.0}}}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"85":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"45":{"tf":3.1622776601683795}},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"d":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{":":{":":{"a":{"d":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{")":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"d":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"45":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"45":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":1,"docs":{"45":{"tf":2.449489742783178}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"88":{"tf":1.0}},"e":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":2,"docs":{"87":{"tf":1.0},"88":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":8,"docs":{"0":{"tf":1.7320508075688772},"1":{"tf":1.0},"22":{"tf":1.0},"28":{"tf":1.0},"4":{"tf":1.0},"58":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}},"f":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}},",":{"df":0,"docs":{},"之":{"df":0,"docs":{},"前":{"df":0,"docs":{},"我":{"df":0,"docs":{},"一":{"df":0,"docs":{},"直":{"df":0,"docs":{},"用":{"df":0,"docs":{},"j":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}}}}}}}}}}}},"s":{"3":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"16":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"79":{"tf":1.0}}}}},"c":{"a":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"!":{"(":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"100":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}},"e":{"d":{"2":{"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":3,"docs":{"100":{"tf":1.0},"24":{"tf":1.4142135623730951},"99":{"tf":1.0}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"99":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"29":{"tf":1.0}}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}},"e":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"91":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},":":{":":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"(":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":5,"docs":{"105":{"tf":1.0},"30":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0}}},"y":{":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":6,"docs":{"16":{"tf":1.0},"17":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":2,"docs":{"91":{"tf":1.0},"92":{"tf":1.0}}}}}}}}}}}},"df":2,"docs":{"22":{"tf":1.0},"23":{"tf":1.0}}}}}},"df":0,"docs":{}},"<":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"17":{"tf":1.0},"30":{"tf":1.0}}},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"91":{"tf":1.0}}}}}}}}}}}},"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":21,"docs":{"0":{"tf":1.0},"103":{"tf":1.4142135623730951},"105":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.4142135623730951},"17":{"tf":1.7320508075688772},"22":{"tf":1.0},"23":{"tf":1.0},"30":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.4142135623730951},"5":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0},"95":{"tf":1.7320508075688772},"96":{"tf":1.4142135623730951}},",":{"df":0,"docs":{},"然":{"df":0,"docs":{},"后":{"df":0,"docs":{},"调":{"df":0,"docs":{},"用":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"d":{"df":0,"docs":{},"l":{"df":2,"docs":{"10":{"tf":1.0},"34":{"tf":1.0}}}},"df":2,"docs":{"29":{"tf":2.23606797749979},"31":{"tf":2.0}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"102":{"tf":4.47213595499958},"103":{"tf":1.4142135623730951},"19":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"f":{")":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"55":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},".":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"46":{"tf":1.0}}}}},"df":1,"docs":{"11":{"tf":1.0}}},"b":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"46":{"tf":1.0},"49":{"tf":1.0}}}},"df":0,"docs":{}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"u":{"df":2,"docs":{"29":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"105":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"29":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},":":{":":{"a":{"df":1,"docs":{"28":{"tf":1.0}}},"b":{"df":1,"docs":{"28":{"tf":1.0}}},"c":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.4142135623730951}}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":15,"docs":{"13":{"tf":1.0},"15":{"tf":1.0},"24":{"tf":1.0},"26":{"tf":1.0},"28":{"tf":1.0},"45":{"tf":2.0},"46":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951},"55":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0}}}},"n":{"d":{"df":1,"docs":{"79":{"tf":1.0}},"和":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"15":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"r":{"d":{"df":2,"docs":{"100":{"tf":1.4142135623730951},"26":{"tf":1.0}},"e":{":":{":":{"df":0,"docs":{},"{":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"100":{"tf":1.0},"26":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{"&":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"z":{"df":0,"docs":{},"e":{"df":0,"docs":{},"和":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"100":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"7":{"tf":1.0},"85":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"72":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":2,"docs":{"105":{"tf":1.4142135623730951},"75":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"71":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}}}}}}}},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"79":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"h":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":2,"docs":{"29":{"tf":2.0},"31":{"tf":1.7320508075688772}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":2.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"29":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"42":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":6,"docs":{"10":{"tf":1.0},"17":{"tf":1.0},"45":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"90":{"tf":1.0},"94":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"45":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}}}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}}},"t":{"df":1,"docs":{"34":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}}},"t":{"df":1,"docs":{"10":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"10":{"tf":1.7320508075688772},"34":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"10":{"tf":1.7320508075688772},"34":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"10":{"tf":1.7320508075688772},"34":{"tf":1.7320508075688772}}}}}}}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"17":{"tf":1.0}}},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"10":{"tf":1.7320508075688772},"34":{"tf":1.7320508075688772}}}}}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":2,"docs":{"52":{"tf":1.0},"80":{"tf":1.0}},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"58":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":2,"docs":{"51":{"tf":1.0},"81":{"tf":1.0}}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"x":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"104":{"tf":1.0}}}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"<":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"a":{"df":0,"docs":{},"s":{"(":{"\\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"102":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"29":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}},"e":{"\\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"58":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":1,"docs":{"27":{"tf":1.0}},"t":{".":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{")":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{")":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"n":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"51":{"tf":2.0}}}},"t":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"79":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"100":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"n":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"17":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"f":{"3":{"2":{":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"20":{"tf":1.0},"54":{"tf":1.0},"58":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":1,"docs":{"20":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{":":{":":{"a":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"103":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{":":{":":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"43":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"43":{"tf":1.4142135623730951}}}},"r":{"df":10,"docs":{"105":{"tf":1.0},"18":{"tf":1.0},"30":{"tf":1.0},"5":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"72":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"m":{"<":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":3,"docs":{"23":{"tf":1.4142135623730951},"43":{"tf":1.0},"92":{"tf":1.0}}}}}}},"df":2,"docs":{"43":{"tf":1.0},"76":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"92":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"(":{"\\"":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":19,"docs":{"10":{"tf":2.0},"102":{"tf":1.0},"103":{"tf":1.4142135623730951},"105":{"tf":2.0},"107":{"tf":1.4142135623730951},"13":{"tf":1.7320508075688772},"15":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"20":{"tf":1.0},"24":{"tf":1.7320508075688772},"29":{"tf":1.7320508075688772},"30":{"tf":2.0},"34":{"tf":2.0},"41":{"tf":1.0},"42":{"tf":2.0},"47":{"tf":1.0},"61":{"tf":1.4142135623730951},"79":{"tf":1.7320508075688772}},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"b":{"df":1,"docs":{"99":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"i":{"6":{"4":{"df":1,"docs":{"99":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":67,"docs":{"10":{"tf":2.6457513110645907},"100":{"tf":1.0},"102":{"tf":1.0},"103":{"tf":1.4142135623730951},"104":{"tf":1.0},"105":{"tf":1.7320508075688772},"107":{"tf":1.4142135623730951},"108":{"tf":1.7320508075688772},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":2.0},"15":{"tf":1.0},"16":{"tf":2.0},"17":{"tf":1.4142135623730951},"18":{"tf":1.0},"19":{"tf":1.7320508075688772},"20":{"tf":1.0},"22":{"tf":2.23606797749979},"23":{"tf":2.0},"24":{"tf":1.7320508075688772},"26":{"tf":1.7320508075688772},"29":{"tf":1.4142135623730951},"30":{"tf":2.0},"31":{"tf":1.4142135623730951},"32":{"tf":2.449489742783178},"33":{"tf":1.7320508075688772},"34":{"tf":2.6457513110645907},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.4142135623730951},"42":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.7320508075688772},"46":{"tf":1.4142135623730951},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.4142135623730951},"5":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"9":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.4142135623730951},"95":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.0}}}},"df":0,"docs":{}}},"u":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":2,"docs":{"80":{"tf":1.0},"81":{"tf":1.0}}}}},"df":0,"docs":{}}},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"76":{"tf":1.0}},"e":{"<":{"\'":{"df":1,"docs":{"76":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"的":{"df":0,"docs":{},"行":{"df":0,"docs":{},"为":{"df":0,"docs":{},"和":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"76":{"tf":1.0}}}}}}}}}}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":4,"docs":{"23":{"tf":1.4142135623730951},"4":{"tf":1.0},"43":{"tf":1.7320508075688772},"92":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"1":{"df":1,"docs":{"23":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"1":{"df":1,"docs":{"23":{"tf":1.4142135623730951}}},"2":{"df":1,"docs":{"23":{"tf":1.7320508075688772}}},":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"23":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"92":{"tf":1.7320508075688772}}}}}},",":{"df":0,"docs":{},"所":{"df":0,"docs":{},"以":{"df":0,"docs":{},"我":{"df":0,"docs":{},"们":{"df":0,"docs":{},"用":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"和":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}}}}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"df":0,"docs":{},"m":{"df":1,"docs":{"4":{"tf":1.0}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"79":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"25":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"1":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"36":{"tf":1.0},"37":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}},"2":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"36":{"tf":1.0},"37":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}},"3":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"36":{"tf":1.0},"37":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":1,"docs":{"26":{"tf":1.0}}}},"df":0,"docs":{}}}},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"55":{"tf":1.4142135623730951}}}}}}}}}},"i":{"c":{"df":0,"docs":{},"k":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"92":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}}},"o":{"_":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"99":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"o":{"df":17,"docs":{"102":{"tf":2.23606797749979},"103":{"tf":1.4142135623730951},"104":{"tf":1.4142135623730951},"105":{"tf":1.0},"22":{"tf":2.0},"24":{"tf":1.7320508075688772},"30":{"tf":1.0},"33":{"tf":1.0},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772},"41":{"tf":1.0},"42":{"tf":1.4142135623730951},"45":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"56":{"tf":1.0},"64":{"tf":1.4142135623730951}},"i":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}},"对":{"df":0,"docs":{},"象":{"df":0,"docs":{},"。":{"df":0,"docs":{},"然":{"df":0,"docs":{},"后":{"df":0,"docs":{},"对":{"df":0,"docs":{},"每":{"df":0,"docs":{},"个":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"对":{"df":0,"docs":{},"象":{"df":0,"docs":{},"同":{"df":0,"docs":{},"时":{"df":0,"docs":{},"调":{"df":0,"docs":{},"用":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"102":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"17":{"tf":1.4142135623730951},"42":{"tf":1.0}}}},"i":{"df":0,"docs":{},"o":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"103":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"43":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"43":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}}}}}}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"e":{"df":4,"docs":{"59":{"tf":1.0},"80":{"tf":1.7320508075688772},"81":{"tf":1.0},"84":{"tf":1.7320508075688772}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"扩":{"df":0,"docs":{},"展":{"df":0,"docs":{},"需":{"df":0,"docs":{},"要":{"df":0,"docs":{},"在":{"df":0,"docs":{},"创":{"df":0,"docs":{},"建":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"58":{"tf":1.0},"67":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"e":{"df":1,"docs":{"65":{"tf":1.4142135623730951}}}},"y":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"y":{"df":3,"docs":{"29":{"tf":2.0},"30":{"tf":1.4142135623730951},"37":{"tf":2.449489742783178}},"p":{"df":0,"docs":{},"e":{"df":9,"docs":{"10":{"tf":1.4142135623730951},"103":{"tf":1.4142135623730951},"104":{"tf":2.0},"20":{"tf":1.0},"24":{"tf":1.0},"28":{"tf":1.7320508075688772},"30":{"tf":1.0},"61":{"tf":1.4142135623730951},"91":{"tf":1.0}}}}}},"u":{"6":{"4":{"df":2,"docs":{"102":{"tf":1.0},"103":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"26":{"tf":1.0}},"n":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"47":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"31":{"tf":1.0},"32":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"l":{"df":1,"docs":{"47":{"tf":1.4142135623730951}}}},"s":{"df":67,"docs":{"10":{"tf":1.4142135623730951},"100":{"tf":1.7320508075688772},"103":{"tf":2.0},"105":{"tf":1.4142135623730951},"107":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"15":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.7320508075688772},"18":{"tf":2.23606797749979},"19":{"tf":1.0},"20":{"tf":1.4142135623730951},"22":{"tf":1.0},"23":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"26":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.4142135623730951},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":2.0},"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.4142135623730951},"53":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.4142135623730951},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"70":{"tf":2.0},"71":{"tf":1.7320508075688772},"72":{"tf":1.7320508075688772},"73":{"tf":1.4142135623730951},"74":{"tf":1.4142135623730951},"75":{"tf":1.4142135623730951},"76":{"tf":1.7320508075688772},"79":{"tf":1.0},"87":{"tf":1.7320508075688772},"88":{"tf":1.7320508075688772},"9":{"tf":1.0},"91":{"tf":1.7320508075688772},"92":{"tf":2.0},"95":{"tf":1.7320508075688772},"96":{"tf":1.7320508075688772},"99":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"41":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"102":{"tf":1.0},"103":{"tf":1.0}}},"df":0,"docs":{}}},"df":8,"docs":{"102":{"tf":4.795831523312719},"103":{"tf":2.0},"104":{"tf":1.4142135623730951},"107":{"tf":2.8284271247461903},"108":{"tf":1.7320508075688772},"22":{"tf":1.0},"33":{"tf":1.0},"41":{"tf":1.0}},"i":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}},"n":{"a":{"df":0,"docs":{},"m":{"df":3,"docs":{"107":{"tf":1.4142135623730951},"41":{"tf":1.0},"42":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"103":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}}}},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"22":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"a":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"33":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"对":{"df":0,"docs":{},"象":{"df":0,"docs":{},"的":{"df":0,"docs":{},"主":{"df":0,"docs":{},"键":{"df":0,"docs":{},"是":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"108":{"tf":1.0}}}}}}}}}}}}},"i":{"df":0,"docs":{},"z":{"df":1,"docs":{"64":{"tf":1.0}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"73":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"73":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"u":{"df":13,"docs":{"100":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.4142135623730951},"105":{"tf":2.449489742783178},"13":{"tf":1.0},"24":{"tf":2.0},"36":{"tf":2.449489742783178},"37":{"tf":2.449489742783178},"43":{"tf":1.7320508075688772},"46":{"tf":1.7320508075688772},"49":{"tf":2.0},"9":{"tf":1.4142135623730951},"99":{"tf":1.7320508075688772}},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"13":{"tf":1.0},"64":{"tf":1.0}}}}}}},"df":0,"docs":{}},".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"105":{"tf":1.0}}}}},"df":0,"docs":{}},"x":{"df":1,"docs":{"26":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"1":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"50":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":4,"docs":{"26":{"tf":1.0},"38":{"tf":1.0},"45":{"tf":1.0},"50":{"tf":1.4142135623730951}}},"2":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"50":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":4,"docs":{"26":{"tf":1.0},"38":{"tf":1.0},"45":{"tf":1.0},"50":{"tf":1.0}}},"3":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"50":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"38":{"tf":1.0},"50":{"tf":1.0}}},":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"0":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"c":{"2":{"8":{"2":{"2":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{"3":{"3":{"9":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"99":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"105":{"tf":1.4142135623730951}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"99":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"_":{"d":{"b":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}},"e":{"d":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"26":{"tf":1.0}}}}}},"2":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"26":{"tf":1.0}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{".":{"0":{"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"64":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"72":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"s":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"72":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"c":{"<":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"73":{"tf":1.0}}}}}}}}}}}},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"48":{"tf":1.0}}}}},"t":{"df":2,"docs":{"25":{"tf":1.4142135623730951},"4":{"tf":1.0}}},"u":{"df":0,"docs":{},"s":{"df":2,"docs":{"22":{"tf":1.0},"33":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":8,"docs":{"105":{"tf":1.0},"30":{"tf":1.0},"5":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"16":{"tf":1.0}}}}}}}}},"w":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}},"r":{"df":1,"docs":{"27":{"tf":1.0}},"p":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":2,"docs":{"91":{"tf":1.0},"92":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"90":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{")":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"(":{"(":{"[":{"0":{"df":2,"docs":{"91":{"tf":1.0},"92":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"{":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":6,"docs":{"17":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"85":{"tf":1.4142135623730951},"90":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0}},"提":{"df":0,"docs":{},"供":{"df":0,"docs":{},"了":{"df":0,"docs":{},"两":{"df":0,"docs":{},"个":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},",":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"和":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"90":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}}}}}},"s":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"b":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"<":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"<":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{")":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"95":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"<":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"95":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"p":{"a":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"96":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":6,"docs":{"3":{"tf":1.4142135623730951},"7":{"tf":1.0},"85":{"tf":2.0},"90":{"tf":1.0},"94":{"tf":1.4142135623730951},"97":{"tf":1.0}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"85":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"58":{"tf":1.0}}},"l":{"d":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"x":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"26":{"tf":1.0}}}}}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"10":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"breadcrumbs":{"root":{"0":{"df":5,"docs":{"36":{"tf":1.0},"43":{"tf":1.0},"51":{"tf":1.0},"91":{"tf":1.7320508075688772},"92":{"tf":1.7320508075688772}}},"1":{")":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"0":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},".":{"0":{"df":8,"docs":{"105":{"tf":1.0},"30":{"tf":1.0},"5":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0}}},"df":0,"docs":{}},"0":{"0":{"0":{"0":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}},"df":2,"docs":{"49":{"tf":1.4142135623730951},"61":{"tf":2.0}}},"df":7,"docs":{"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.0},"47":{"tf":1.4142135623730951},"48":{"tf":1.0},"5":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"2":{"3":{"4":{"df":1,"docs":{"18":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"7":{"7":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"8":{"0":{"df":1,"docs":{"27":{"tf":1.0}}},"3":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":4,"docs":{"102":{"tf":4.242640687119285},"103":{"tf":1.0},"43":{"tf":1.4142135623730951},"50":{"tf":1.0}}},"2":{"0":{"df":1,"docs":{"5":{"tf":1.0}}},"3":{"4":{"a":{"\\"":{".":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"57":{"tf":1.0}},"e":{"(":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"103":{"tf":1.0},"50":{"tf":1.0}}},"3":{"0":{"df":2,"docs":{"36":{"tf":1.4142135623730951},"50":{"tf":1.0}}},"df":3,"docs":{"103":{"tf":1.0},"50":{"tf":1.0},"62":{"tf":1.0}}},"4":{".":{"0":{"df":1,"docs":{"3":{"tf":2.0}}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"20":{"tf":1.0}}},"4":{"df":4,"docs":{"54":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"103":{"tf":1.0}}},"5":{"0":{"0":{"df":1,"docs":{"57":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":6,"docs":{"37":{"tf":1.0},"38":{"tf":1.0},"47":{"tf":1.4142135623730951},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0}}},"6":{"0":{"df":1,"docs":{"50":{"tf":1.0}}},"4":{"df":1,"docs":{"99":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"8":{"0":{"0":{"0":{")":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"91":{"tf":1.0},"92":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":2,"docs":{"105":{"tf":1.4142135623730951},"46":{"tf":1.0}}}}},"df":2,"docs":{"105":{"tf":1.0},"43":{"tf":1.0}}},"a":{"(":{"a":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"b":{":":{":":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":1,"docs":{"95":{"tf":1.0}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"96":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}},"{":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"b":{"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.0}}},"df":0,"docs":{}}}},"df":6,"docs":{"3":{"tf":1.4142135623730951},"85":{"tf":1.4142135623730951},"94":{"tf":2.0},"95":{"tf":1.0},"96":{"tf":1.0},"97":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"94":{"tf":1.0}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"46":{"tf":1.7320508075688772},"49":{"tf":1.0}}}},"df":0,"docs":{}}}},"d":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"4":{"tf":1.0}}}}}}},"a":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"45":{"tf":2.0}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"|":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"df":0,"docs":{},"z":{"df":1,"docs":{"78":{"tf":1.7320508075688772}}}}}},"d":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{":":{":":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"(":{"\\"":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"\\"":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":7,"docs":{"106":{"tf":2.0},"107":{"tf":1.0},"108":{"tf":1.0},"59":{"tf":2.23606797749979},"79":{"tf":1.7320508075688772},"80":{"tf":2.449489742783178},"81":{"tf":2.0}},"t":{"df":0,"docs":{},"r":{"a":{"c":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"p":{"df":2,"docs":{"87":{"tf":1.0},"88":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"18":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"r":{"c":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"x":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"a":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"29":{"tf":1.0}}},"df":0,"docs":{}},"g":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"29":{"tf":1.0},"37":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"df":1,"docs":{"24":{"tf":1.0}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"/":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"103":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{">":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":7,"docs":{"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"c":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"43":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"43":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"72":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"17":{"tf":1.0},"91":{"tf":1.0}}}}}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"_":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"b":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"96":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"{":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"95":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"87":{"tf":1.0}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"88":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":2,"docs":{"87":{"tf":1.0},"88":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{")":{".":{"a":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"|":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"91":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"92":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":3,"docs":{"17":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":68,"docs":{"10":{"tf":2.0},"100":{"tf":1.4142135623730951},"103":{"tf":1.0},"105":{"tf":2.0},"107":{"tf":1.4142135623730951},"108":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"13":{"tf":1.4142135623730951},"15":{"tf":1.4142135623730951},"16":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"26":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"28":{"tf":2.0},"29":{"tf":1.4142135623730951},"30":{"tf":2.0},"31":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"34":{"tf":2.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"4":{"tf":1.4142135623730951},"41":{"tf":1.4142135623730951},"42":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"45":{"tf":2.0},"46":{"tf":1.4142135623730951},"47":{"tf":1.4142135623730951},"48":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"50":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"53":{"tf":1.4142135623730951},"54":{"tf":1.4142135623730951},"55":{"tf":2.0},"56":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"59":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"64":{"tf":1.4142135623730951},"65":{"tf":1.4142135623730951},"70":{"tf":2.0},"71":{"tf":1.7320508075688772},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"74":{"tf":1.4142135623730951},"75":{"tf":1.4142135623730951},"76":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951},"87":{"tf":1.4142135623730951},"88":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951},"91":{"tf":1.4142135623730951},"92":{"tf":1.4142135623730951},"95":{"tf":1.4142135623730951},"96":{"tf":1.4142135623730951},"99":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"d":{":":{":":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"103":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{":":{":":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":9,"docs":{"105":{"tf":1.4142135623730951},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0},"79":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":75,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.4142135623730951},"102":{"tf":1.0},"103":{"tf":1.4142135623730951},"104":{"tf":1.4142135623730951},"105":{"tf":2.23606797749979},"106":{"tf":1.0},"107":{"tf":1.7320508075688772},"108":{"tf":1.0},"11":{"tf":1.0},"13":{"tf":1.4142135623730951},"15":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":2.0},"23":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"27":{"tf":1.0},"29":{"tf":2.8284271247461903},"3":{"tf":2.0},"30":{"tf":1.7320508075688772},"31":{"tf":2.23606797749979},"33":{"tf":1.0},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772},"39":{"tf":1.0},"4":{"tf":1.4142135623730951},"41":{"tf":1.0},"42":{"tf":1.4142135623730951},"43":{"tf":1.0},"45":{"tf":1.4142135623730951},"46":{"tf":1.4142135623730951},"47":{"tf":1.4142135623730951},"48":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.4142135623730951},"50":{"tf":2.23606797749979},"51":{"tf":1.7320508075688772},"53":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.4142135623730951},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"66":{"tf":1.0},"67":{"tf":1.0},"68":{"tf":1.0},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.4142135623730951},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"77":{"tf":1.0},"79":{"tf":1.4142135623730951},"8":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.7320508075688772},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"85":{"tf":2.449489742783178},"90":{"tf":1.0},"91":{"tf":1.7320508075688772},"92":{"tf":1.7320508075688772},"94":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}},"t":{"(":{"\\"":{"/":{"df":0,"docs":{},"w":{"df":2,"docs":{"87":{"tf":1.0},"88":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"17":{"tf":1.7320508075688772}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"58":{"tf":1.0}}}}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":7,"docs":{"78":{"tf":1.0},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0}}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"102":{"tf":1.0},"17":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0}}}}},"df":0,"docs":{}},"x":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"85":{"tf":1.4142135623730951}}}}}},"b":{"(":{"b":{"df":1,"docs":{"32":{"tf":1.0}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":2,"docs":{"55":{"tf":1.0},"58":{"tf":1.0}}}}},"df":15,"docs":{"10":{"tf":1.0},"100":{"tf":1.0},"108":{"tf":1.4142135623730951},"11":{"tf":1.0},"12":{"tf":1.0},"19":{"tf":1.0},"28":{"tf":2.0},"32":{"tf":1.0},"34":{"tf":1.0},"4":{"tf":1.7320508075688772},"5":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"51":{"tf":1.7320508075688772}},"e":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"1":{"0":{"0":{"0":{"0":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"n":{"d":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"102":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"65":{"tf":1.0}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"<":{"\'":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":1,"docs":{"15":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"x":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"<":{"\'":{"df":1,"docs":{"76":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"c":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"11":{"tf":1.0}}}}}}},"c":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"a":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"79":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":5,"docs":{"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0}}}},"n":{"_":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"53":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":7,"docs":{"19":{"tf":1.4142135623730951},"28":{"tf":2.0},"32":{"tf":2.0},"62":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.4142135623730951},"9":{"tf":1.0}},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"x":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":3,"docs":{"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"49":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"l":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"64":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{":":{":":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"24":{"tf":1.0}}}}}}},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":2,"docs":{"29":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}},"e":{"\\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"(":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":6,"docs":{"26":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"65":{"tf":1.0},"79":{"tf":1.0}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":2,"docs":{"55":{"tf":1.0},"57":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"56":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":3,"docs":{"11":{"tf":1.0},"63":{"tf":1.0},"78":{"tf":1.0}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"11":{"tf":1.4142135623730951}},"和":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"45":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"t":{"(":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"105":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"105":{"tf":2.449489742783178}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":2.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"51":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"<":{"\'":{"_":{"df":9,"docs":{"102":{"tf":1.0},"103":{"tf":1.0},"105":{"tf":1.4142135623730951},"13":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"65":{"tf":1.0}}},"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":1,"docs":{"15":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":6,"docs":{"14":{"tf":1.7320508075688772},"15":{"tf":1.4142135623730951},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.4142135623730951},"19":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"28":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"33":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"i":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"65":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"64":{"tf":1.4142135623730951}}}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":69,"docs":{"10":{"tf":1.4142135623730951},"100":{"tf":1.4142135623730951},"105":{"tf":1.4142135623730951},"107":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"15":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.7320508075688772},"18":{"tf":1.4142135623730951},"19":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.4142135623730951},"26":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.4142135623730951},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.7320508075688772},"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":2.0},"56":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0},"79":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"87":{"tf":1.7320508075688772},"88":{"tf":1.7320508075688772},"9":{"tf":1.0},"91":{"tf":1.7320508075688772},"92":{"tf":1.7320508075688772},"95":{"tf":1.7320508075688772},"96":{"tf":1.7320508075688772},"99":{"tf":1.0}},"s":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":1,"docs":{"81":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"x":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{"\\"":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{":":{":":{"<":{"d":{"b":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{">":{"(":{")":{"?":{".":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"15":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"15":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{":":{":":{"<":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"45":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"d":{":":{":":{"<":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"103":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"a":{"d":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{">":{"(":{")":{".":{"0":{"df":1,"docs":{"65":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"<":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"df":1,"docs":{"102":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{"\\"":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"_":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"_":{"a":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"(":{")":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"\\"":{"a":{"\\"":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"\\"":{")":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"\\"":{"c":{"\\"":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":15,"docs":{"102":{"tf":1.0},"103":{"tf":1.4142135623730951},"13":{"tf":1.0},"15":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"45":{"tf":1.4142135623730951},"68":{"tf":1.0},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"51":{"tf":1.7320508075688772}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"105":{"tf":2.23606797749979}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"<":{"df":0,"docs":{},"i":{"3":{"2":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"d":{"(":{"d":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"t":{"a":{"(":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"103":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"b":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"16":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"s":{"3":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"16":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":2,"docs":{"13":{"tf":1.0},"17":{"tf":1.0}},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"103":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"c":{"3":{"3":{"3":{"9":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"c":{"2":{"8":{"2":{"2":{"(":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"24":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{"3":{"3":{"9":{"(":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"c":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{".":{"0":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":1,"docs":{"24":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"b":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"16":{"tf":1.0}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":2.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}}}}},"df":3,"docs":{"19":{"tf":1.0},"32":{"tf":2.0},"63":{"tf":1.0}},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"62":{"tf":1.0}}}},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"df":4,"docs":{"22":{"tf":1.0},"23":{"tf":1.0},"37":{"tf":1.4142135623730951},"70":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"55":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"3":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"78":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"32":{"tf":2.449489742783178}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"32":{"tf":1.7320508075688772}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{",":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"16":{"tf":1.0},"23":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":5,"docs":{"17":{"tf":1.0},"22":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"87":{"tf":1.0},"88":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"65":{"tf":1.0}}}}},"q":{"df":1,"docs":{"45":{"tf":1.4142135623730951}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"108":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":2.6457513110645907},"38":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"a":{"c":{"df":3,"docs":{"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"23":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"100":{"tf":1.0},"26":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":14,"docs":{"10":{"tf":2.6457513110645907},"107":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"19":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"26":{"tf":1.0},"30":{"tf":1.4142135623730951},"33":{"tf":1.0},"41":{"tf":1.0},"45":{"tf":1.0},"65":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"100":{"tf":1.7320508075688772},"26":{"tf":1.7320508075688772}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":3,"docs":{"19":{"tf":2.0},"53":{"tf":1.0},"57":{"tf":1.0}}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"105":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"105":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"o":{"c":{"df":1,"docs":{"79":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"\'":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"c":{"2":{"8":{"2":{"2":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"24":{"tf":1.0}}}}}}},"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"24":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{"3":{"3":{"9":{"(":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"24":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"(":{"\\"":{"c":{"df":0,"docs":{},"o":{"d":{"df":6,"docs":{"20":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":2,"docs":{"53":{"tf":1.0},"57":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"55":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"55":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"a":{"d":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"n":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":9,"docs":{"20":{"tf":1.0},"28":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.0},"57":{"tf":2.23606797749979},"58":{"tf":2.0},"63":{"tf":1.0}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"47":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"27":{"tf":1.0}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"51":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":17,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.4142135623730951},"22":{"tf":1.0},"23":{"tf":1.0},"30":{"tf":1.4142135623730951},"5":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"91":{"tf":1.4142135623730951},"92":{"tf":1.0},"95":{"tf":1.4142135623730951},"96":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":13,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"22":{"tf":1.0},"30":{"tf":1.4142135623730951},"5":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"91":{"tf":1.4142135623730951},"95":{"tf":1.0},"96":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{")":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":5,"docs":{"17":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"n":{"d":{"df":1,"docs":{"51":{"tf":2.449489742783178}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":10,"docs":{"27":{"tf":2.0},"28":{"tf":2.6457513110645907},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"32":{"tf":1.7320508075688772},"37":{"tf":1.0},"45":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"65":{"tf":1.0}}}},"v":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}}},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}}}}},"q":{"df":3,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"65":{"tf":1.0}},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"d":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"1":{"0":{"0":{"df":1,"docs":{"46":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":1,"docs":{"46":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"r":{"(":{"\\"":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"i":{"d":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"\\"":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":2,"docs":{"45":{"tf":1.0},"46":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":1,"docs":{"57":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"\\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"\\"":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"53":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"!":{"(":{"\\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"49":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"99":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}}},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"55":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"55":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"20":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"54":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"\\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}}}}}}}}}}}}},"df":0,"docs":{}},":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"58":{"tf":1.0}}}}}}}}}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"55":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":7,"docs":{"103":{"tf":1.0},"104":{"tf":1.4142135623730951},"20":{"tf":1.0},"53":{"tf":1.4142135623730951},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"57":{"tf":1.0}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":3,"docs":{"54":{"tf":1.4142135623730951},"55":{"tf":2.0},"58":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"为":{"&":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}}}}}}}}}}}}}},"很":{"df":0,"docs":{},"麻":{"df":0,"docs":{},"烦":{"df":0,"docs":{},"。":{"df":0,"docs":{},"这":{"df":0,"docs":{},"就":{"df":0,"docs":{},"是":{"df":0,"docs":{},"为":{"df":0,"docs":{},"什":{"df":0,"docs":{},"么":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"54":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"1":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"23":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"23":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"26":{"tf":1.4142135623730951},"56":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"72":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":2,"docs":{"17":{"tf":1.0},"74":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"46":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"55":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"56":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"_":{"df":1,"docs":{"57":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":1,"docs":{"54":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":13,"docs":{"53":{"tf":1.0},"55":{"tf":2.0},"57":{"tf":1.0},"67":{"tf":1.7320508075688772},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"c":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"<":{"\'":{"_":{"df":8,"docs":{"68":{"tf":1.0},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"n":{"df":68,"docs":{"10":{"tf":1.4142135623730951},"100":{"tf":1.4142135623730951},"105":{"tf":1.4142135623730951},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"15":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.7320508075688772},"18":{"tf":1.4142135623730951},"19":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.4142135623730951},"26":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.4142135623730951},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.7320508075688772},"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":2.0},"56":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0},"79":{"tf":1.0},"87":{"tf":1.7320508075688772},"88":{"tf":1.7320508075688772},"9":{"tf":1.0},"91":{"tf":1.7320508075688772},"92":{"tf":1.7320508075688772},"95":{"tf":1.7320508075688772},"96":{"tf":1.7320508075688772},"99":{"tf":1.0}}}}}}}},"f":{"3":{"2":{"df":2,"docs":{"29":{"tf":2.8284271247461903},"31":{"tf":2.449489742783178}}},"df":0,"docs":{}},"6":{"4":{"df":2,"docs":{"33":{"tf":1.7320508075688772},"4":{"tf":1.0}}},"df":0,"docs":{}},"a":{"c":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"103":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":1,"docs":{"65":{"tf":1.4142135623730951}}}}},"df":1,"docs":{"63":{"tf":1.0}},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"是":{"df":0,"docs":{},"一":{"df":0,"docs":{},"个":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"106":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"的":{"df":0,"docs":{},"所":{"df":0,"docs":{},"有":{"df":0,"docs":{},"功":{"df":0,"docs":{},"能":{"df":0,"docs":{},",":{"df":0,"docs":{},"但":{"df":0,"docs":{},"需":{"df":0,"docs":{},"要":{"df":0,"docs":{},"对":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"106":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}}}}}}}},"df":3,"docs":{"106":{"tf":1.7320508075688772},"107":{"tf":1.0},"108":{"tf":1.0}}}}},"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"103":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"102":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":3,"docs":{"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"37":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"1":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"22":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"10":{"tf":2.0},"34":{"tf":2.0}}},"2":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"22":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"10":{"tf":2.0},"34":{"tf":2.0}}},"df":3,"docs":{"105":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"75":{"tf":1.0}}},"df":0,"docs":{}}},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"56":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"m":{"df":1,"docs":{"27":{"tf":1.0}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"91":{"tf":1.0},"92":{"tf":1.0}},",":{"df":0,"docs":{},"或":{"df":0,"docs":{},"者":{"df":0,"docs":{},"直":{"df":0,"docs":{},"接":{"df":0,"docs":{},"调":{"df":0,"docs":{},"用":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"90":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}}}},"n":{"d":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"b":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"107":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"a":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"107":{"tf":1.0}},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"107":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"107":{"tf":1.0}},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"107":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"107":{"tf":1.0}}},"df":0,"docs":{}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"108":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":1,"docs":{"55":{"tf":1.4142135623730951}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":7,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"16":{"tf":1.0},"30":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{")":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":1,"docs":{"51":{"tf":2.0}}}}}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"32":{"tf":1.0}}}}}}},"df":0,"docs":{}},"n":{"df":61,"docs":{"102":{"tf":1.0},"103":{"tf":1.4142135623730951},"104":{"tf":1.4142135623730951},"105":{"tf":2.23606797749979},"107":{"tf":1.7320508075688772},"108":{"tf":1.0},"11":{"tf":1.0},"13":{"tf":2.0},"15":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":2.0},"23":{"tf":1.4142135623730951},"24":{"tf":2.449489742783178},"26":{"tf":1.4142135623730951},"28":{"tf":1.0},"29":{"tf":2.449489742783178},"30":{"tf":1.4142135623730951},"31":{"tf":2.0},"33":{"tf":1.0},"36":{"tf":2.0},"37":{"tf":2.0},"38":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.4142135623730951},"43":{"tf":1.0},"45":{"tf":2.0},"46":{"tf":1.7320508075688772},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.7320508075688772},"5":{"tf":1.4142135623730951},"50":{"tf":1.7320508075688772},"51":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.4142135623730951},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"65":{"tf":1.0},"68":{"tf":1.0},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0},"79":{"tf":1.4142135623730951},"91":{"tf":1.4142135623730951},"92":{"tf":1.7320508075688772},"95":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.4142135623730951}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"!":{"(":{"\\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"d":{"\\"":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"103":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"(":{"df":1,"docs":{"28":{"tf":1.0}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":2,"docs":{"24":{"tf":1.0},"26":{"tf":1.0}}}}},"df":0,"docs":{}}},"<":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"c":{"2":{"8":{"2":{"2":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"u":{"df":1,"docs":{"25":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":1.0}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"t":{">":{">":{"(":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"26":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"u":{"df":1,"docs":{"25":{"tf":1.0}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"c":{"<":{"df":0,"docs":{},"u":{"df":1,"docs":{"25":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{":":{":":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"76":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{"0":{".":{".":{"1":{"0":{"df":2,"docs":{"23":{"tf":1.0},"92":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{"0":{".":{".":{"2":{"0":{"df":1,"docs":{"23":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"{":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"23":{"tf":1.0},"92":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"46":{"tf":1.0},"79":{"tf":1.0}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"88":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"(":{"c":{"a":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"(":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"x":{"_":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"50":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"50":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":2,"docs":{"11":{"tf":1.0},"64":{"tf":1.4142135623730951}}}}}}},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"10":{"tf":2.0},"34":{"tf":2.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"36":{"tf":1.0},"38":{"tf":1.0}}}}}}},"df":3,"docs":{"36":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"43":{"tf":1.0}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"d":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"24":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"26":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"s":{"c":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"107":{"tf":1.7320508075688772},"108":{"tf":1.0}}}}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"30":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"32":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"45":{"tf":1.4142135623730951},"46":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"12":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"107":{"tf":1.0}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"29":{"tf":1.4142135623730951}},"e":{"=":{"\\"":{"a":{"a":{"a":{"df":1,"docs":{"27":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"9":{"tf":1.0}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"49":{"tf":1.0}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"48":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"47":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"65":{"tf":2.0}}}}}},"/":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"_":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"81":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"7":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"/":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"97":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"107":{"tf":1.0}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"89":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":1,"docs":{"93":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"87":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"90":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":40,"docs":{"0":{"tf":2.23606797749979},"1":{"tf":2.0},"101":{"tf":1.0},"105":{"tf":1.4142135623730951},"106":{"tf":1.0},"11":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"29":{"tf":2.0},"3":{"tf":2.0},"30":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":2.23606797749979},"41":{"tf":1.0},"50":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"52":{"tf":1.4142135623730951},"54":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.7320508075688772},"72":{"tf":1.7320508075688772},"77":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951},"80":{"tf":1.0},"81":{"tf":1.4142135623730951},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"85":{"tf":2.449489742783178},"9":{"tf":1.4142135623730951},"90":{"tf":1.4142135623730951},"94":{"tf":1.0},"99":{"tf":1.0}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"95":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"95":{"tf":1.0}}}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"94":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{":":{":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"&":{"*":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{")":{")":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":1,"docs":{"96":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"仅":{"df":0,"docs":{},"支":{"df":0,"docs":{},"持":{"df":0,"docs":{},"添":{"df":0,"docs":{},"加":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"105":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}}},"可":{"df":0,"docs":{},"以":{"df":0,"docs":{},"完":{"df":0,"docs":{},"全":{"df":0,"docs":{},"支":{"df":0,"docs":{},"持":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":1,"docs":{"106":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}}}},"用":{"df":0,"docs":{},"于":{"df":0,"docs":{},"执":{"df":0,"docs":{},"行":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"和":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"90":{"tf":1.0}}}}}}}}}}}}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"u":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"45":{"tf":2.23606797749979},"46":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"45":{"tf":1.7320508075688772}}}}}}},"h":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"100":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"17":{"tf":1.0},"18":{"tf":2.449489742783178}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{":":{":":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"_":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":2,"docs":{"18":{"tf":2.23606797749979},"50":{"tf":1.0}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.0}}}}}}}}},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":5,"docs":{"107":{"tf":1.0},"7":{"tf":1.0},"89":{"tf":1.0},"93":{"tf":1.0},"97":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"i":{"3":{"2":{":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":32,"docs":{"10":{"tf":2.0},"100":{"tf":1.4142135623730951},"108":{"tf":1.7320508075688772},"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"13":{"tf":1.0},"16":{"tf":1.0},"19":{"tf":2.0},"22":{"tf":2.0},"23":{"tf":1.7320508075688772},"32":{"tf":2.449489742783178},"33":{"tf":1.0},"34":{"tf":2.0},"36":{"tf":2.6457513110645907},"37":{"tf":3.605551275463989},"38":{"tf":2.0},"4":{"tf":2.0},"41":{"tf":1.0},"43":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"46":{"tf":2.449489742783178},"49":{"tf":2.23606797749979},"50":{"tf":1.7320508075688772},"51":{"tf":1.4142135623730951},"64":{"tf":1.4142135623730951},"65":{"tf":1.7320508075688772},"87":{"tf":1.0},"88":{"tf":1.0},"9":{"tf":1.7320508075688772},"92":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0}}},"df":0,"docs":{}},"6":{"4":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"d":{"df":6,"docs":{"102":{"tf":4.58257569495584},"103":{"tf":2.0},"107":{"tf":3.4641016151377544},"108":{"tf":1.4142135623730951},"13":{"tf":1.7320508075688772},"79":{"tf":1.4142135623730951}},"和":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"107":{"tf":1.0}},"e":{"df":0,"docs":{},"查":{"df":0,"docs":{},"找":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"对":{"df":0,"docs":{},"象":{"df":0,"docs":{},",":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"107":{"tf":1.0}}}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}},"查":{"df":0,"docs":{},"找":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"对":{"df":0,"docs":{},"象":{"df":0,"docs":{},",":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"107":{"tf":1.4142135623730951}}}}}}}}}}}}},",":{"df":0,"docs":{},"并":{"df":0,"docs":{},"且":{"df":0,"docs":{},"请":{"df":0,"docs":{},"求":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"对":{"df":0,"docs":{},"象":{"df":0,"docs":{},"的":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"107":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}}}}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":55,"docs":{"102":{"tf":1.0},"103":{"tf":1.4142135623730951},"104":{"tf":1.4142135623730951},"105":{"tf":2.23606797749979},"107":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"13":{"tf":2.0},"15":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":2.0},"23":{"tf":2.0},"24":{"tf":2.0},"25":{"tf":1.0},"26":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"33":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.4142135623730951},"45":{"tf":2.0},"46":{"tf":1.7320508075688772},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.7320508075688772},"5":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.4142135623730951},"56":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.7320508075688772},"99":{"tf":1.0}},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"58":{"tf":1.4142135623730951},"70":{"tf":1.0}}}}}}}}}},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"_":{"df":0,"docs":{},"w":{"df":1,"docs":{"96":{"tf":1.0}}}},"df":1,"docs":{"95":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{">":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":1,"docs":{"91":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"o":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"75":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"57":{"tf":1.4142135623730951},"75":{"tf":1.0}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"47":{"tf":1.0},"48":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"12":{"tf":1.0}}}},"df":0,"docs":{}}},"df":3,"docs":{"20":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.4142135623730951}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"12":{"tf":1.0},"33":{"tf":1.7320508075688772},"34":{"tf":1.7320508075688772},"38":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"34":{"tf":1.7320508075688772}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"<":{"df":0,"docs":{},"i":{"3":{"2":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"99":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}}}}},"t":{"df":1,"docs":{"61":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"43":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"f":{"a":{"c":{"df":4,"docs":{"22":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.7320508075688772},"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"o":{":":{":":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"25":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"p":{"df":1,"docs":{"47":{"tf":1.4142135623730951}}},"s":{"_":{"a":{"d":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"(":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":1,"docs":{"65":{"tf":1.0}}}}},"df":0,"docs":{}},"df":1,"docs":{"65":{"tf":2.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"d":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"(":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"65":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"103":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"j":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.7320508075688772}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{".":{"a":{"df":1,"docs":{"108":{"tf":1.0}}},"df":0,"docs":{}},"df":7,"docs":{"103":{"tf":1.0},"104":{"tf":1.4142135623730951},"107":{"tf":1.7320508075688772},"108":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"79":{"tf":2.0}},"s":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{")":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"103":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":2.449489742783178}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"55":{"tf":1.0}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"(":{"5":{"df":1,"docs":{"63":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"5":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"o":{"a":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"79":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"i":{"d":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"<":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"u":{"6":{"4":{"df":1,"docs":{"103":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}},"c":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"28":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":2.0}}}}}}},"t":{"df":3,"docs":{"53":{"tf":1.0},"55":{"tf":1.0},"57":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"82":{"tf":1.7320508075688772},"84":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"42":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"19":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"43":{"tf":1.0}}}}}},"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"(":{"a":{"df":0,"docs":{},"r":{"c":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"103":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"|":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":2,"docs":{"20":{"tf":1.0},"54":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"58":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"k":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"103":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"c":{"df":0,"docs":{},"h":{"df":4,"docs":{"105":{"tf":1.0},"28":{"tf":1.0},"55":{"tf":1.0},"57":{"tf":1.0}}}},"df":0,"docs":{}},"x":{"_":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"=":{"3":{"0":{"df":1,"docs":{"50":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"6":{"0":{"df":1,"docs":{"50":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":2,"docs":{"47":{"tf":1.0},"48":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"21":{"tf":1.7320508075688772},"22":{"tf":1.0},"23":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":3,"docs":{"53":{"tf":1.0},"55":{"tf":1.0},"57":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"29":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"i":{"d":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"68":{"tf":1.0}},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"68":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"68":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"o":{"d":{"df":1,"docs":{"28":{"tf":1.4142135623730951}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"57":{"tf":1.4142135623730951}}}},"v":{"df":0,"docs":{},"e":{"df":3,"docs":{"17":{"tf":1.0},"51":{"tf":1.0},"91":{"tf":1.0}}},"i":{"df":1,"docs":{"22":{"tf":1.0}},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.7320508075688772}}}}}}},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"22":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"18":{"tf":1.4142135623730951}},"e":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"=":{"df":0,"docs":{},"n":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"t":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"33":{"tf":1.4142135623730951},"4":{"tf":1.0},"42":{"tf":1.4142135623730951},"5":{"tf":1.0}}}},"df":3,"docs":{"17":{"tf":1.0},"43":{"tf":1.0},"51":{"tf":1.7320508075688772}}}},"v":{"df":1,"docs":{"100":{"tf":2.0}}},"y":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"36":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"65":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"55":{"tf":1.0}}}}}}}}}}}}}}}}}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"55":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"55":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"55":{"tf":1.7320508075688772}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"38":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"a":{"c":{"df":2,"docs":{"30":{"tf":1.7320508075688772},"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"53":{"tf":1.0},"57":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{"d":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":7,"docs":{"70":{"tf":2.0},"71":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"74":{"tf":1.4142135623730951},"75":{"tf":1.4142135623730951},"76":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"b":{"df":0,"docs":{},"j":{"(":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":5,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"19":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"65":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"(":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":4,"docs":{"13":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"30":{"tf":1.7320508075688772},"9":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"12":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":2,"docs":{"30":{"tf":1.0},"91":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"1":{"0":{"0":{"df":1,"docs":{"49":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":1,"docs":{"49":{"tf":2.0}}},"df":0,"docs":{}},"u":{"df":1,"docs":{"100":{"tf":1.0}}}}},"df":0,"docs":{}}}},"n":{"+":{"1":{"df":4,"docs":{"101":{"tf":1.7320508075688772},"102":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"102":{"tf":1.0},"103":{"tf":1.0}}}}}}},"df":0,"docs":{}},".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":1,"docs":{"103":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},":":{"\\"":{"\\"":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":9,"docs":{"102":{"tf":4.58257569495584},"103":{"tf":2.0},"13":{"tf":1.0},"17":{"tf":1.0},"26":{"tf":1.0},"29":{"tf":1.4142135623730951},"30":{"tf":2.0},"47":{"tf":1.4142135623730951},"48":{"tf":1.0}},"和":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"29":{"tf":1.0}},"属":{"df":0,"docs":{},"性":{"df":0,"docs":{},"同":{"df":0,"docs":{},"时":{"df":0,"docs":{},"存":{"df":0,"docs":{},"在":{"df":0,"docs":{},"时":{"df":0,"docs":{},",":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}},"存":{"df":0,"docs":{},"在":{"df":0,"docs":{},"时":{"df":0,"docs":{},",":{"df":0,"docs":{},"转":{"df":0,"docs":{},"换":{"df":0,"docs":{},"为":{"df":0,"docs":{},"驼":{"df":0,"docs":{},"峰":{"df":0,"docs":{},"命":{"df":0,"docs":{},"名":{"df":0,"docs":{},"后":{"df":0,"docs":{},"的":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}}}}}}}},"df":4,"docs":{"102":{"tf":1.0},"47":{"tf":3.0},"49":{"tf":2.0},"51":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}},"e":{"d":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"108":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"w":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"46":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"df":1,"docs":{"49":{"tf":1.0}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"45":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"x":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"(":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"68":{"tf":1.0},"70":{"tf":1.4142135623730951},"73":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":5,"docs":{"71":{"tf":1.0},"72":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0}}}}},"df":0,"docs":{}},"df":1,"docs":{"68":{"tf":1.7320508075688772}}}}}},"df":8,"docs":{"68":{"tf":1.4142135623730951},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"<":{"\'":{"_":{"df":1,"docs":{"74":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"m":{"df":0,"docs":{},"i":{"d":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"<":{"\'":{"_":{"df":1,"docs":{"68":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"<":{"\'":{"_":{"df":1,"docs":{"72":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"<":{"\'":{"_":{"df":1,"docs":{"71":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"<":{"\'":{"_":{"df":1,"docs":{"70":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"<":{"\'":{"_":{"df":1,"docs":{"75":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"e":{"<":{"\'":{"_":{"df":1,"docs":{"76":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"\'":{"_":{"df":1,"docs":{"73":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"55":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}}}},"u":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"51":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"o":{"b":{"df":0,"docs":{},"j":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"19":{"tf":1.0},"30":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"19":{"tf":1.4142135623730951}},"e":{"c":{"df":0,"docs":{},"t":{"(":{"c":{"a":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"(":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"x":{"_":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"50":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":47,"docs":{"10":{"tf":1.0},"102":{"tf":1.0},"103":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"13":{"tf":2.23606797749979},"14":{"tf":1.0},"15":{"tf":1.4142135623730951},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"21":{"tf":1.0},"22":{"tf":3.0},"23":{"tf":1.0},"24":{"tf":1.4142135623730951},"25":{"tf":1.0},"26":{"tf":1.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"36":{"tf":1.0},"37":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0}}}},"df":0,"docs":{}}}},"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"2":{"0":{"2":{"1":{"df":1,"docs":{"72":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"k":{"(":{"\\"":{"2":{"3":{"4":{"a":{"df":3,"docs":{"20":{"tf":1.0},"54":{"tf":1.0},"56":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{"df":0,"docs":{},"i":{"d":{")":{"?":{".":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"102":{"tf":1.0}}}},"df":1,"docs":{"57":{"tf":1.4142135623730951}}},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"x":{":":{":":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"a":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"103":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{")":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"b":{"df":1,"docs":{"99":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},":":{":":{"<":{"_":{"df":3,"docs":{"17":{"tf":1.0},"51":{"tf":1.0},"91":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"45":{"tf":1.0},"46":{"tf":1.0},"49":{"tf":1.0}}},"n":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"55":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":2,"docs":{"18":{"tf":1.0},"27":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"83":{"tf":2.0}}}}}}}}}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"74":{"tf":1.0}},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"74":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"74":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"3":{"2":{"df":1,"docs":{"51":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":4,"docs":{"103":{"tf":1.0},"17":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"79":{"tf":1.0}}}}},"t":{"df":5,"docs":{"10":{"tf":1.4142135623730951},"25":{"tf":1.0},"26":{"tf":1.0},"34":{"tf":1.4142135623730951},"4":{"tf":1.0}}},"u":{"df":1,"docs":{"26":{"tf":1.0}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}},"e":{"d":{"2":{"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"26":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"t":{"df":1,"docs":{"26":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"56":{"tf":1.0}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"10":{"tf":1.7320508075688772}}}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"18":{"tf":1.0},"57":{"tf":1.0}}}}}}}}}},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"10":{"tf":1.0},"34":{"tf":1.0}},"s":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951}}}}}}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"28":{"tf":1.0}}}}},"s":{"df":5,"docs":{"20":{"tf":1.0},"54":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.0},"72":{"tf":1.0}},"e":{"(":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"99":{"tf":1.0}}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"72":{"tf":1.4142135623730951}}}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":5,"docs":{"20":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"55":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"20":{"tf":1.0},"54":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}}}}}}}}},"t":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":4,"docs":{"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"65":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"42":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"53":{"tf":1.0},"55":{"tf":1.0},"57":{"tf":1.0}}}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"96":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":1.7320508075688772}}}}}}}},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"87":{"tf":1.0}}}}}},"{":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"88":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":5,"docs":{"85":{"tf":1.4142135623730951},"86":{"tf":1.7320508075688772},"87":{"tf":1.4142135623730951},"88":{"tf":1.4142135623730951},"89":{"tf":1.0}}}},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"102":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.7320508075688772}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"104":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"s":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"70":{"tf":1.0},"71":{"tf":1.7320508075688772}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":2,"docs":{"18":{"tf":1.0},"57":{"tf":1.0}}}}}}},"i":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"50":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"80":{"tf":1.0}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"106":{"tf":1.0},"28":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"b":{"df":11,"docs":{"10":{"tf":1.7320508075688772},"13":{"tf":1.0},"17":{"tf":1.0},"26":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.7320508075688772},"32":{"tf":2.449489742783178},"34":{"tf":1.7320508075688772},"49":{"tf":1.0},"55":{"tf":1.4142135623730951},"79":{"tf":1.0}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":43,"docs":{"102":{"tf":1.0},"103":{"tf":1.0},"105":{"tf":1.4142135623730951},"107":{"tf":1.4142135623730951},"108":{"tf":1.4142135623730951},"15":{"tf":1.4142135623730951},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.4142135623730951},"19":{"tf":2.23606797749979},"20":{"tf":1.4142135623730951},"23":{"tf":1.0},"24":{"tf":1.7320508075688772},"30":{"tf":2.0},"36":{"tf":1.4142135623730951},"4":{"tf":1.7320508075688772},"41":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.4142135623730951},"47":{"tf":1.4142135623730951},"48":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951},"5":{"tf":1.7320508075688772},"50":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"53":{"tf":1.4142135623730951},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"59":{"tf":1.4142135623730951},"61":{"tf":1.0},"62":{"tf":1.4142135623730951},"63":{"tf":1.4142135623730951},"64":{"tf":1.4142135623730951},"72":{"tf":2.0},"73":{"tf":1.0},"74":{"tf":1.0},"79":{"tf":2.449489742783178},"87":{"tf":1.0},"88":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0}}},"y":{"(":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.0}}}}}}}}}}}},":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":2,"docs":{"22":{"tf":1.0},"23":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":2,"docs":{"91":{"tf":1.4142135623730951},"92":{"tf":1.4142135623730951}}}}}}}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"u":{"df":3,"docs":{"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"33":{"tf":1.0}}}}},"df":0,"docs":{},"w":{"df":1,"docs":{"72":{"tf":1.0}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"55":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"df":1,"docs":{"5":{"tf":1.0}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"79":{"tf":1.0}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"=":{"df":0,"docs":{},"r":{"df":1,"docs":{"47":{"tf":1.0}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{":":{":":{"<":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"a":{"c":{"df":1,"docs":{"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":2.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"y":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"27":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.4142135623730951}},"e":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{":":{":":{"a":{"df":1,"docs":{"28":{"tf":1.0}}},"b":{"df":1,"docs":{"28":{"tf":1.0}}},"c":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},">":{"df":0,"docs":{},"以":{"df":0,"docs":{},"及":{"df":0,"docs":{},"相":{"df":0,"docs":{},"反":{"df":0,"docs":{},"的":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"<":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":1,"docs":{"28":{"tf":1.7320508075688772}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"28":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"28":{"tf":1.4142135623730951}}}}}}}}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"17":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"80":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":6,"docs":{"78":{"tf":1.0},"79":{"tf":1.0},"80":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0}}}}}}}}}},"q":{"df":1,"docs":{"96":{"tf":1.0}},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"70":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"71":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":6,"docs":{"17":{"tf":1.4142135623730951},"70":{"tf":1.4142135623730951},"71":{"tf":1.7320508075688772},"76":{"tf":1.0},"91":{"tf":1.0},"95":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":13,"docs":{"102":{"tf":2.0},"105":{"tf":1.4142135623730951},"13":{"tf":2.0},"15":{"tf":1.0},"20":{"tf":1.0},"29":{"tf":2.0},"41":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"50":{"tf":1.4142135623730951},"53":{"tf":1.0},"75":{"tf":2.6457513110645907},"9":{"tf":1.0}},"e":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"105":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"105":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"<":{"\'":{"_":{"df":1,"docs":{"105":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"<":{"\'":{"_":{"df":1,"docs":{"75":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"p":{"df":1,"docs":{"91":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":4,"docs":{"17":{"tf":1.0},"70":{"tf":1.4142135623730951},"74":{"tf":1.0},"76":{"tf":1.4142135623730951}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"&":{"\'":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":1,"docs":{"15":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"42":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"<":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"u":{"6":{"4":{"df":1,"docs":{"103":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"i":{"3":{"2":{"df":9,"docs":{"20":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"41":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":4,"docs":{"102":{"tf":1.0},"103":{"tf":1.0},"13":{"tf":1.0},"42":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"43":{"tf":1.0}}}},"df":0,"docs":{}}}}},"t":{"df":1,"docs":{"20":{"tf":1.0}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"73":{"tf":1.0}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":8,"docs":{"20":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"49":{"tf":1.0},"70":{"tf":1.4142135623730951},"71":{"tf":1.4142135623730951},"74":{"tf":1.4142135623730951},"75":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"56":{"tf":1.4142135623730951}}}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"4":{"tf":1.0}}}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"85":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"45":{"tf":3.1622776601683795}},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"d":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{":":{":":{"a":{"d":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{")":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"d":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"45":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"45":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":1,"docs":{"45":{"tf":2.449489742783178}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"88":{"tf":1.0}},"e":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":2,"docs":{"87":{"tf":1.0},"88":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":8,"docs":{"0":{"tf":1.7320508075688772},"1":{"tf":1.0},"22":{"tf":1.0},"28":{"tf":1.0},"4":{"tf":1.0},"58":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}},"f":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}}},",":{"df":0,"docs":{},"之":{"df":0,"docs":{},"前":{"df":0,"docs":{},"我":{"df":0,"docs":{},"一":{"df":0,"docs":{},"直":{"df":0,"docs":{},"用":{"df":0,"docs":{},"j":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}}}}}}}}}}}},"s":{"3":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"16":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"79":{"tf":1.0}}}}},"c":{"a":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"!":{"(":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"100":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}},"e":{"d":{"2":{"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":3,"docs":{"100":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"99":{"tf":1.0}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"99":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"29":{"tf":1.0}}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}},"e":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"91":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},":":{":":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"(":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":5,"docs":{"105":{"tf":1.0},"30":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0}}},"y":{":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":6,"docs":{"16":{"tf":1.0},"17":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"5":{"tf":1.0}}},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":2,"docs":{"91":{"tf":1.0},"92":{"tf":1.0}}}}}}}}}}}},"df":2,"docs":{"22":{"tf":1.0},"23":{"tf":1.0}}}}}},"df":0,"docs":{}},"<":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"17":{"tf":1.0},"30":{"tf":1.0}}},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"91":{"tf":1.0}}}}}}}}}}}},"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":25,"docs":{"0":{"tf":1.0},"103":{"tf":1.4142135623730951},"105":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.7320508075688772},"17":{"tf":1.7320508075688772},"22":{"tf":1.0},"23":{"tf":1.0},"30":{"tf":1.0},"39":{"tf":1.7320508075688772},"4":{"tf":1.7320508075688772},"40":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"5":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0},"95":{"tf":1.7320508075688772},"96":{"tf":1.4142135623730951}},",":{"df":0,"docs":{},"然":{"df":0,"docs":{},"后":{"df":0,"docs":{},"调":{"df":0,"docs":{},"用":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"d":{"df":0,"docs":{},"l":{"df":2,"docs":{"10":{"tf":1.0},"34":{"tf":1.0}}}},"df":2,"docs":{"29":{"tf":2.23606797749979},"31":{"tf":2.0}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"102":{"tf":4.47213595499958},"103":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"f":{")":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"55":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},".":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"46":{"tf":1.0}}}}},"df":1,"docs":{"11":{"tf":1.0}}},"b":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"46":{"tf":1.0},"49":{"tf":1.0}}}},"df":0,"docs":{}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"u":{"df":2,"docs":{"29":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"105":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"29":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},":":{":":{"a":{"df":1,"docs":{"28":{"tf":1.0}}},"b":{"df":1,"docs":{"28":{"tf":1.0}}},"c":{"df":1,"docs":{"28":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.4142135623730951}}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":15,"docs":{"13":{"tf":1.0},"15":{"tf":1.0},"24":{"tf":1.0},"26":{"tf":1.0},"28":{"tf":1.0},"45":{"tf":2.0},"46":{"tf":1.4142135623730951},"49":{"tf":1.4142135623730951},"55":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0}}}},"n":{"d":{"df":1,"docs":{"79":{"tf":1.0}},"和":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"15":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"r":{"d":{"df":2,"docs":{"100":{"tf":1.4142135623730951},"26":{"tf":1.0}},"e":{":":{":":{"df":0,"docs":{},"{":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"100":{"tf":1.0},"26":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{"&":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"z":{"df":0,"docs":{},"e":{"df":0,"docs":{},"和":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"100":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"7":{"tf":1.4142135623730951},"85":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"72":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":2,"docs":{"105":{"tf":1.4142135623730951},"75":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"71":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}}}}}}}},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"79":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"h":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":2,"docs":{"29":{"tf":2.0},"31":{"tf":1.7320508075688772}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":2.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"29":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"42":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"10":{"tf":1.4142135623730951},"11":{"tf":1.0},"12":{"tf":1.0},"17":{"tf":1.0},"45":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"9":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"90":{"tf":1.0},"94":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"45":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}}}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}}},"t":{"df":1,"docs":{"34":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"10":{"tf":1.0}}}}}}}}},"t":{"df":1,"docs":{"10":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"10":{"tf":1.7320508075688772},"34":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"10":{"tf":1.7320508075688772},"34":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"10":{"tf":1.7320508075688772},"34":{"tf":1.7320508075688772}}}}}}}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"17":{"tf":1.0}}},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"10":{"tf":1.7320508075688772},"34":{"tf":1.7320508075688772}}}}}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":2,"docs":{"52":{"tf":1.0},"80":{"tf":1.0}},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"58":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":2,"docs":{"51":{"tf":1.0},"81":{"tf":1.0}}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"x":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"104":{"tf":1.0}}}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"<":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"df":2,"docs":{"103":{"tf":1.0},"104":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"a":{"df":0,"docs":{},"s":{"(":{"\\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"102":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"29":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772}},"e":{"\\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"58":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":1,"docs":{"27":{"tf":1.0}},"t":{".":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{")":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{")":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"n":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"51":{"tf":2.0}}}},"t":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"79":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"100":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"n":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"17":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"f":{"3":{"2":{":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":2,"docs":{"29":{"tf":1.0},"31":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"20":{"tf":1.0},"54":{"tf":1.0},"58":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":1,"docs":{"20":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{":":{":":{"a":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"103":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{":":{":":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"43":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"43":{"tf":1.4142135623730951}}}},"r":{"df":10,"docs":{"105":{"tf":1.0},"18":{"tf":1.0},"30":{"tf":1.0},"5":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"72":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"m":{"<":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":3,"docs":{"23":{"tf":1.4142135623730951},"43":{"tf":1.0},"92":{"tf":1.0}}}}}}},"df":2,"docs":{"43":{"tf":1.0},"76":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"92":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"(":{"\\"":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":19,"docs":{"10":{"tf":2.0},"102":{"tf":1.0},"103":{"tf":1.4142135623730951},"105":{"tf":2.0},"107":{"tf":1.4142135623730951},"13":{"tf":1.7320508075688772},"15":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"20":{"tf":1.0},"24":{"tf":1.7320508075688772},"29":{"tf":1.7320508075688772},"30":{"tf":2.0},"34":{"tf":2.0},"41":{"tf":1.0},"42":{"tf":2.0},"47":{"tf":1.0},"61":{"tf":1.4142135623730951},"79":{"tf":1.7320508075688772}},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"b":{"df":1,"docs":{"99":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"i":{"6":{"4":{"df":1,"docs":{"99":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":67,"docs":{"10":{"tf":2.6457513110645907},"100":{"tf":1.0},"102":{"tf":1.0},"103":{"tf":1.4142135623730951},"104":{"tf":1.0},"105":{"tf":1.7320508075688772},"107":{"tf":1.4142135623730951},"108":{"tf":1.7320508075688772},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":2.0},"15":{"tf":1.0},"16":{"tf":2.0},"17":{"tf":1.4142135623730951},"18":{"tf":1.0},"19":{"tf":1.7320508075688772},"20":{"tf":1.0},"22":{"tf":2.23606797749979},"23":{"tf":2.0},"24":{"tf":1.7320508075688772},"26":{"tf":1.7320508075688772},"29":{"tf":1.4142135623730951},"30":{"tf":2.0},"31":{"tf":1.4142135623730951},"32":{"tf":2.449489742783178},"33":{"tf":1.7320508075688772},"34":{"tf":2.6457513110645907},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.4142135623730951},"42":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.7320508075688772},"46":{"tf":1.4142135623730951},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.4142135623730951},"5":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"9":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.4142135623730951},"95":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.0}}}},"df":0,"docs":{}}},"u":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":2,"docs":{"80":{"tf":1.0},"81":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"76":{"tf":1.0}},"e":{"<":{"\'":{"df":1,"docs":{"76":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"的":{"df":0,"docs":{},"行":{"df":0,"docs":{},"为":{"df":0,"docs":{},"和":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"76":{"tf":1.0}}}}}}}}}}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":4,"docs":{"23":{"tf":1.4142135623730951},"4":{"tf":1.0},"43":{"tf":1.7320508075688772},"92":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"1":{"df":1,"docs":{"23":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"1":{"df":1,"docs":{"23":{"tf":1.4142135623730951}}},"2":{"df":1,"docs":{"23":{"tf":1.7320508075688772}}},":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"23":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"92":{"tf":1.7320508075688772}}}}}},",":{"df":0,"docs":{},"所":{"df":0,"docs":{},"以":{"df":0,"docs":{},"我":{"df":0,"docs":{},"们":{"df":0,"docs":{},"用":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"和":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}}}}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"df":0,"docs":{},"m":{"df":1,"docs":{"4":{"tf":1.0}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"79":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"25":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"1":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"36":{"tf":1.0},"37":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}},"2":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"36":{"tf":1.0},"37":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}},"3":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"36":{"tf":1.0},"37":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":1,"docs":{"26":{"tf":1.0}}}},"df":0,"docs":{}}}},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"55":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"55":{"tf":1.4142135623730951}}}}}}}}}},"i":{"c":{"df":0,"docs":{},"k":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"92":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}}},"o":{"_":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"99":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"o":{"df":17,"docs":{"102":{"tf":2.23606797749979},"103":{"tf":1.4142135623730951},"104":{"tf":1.4142135623730951},"105":{"tf":1.0},"22":{"tf":2.0},"24":{"tf":1.7320508075688772},"30":{"tf":1.0},"33":{"tf":1.0},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772},"41":{"tf":1.0},"42":{"tf":1.4142135623730951},"45":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"56":{"tf":1.0},"64":{"tf":1.4142135623730951}},"i":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}},"对":{"df":0,"docs":{},"象":{"df":0,"docs":{},"。":{"df":0,"docs":{},"然":{"df":0,"docs":{},"后":{"df":0,"docs":{},"对":{"df":0,"docs":{},"每":{"df":0,"docs":{},"个":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"对":{"df":0,"docs":{},"象":{"df":0,"docs":{},"同":{"df":0,"docs":{},"时":{"df":0,"docs":{},"调":{"df":0,"docs":{},"用":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"102":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"17":{"tf":1.4142135623730951},"42":{"tf":1.0}}}},"i":{"df":0,"docs":{},"o":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"103":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"43":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"43":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}}}}}}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"e":{"df":4,"docs":{"59":{"tf":1.7320508075688772},"80":{"tf":2.0},"81":{"tf":1.0},"84":{"tf":2.0}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"扩":{"df":0,"docs":{},"展":{"df":0,"docs":{},"需":{"df":0,"docs":{},"要":{"df":0,"docs":{},"在":{"df":0,"docs":{},"创":{"df":0,"docs":{},"建":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"58":{"tf":1.0},"67":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"e":{"df":1,"docs":{"65":{"tf":1.4142135623730951}}}},"y":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"y":{"df":3,"docs":{"29":{"tf":2.0},"30":{"tf":1.4142135623730951},"37":{"tf":2.449489742783178}},"p":{"df":0,"docs":{},"e":{"df":9,"docs":{"10":{"tf":1.4142135623730951},"103":{"tf":1.4142135623730951},"104":{"tf":2.0},"20":{"tf":1.0},"24":{"tf":1.0},"28":{"tf":1.7320508075688772},"30":{"tf":1.0},"61":{"tf":1.4142135623730951},"91":{"tf":1.0}}}}}},"u":{"6":{"4":{"df":2,"docs":{"102":{"tf":1.0},"103":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"26":{"tf":1.0}},"n":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"47":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"31":{"tf":1.7320508075688772},"32":{"tf":1.4142135623730951}}}}}},"r":{"df":0,"docs":{},"l":{"df":1,"docs":{"47":{"tf":1.4142135623730951}}}},"s":{"df":67,"docs":{"10":{"tf":1.4142135623730951},"100":{"tf":1.7320508075688772},"103":{"tf":2.0},"105":{"tf":1.4142135623730951},"107":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"15":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.7320508075688772},"18":{"tf":2.23606797749979},"19":{"tf":1.0},"20":{"tf":1.4142135623730951},"22":{"tf":1.0},"23":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"26":{"tf":1.4142135623730951},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.4142135623730951},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"4":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":2.0},"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.4142135623730951},"53":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.4142135623730951},"56":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.4142135623730951},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"70":{"tf":2.0},"71":{"tf":1.7320508075688772},"72":{"tf":1.7320508075688772},"73":{"tf":1.4142135623730951},"74":{"tf":1.4142135623730951},"75":{"tf":1.4142135623730951},"76":{"tf":1.7320508075688772},"79":{"tf":1.0},"87":{"tf":1.7320508075688772},"88":{"tf":1.7320508075688772},"9":{"tf":1.0},"91":{"tf":1.7320508075688772},"92":{"tf":2.0},"95":{"tf":1.7320508075688772},"96":{"tf":1.7320508075688772},"99":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"41":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"102":{"tf":1.0},"103":{"tf":1.0}}},"df":0,"docs":{}}},"df":8,"docs":{"102":{"tf":4.795831523312719},"103":{"tf":2.0},"104":{"tf":1.4142135623730951},"107":{"tf":2.8284271247461903},"108":{"tf":1.7320508075688772},"22":{"tf":1.0},"33":{"tf":1.0},"41":{"tf":1.0}},"i":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}},"n":{"a":{"df":0,"docs":{},"m":{"df":3,"docs":{"107":{"tf":1.4142135623730951},"41":{"tf":1.0},"42":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"103":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}}}},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"22":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"a":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"33":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"对":{"df":0,"docs":{},"象":{"df":0,"docs":{},"的":{"df":0,"docs":{},"主":{"df":0,"docs":{},"键":{"df":0,"docs":{},"是":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"108":{"tf":1.0}}}}}}}}}}}}},"i":{"df":0,"docs":{},"z":{"df":1,"docs":{"64":{"tf":1.0}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"73":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"73":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"u":{"df":13,"docs":{"100":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.4142135623730951},"105":{"tf":2.449489742783178},"13":{"tf":1.0},"24":{"tf":2.0},"36":{"tf":2.449489742783178},"37":{"tf":2.449489742783178},"43":{"tf":1.7320508075688772},"46":{"tf":1.7320508075688772},"49":{"tf":2.0},"9":{"tf":1.4142135623730951},"99":{"tf":1.7320508075688772}},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"13":{"tf":1.0},"64":{"tf":1.0}}}}}}},"df":0,"docs":{}},".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"|":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"105":{"tf":1.0}}}}},"df":0,"docs":{}},"x":{"df":1,"docs":{"26":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"1":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"50":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":4,"docs":{"26":{"tf":1.0},"38":{"tf":1.0},"45":{"tf":1.0},"50":{"tf":1.4142135623730951}}},"2":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"50":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":4,"docs":{"26":{"tf":1.0},"38":{"tf":1.0},"45":{"tf":1.0},"50":{"tf":1.0}}},"3":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"50":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"38":{"tf":1.0},"50":{"tf":1.0}}},":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{".":{"0":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"c":{"2":{"8":{"2":{"2":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{"3":{"3":{"9":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"99":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"105":{"tf":1.4142135623730951}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"99":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"_":{"d":{"b":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}},"e":{"d":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"26":{"tf":1.0}}}}}},"2":{"(":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"26":{"tf":1.0}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{".":{"0":{"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"s":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"64":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"72":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"s":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"72":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"c":{"<":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"73":{"tf":1.0}}}}}}}}}}}},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"48":{"tf":1.0}}}}},"t":{"df":2,"docs":{"25":{"tf":1.4142135623730951},"4":{"tf":1.0}}},"u":{"df":0,"docs":{},"s":{"df":2,"docs":{"22":{"tf":1.0},"33":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":8,"docs":{"105":{"tf":1.0},"30":{"tf":1.0},"5":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"16":{"tf":1.0}}}}}}}}},"w":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}},"r":{"df":1,"docs":{"27":{"tf":1.0}},"p":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":2,"docs":{"91":{"tf":1.0},"92":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"90":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{")":{".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"(":{"(":{"[":{"0":{"df":2,"docs":{"91":{"tf":1.0},"92":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"{":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":7,"docs":{"17":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"85":{"tf":1.4142135623730951},"90":{"tf":1.7320508075688772},"91":{"tf":1.4142135623730951},"92":{"tf":1.4142135623730951},"93":{"tf":1.0}},"提":{"df":0,"docs":{},"供":{"df":0,"docs":{},"了":{"df":0,"docs":{},"两":{"df":0,"docs":{},"个":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},",":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"和":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"90":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}}}}}}}}}}}}}},"s":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"b":{":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"<":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"<":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"95":{"tf":1.0},"96":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{")":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"95":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"<":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"95":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"p":{"a":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"96":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":8,"docs":{"3":{"tf":1.4142135623730951},"7":{"tf":1.4142135623730951},"85":{"tf":2.0},"90":{"tf":1.0},"94":{"tf":2.0},"95":{"tf":1.0},"96":{"tf":1.0},"97":{"tf":1.4142135623730951}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":13,"docs":{"85":{"tf":1.7320508075688772},"86":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"89":{"tf":1.0},"90":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0},"93":{"tf":1.0},"94":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"97":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"58":{"tf":1.0}}},"l":{"d":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"x":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"26":{"tf":1.0}}}}}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"10":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"title":{"root":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"94":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"df":0,"docs":{},"z":{"df":1,"docs":{"78":{"tf":1.0}}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":5,"docs":{"106":{"tf":1.0},"59":{"tf":1.0},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"51":{"tf":1.0}}}}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"103":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"27":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":2,"docs":{"54":{"tf":1.0},"55":{"tf":1.0}}}}}}}}}}}},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"26":{"tf":1.0}}}}}},"df":0,"docs":{}}},"f":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"106":{"tf":1.0}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"45":{"tf":1.0}}},"df":0,"docs":{}}}}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}}}}}}},"df":0,"docs":{}},"u":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"45":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"33":{"tf":1.0},"38":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"a":{"c":{"df":1,"docs":{"29":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"104":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"19":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"21":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"n":{"+":{"1":{"df":1,"docs":{"101":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"13":{"tf":1.0},"22":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"83":{"tf":1.0}}}}}}}}}}}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"86":{"tf":1.0}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"79":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"75":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"56":{"tf":1.0}}}}}}}}}}},"s":{"c":{"a":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"100":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":3,"docs":{"16":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"7":{"tf":1.0}}}}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"t":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":1,"docs":{"81":{"tf":1.0}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"r":{"a":{"c":{"df":0,"docs":{},"e":{"df":3,"docs":{"59":{"tf":1.0},"80":{"tf":1.0},"84":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"31":{"tf":1.0}}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":1,"docs":{"90":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"b":{"df":2,"docs":{"7":{"tf":1.0},"94":{"tf":1.0}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"85":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}},"lang":"English","pipeline":["trimmer","stopWordFilter","stemmer"],"ref":"id","version":"0.9.5"},"results_options":{"limit_results":30,"teaser_word_count":30},"search_options":{"bool":"OR","expand":true,"fields":{"body":{"boost":1},"breadcrumbs":{"boost":1},"title":{"boost":2}}}}'); \ No newline at end of file diff --git a/zh-CN/subscription.html b/zh-CN/subscription.html new file mode 100644 index 000000000..d73c2d285 --- /dev/null +++ b/zh-CN/subscription.html @@ -0,0 +1,247 @@ + + + + + + 订阅 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

订阅

+

订阅根对象和其它根对象定义稍有不同,它的 Resolver 函数总是返回一个 Stream 或者Result<Stream>,而字段参数通常作为数据的筛选条件。

+

下面的例子订阅一个整数流,它每秒产生一个整数,参数step指定了整数的步长,默认为 1。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use std::time::Duration;
+use async_graphql::futures_util::stream::Stream;
+use async_graphql::futures_util::StreamExt;
+extern crate tokio_stream;
+extern crate tokio;
+use async_graphql::*;
+
+struct Subscription;
+
+#[Subscription]
+impl Subscription {
+    async fn integers(&self, #[graphql(default = 1)] step: i32) -> impl Stream<Item = i32> {
+        let mut value = 0;
+        tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(Duration::from_secs(1)))
+            .map(move |_| {
+                value += step;
+                value
+            })
+    }
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/toc.html b/zh-CN/toc.html new file mode 100644 index 000000000..e2b937996 --- /dev/null +++ b/zh-CN/toc.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + +
  1. 介绍
  2. 快速开始
  3. 类型系统
    1. 简单对象 (SimpleObject)
    2. 对象 (Object)
      1. 查询上下文 (Context)
      2. 错误处理
      3. 合并对象 (MergedObject)
      4. 派生字段
    3. 枚举 (Enum)
    4. 接口 (Interface)
    5. 联合 (Union)
    6. 输入对象 (InputObject)
    7. 默认值
  4. 定义模式 (Schema)
    1. 查询和变更
    2. 订阅
  5. 实用功能
    1. 字段守卫
    2. 输入值校验器
    3. 查询缓存控制
    4. 游标连接
    5. 错误扩展
    6. Apollo Tracing 支持
    7. 查询的深度和复杂度
    8. 在内省中隐藏内容
  6. 扩展
    1. 扩展如何工作
    2. 可用的扩展列表
  7. 集成到 WebServer
    1. Poem
    2. Warp
    3. Actix-web
  8. 高级主题
    1. 自定义标量
    2. 优化查询(解决 N+1 问题)
    3. 自定义指令
    4. Apollo Federation 集成
+ + diff --git a/zh-CN/toc.js b/zh-CN/toc.js new file mode 100644 index 000000000..752996d32 --- /dev/null +++ b/zh-CN/toc.js @@ -0,0 +1,70 @@ +// Populate the sidebar +// +// This is a script, and not included directly in the page, to control the total size of the book. +// The TOC contains an entry for each page, so if each page includes a copy of the TOC, +// the total size of the page becomes O(n**2). +class MDBookSidebarScrollbox extends HTMLElement { + constructor() { + super(); + } + connectedCallback() { + this.innerHTML = '
  1. 介绍
  2. 快速开始
  3. 类型系统
    1. 简单对象 (SimpleObject)
    2. 对象 (Object)
      1. 查询上下文 (Context)
      2. 错误处理
      3. 合并对象 (MergedObject)
      4. 派生字段
    3. 枚举 (Enum)
    4. 接口 (Interface)
    5. 联合 (Union)
    6. 输入对象 (InputObject)
    7. 默认值
  4. 定义模式 (Schema)
    1. 查询和变更
    2. 订阅
  5. 实用功能
    1. 字段守卫
    2. 输入值校验器
    3. 查询缓存控制
    4. 游标连接
    5. 错误扩展
    6. Apollo Tracing 支持
    7. 查询的深度和复杂度
    8. 在内省中隐藏内容
  6. 扩展
    1. 扩展如何工作
    2. 可用的扩展列表
  7. 集成到 WebServer
    1. Poem
    2. Warp
    3. Actix-web
  8. 高级主题
    1. 自定义标量
    2. 优化查询(解决 N+1 问题)
    3. 自定义指令
    4. Apollo Federation 集成
'; + // Set the current, active page, and reveal it if it's hidden + let current_page = document.location.href.toString().split("#")[0].split("?")[0]; + if (current_page.endsWith("/")) { + current_page += "index.html"; + } + var links = Array.prototype.slice.call(this.querySelectorAll("a")); + var l = links.length; + for (var i = 0; i < l; ++i) { + var link = links[i]; + var href = link.getAttribute("href"); + if (href && !href.startsWith("#") && !/^(?:[a-z+]+:)?\/\//.test(href)) { + link.href = path_to_root + href; + } + // The "index" page is supposed to alias the first chapter in the book. + if (link.href === current_page || (i === 0 && path_to_root === "" && current_page.endsWith("/index.html"))) { + link.classList.add("active"); + var parent = link.parentElement; + if (parent && parent.classList.contains("chapter-item")) { + parent.classList.add("expanded"); + } + while (parent) { + if (parent.tagName === "LI" && parent.previousElementSibling) { + if (parent.previousElementSibling.classList.contains("chapter-item")) { + parent.previousElementSibling.classList.add("expanded"); + } + } + parent = parent.parentElement; + } + } + } + // Track and set sidebar scroll position + this.addEventListener('click', function(e) { + if (e.target.tagName === 'A') { + sessionStorage.setItem('sidebar-scroll', this.scrollTop); + } + }, { passive: true }); + var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll'); + sessionStorage.removeItem('sidebar-scroll'); + if (sidebarScrollTop) { + // preserve sidebar scroll position when navigating via links within sidebar + this.scrollTop = sidebarScrollTop; + } else { + // scroll sidebar to current active section when navigating via "next/previous chapter" buttons + var activeSection = document.querySelector('#sidebar .active'); + if (activeSection) { + activeSection.scrollIntoView({ block: 'center' }); + } + } + // Toggle buttons + var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle'); + function toggleSection(ev) { + ev.currentTarget.parentElement.classList.toggle('expanded'); + } + Array.from(sidebarAnchorToggles).forEach(function (el) { + el.addEventListener('click', toggleSection); + }); + } +} +window.customElements.define("mdbook-sidebar-scrollbox", MDBookSidebarScrollbox); diff --git a/zh-CN/tomorrow-night.css b/zh-CN/tomorrow-night.css new file mode 100644 index 000000000..11752b8a8 --- /dev/null +++ b/zh-CN/tomorrow-night.css @@ -0,0 +1,104 @@ +/* Tomorrow Night Theme */ +/* https://github.com/jmblog/color-themes-for-highlightjs */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* https://github.com/jmblog/color-themes-for-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment { + color: #969896; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-attribute, +.hljs-attr, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #cc6666; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #de935f; +} + +/* Tomorrow Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #f0c674; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.hljs-name, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #b5bd68; +} + +/* Tomorrow Aqua */ +.hljs-title, +.hljs-section, +.css .hljs-hexcolor { + color: #8abeb7; +} + +/* Tomorrow Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #81a2be; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #b294bb; +} + +.hljs { + display: block; + overflow-x: auto; + background: #1d1f21; + color: #c5c8c6; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} + +.hljs-addition { + color: #718c00; +} + +.hljs-deletion { + color: #c82829; +} diff --git a/zh-CN/typesystem.html b/zh-CN/typesystem.html new file mode 100644 index 000000000..fc0afbe8f --- /dev/null +++ b/zh-CN/typesystem.html @@ -0,0 +1,222 @@ + + + + + + 类型系统 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

类型系统

+

Async-graphql包含 GraphQL 类型到 Rust 类型的完整实现,并且非常易于使用。

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/utilities.html b/zh-CN/utilities.html new file mode 100644 index 000000000..ae31412bb --- /dev/null +++ b/zh-CN/utilities.html @@ -0,0 +1,221 @@ + + + + + + 实用功能 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

实用功能

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/zh-CN/visibility.html b/zh-CN/visibility.html new file mode 100644 index 000000000..fe8dfe43c --- /dev/null +++ b/zh-CN/visibility.html @@ -0,0 +1,262 @@ + + + + + + 在内省中隐藏内容 - Async-graphql教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Keyboard shortcuts

+
+

Press or to navigate between chapters

+

Press S or / to search in the book

+

Press ? to show this help

+

Press Esc to hide this help

+
+
+
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

在内省中隐藏内容

+

默认情况下,所有类型,字段在内省中都是可见的。但可能你希望根据不同的用户来隐藏一些信息,避免引起不必要的误会。你可以在类型或者字段上添加visible属性来做到。

+
#![allow(unused)]
+fn main() {
+extern crate async_graphql;
+use async_graphql::*;
+
+#[derive(SimpleObject)]
+struct MyObj {
+    // 这个字段将在内省中可见
+    a: i32,
+
+    // 这个字段在内省中总是隐藏
+    #[graphql(visible = false)]
+    b: i32, 
+
+    // 这个字段调用 `is_admin` 函数,如果函数的返回值为 `true` 则可见
+    #[graphql(visible = "is_admin")]
+    c: i32, 
+}
+
+#[derive(Enum, Copy, Clone, Eq, PartialEq)]
+enum MyEnum {
+    // 这个项目将在内省中可见
+    A,
+
+    // 这个项目在内省中总是隐藏
+    #[graphql(visible = false)]
+    B,
+
+    // 这个项目调用 `is_admin` 函数,如果函数的返回值为 `true` 则可见
+    #[graphql(visible = "is_admin")]
+    C,
+}
+
+struct IsAdmin(bool);
+
+fn is_admin(ctx: &Context<'_>) -> bool {
+    ctx.data_unchecked::<IsAdmin>().0
+}
+
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ +