pax_global_header00006660000000000000000000000064141412145300014505gustar00rootroot0000000000000052 comment=c4778ffcba5de34b05dfe1b63ce94a60201a5625 graphql-ruby-1.11.10/000077500000000000000000000000001414121453000142635ustar00rootroot00000000000000graphql-ruby-1.11.10/.codeclimate.yml000066400000000000000000000004151414121453000173350ustar00rootroot00000000000000version: "2" exclude_patterns: - "spec/" - "lib/graphql/language/lexer.rb" - "lib/graphql/language/parser.rb" # plugin - duplication - is failing on these - "spec/graphql/language/printer_spec.rb" - "lib/graphql/compatibility/query_parser_specification.rb" graphql-ruby-1.11.10/.gitattributes000066400000000000000000000000641414121453000171560ustar00rootroot00000000000000*.snap linguist-generated *.lock linguist-generated graphql-ruby-1.11.10/.github/000077500000000000000000000000001414121453000156235ustar00rootroot00000000000000graphql-ruby-1.11.10/.github/ISSUE_TEMPLATE/000077500000000000000000000000001414121453000200065ustar00rootroot00000000000000graphql-ruby-1.11.10/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000030131414121453000224750ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve graphql-ruby title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **Versions** `graphql` version: `rails` (or other framework): other applicable versions (`graphql-batch`, etc) **GraphQL schema** Include relevant types and fields (in Ruby is best, in GraphQL IDL is ok). Are you using [interpreter](https://graphql-ruby.org/queries/interpreter.html)? Any custom instrumentation, etc? ```ruby class Product < GraphQL::Schema::Object field :id, ID, null: false, hash_key: :id # … end class ApplicationSchema < GraphQL::Schema query QueryType # … end ``` **GraphQL query** Example GraphQL query and response (if query execution is involved) ```graphql query { products { id title } } ``` ```json { "data": { "products": […] } } ``` **Steps to reproduce** Steps to reproduce the behavior **Expected behavior** A clear and concise description of what you expected to happen. **Actual behavior** What specifically went wrong? Place full backtrace here (if a Ruby exception is involved):
Click to view exception backtrace ``` Something went wrong 2.6.0/gems/graphql-1.9.17/lib/graphql/subscriptions/instrumentation.rb:34:in `after_query' … don't hesitate to include all the rows here: they will be collapsed ```
**Additional context** Add any other context about the problem here. With these details, we can efficiently hunt down the bug! graphql-ruby-1.11.10/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011271414121453000235340ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. graphql-ruby-1.11.10/.github/contributing.md000066400000000000000000000026511414121453000206600ustar00rootroot00000000000000# Contributing Thanks for getting involved! I hope the information below will help you contribute to graphql-ruby. ## Issues When reporting a bug, please include these details when applicable: - `graphql` version and other applicable versions (Rails, `graphql-batch`, etc) - Definitions of schema or relevant types and fields (in Ruby is best, in GraphQL IDL is ok) - Example GraphQL query and response (if query execution is involved) - Full backtrace (if a Ruby exception is involved) With these details, we can efficiently hunt down the bug! ## Code It's important for code to fit in with design and maintenance goals of the project. For this reason, consider an issue to discuss new features or large refactors. That way we can work together to find suitable solution! ## Legal By submitting a Pull Request, you disavow any rights or claims to any changes submitted to `graphql-ruby` and assign the copyright of those changes to Robert Mosolgo, the author and maintainer of `graphql-ruby`. If you cannot or don't want to reassign those rights (your employment contract for your employer may not allow this), don't submit a PR. Instead, open an issue so that someone else can give it a try. In short, contributing code means that the code belongs to the maintainer. That's generally what you want, since the burden of upkeep, support and distribution falls on the maintainer anyways. I hope this doesn't prohibit you from contributing! graphql-ruby-1.11.10/.github/workflows/000077500000000000000000000000001414121453000176605ustar00rootroot00000000000000graphql-ruby-1.11.10/.github/workflows/apidocs.yaml000066400000000000000000000026301414121453000221670ustar00rootroot00000000000000name: Publish API docs on: # For some reason, `on: release: ...` didn't work with `nektos/act` push: # Sequence of patterns matched against refs/tags tags: - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 jobs: build: name: Publish API Docs runs-on: ubuntu-latest steps: - name: Checkout release tag uses: actions/checkout@v2 with: ref: ${{ env.GITHUB_REF }} - name: Checkout GitHub pages branch uses: actions/checkout@v2 with: path: gh-pages ref: gh-pages - uses: actions/setup-ruby@v1 with: ruby-version: '2.7' - name: Bundle install run: | gem install bundler bundle config path vendor/bundle bundle install --jobs 4 --retry 3 - name: Build API docs run: | bundle exec rake site:fetch_latest apidocs:gen_version - name: Commit changes as last committer run: | git config --global user.name rmosolgo git config --global user.email rdmosolgo@github.com git status bundle exec rake site:commit_changes git status - name: Deploy to GitHub pages via gh-pages branch uses: s0/git-publish-subdir-action@master env: REPO: self BRANCH: gh-pages FOLDER: gh-pages GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} graphql-ruby-1.11.10/.github/workflows/ci.yaml000066400000000000000000000072741414121453000211510ustar00rootroot00000000000000name: CI Suite on: push jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 bundler-cache: true - run: bundle exec rake rubocop system_tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 bundler-cache: true env: BUNDLE_GEMFILE: ./spec/dummy/Gemfile - run: bundle exec rails test:system working-directory: ./spec/dummy # Some coverage goals of these tests: # - Test once without Rails at all # - Test postgres, to make sure that the ActiveRecord # stuff works on that (as well as the default sqlite) # - Test mongoid -- and several versions, since they're quite different # - Run the tests with Rails _and_ TESTING_LEGACY=1 to test legacy codepaths # - Run the JS unit tests once # - Test each major version of Rails we support # - Test the min/max minor Ruby version we support (and others?) test: strategy: fail-fast: false matrix: include: - gemfile: Gemfile ruby: 2.6 - gemfile: gemfiles/rails_3.2.gemfile ruby: 2.3 - gemfile: gemfiles/rails_4.2.gemfile ruby: 2.4 bundler: "1" # Rails 5.2 is tested with Postgresql below - gemfile: gemfiles/rails_6.0.gemfile ruby: 2.7 - gemfile: gemfiles/rails_master.gemfile ruby: 3.0 runs-on: ubuntu-latest steps: - run: echo BUNDLE_GEMFILE=${{ matrix.gemfile }} > $GITHUB_ENV - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true bundler: ${{ matrix.bundler || 'default' }} - run: bundle exec rake test legacy_test: runs-on: ubuntu-latest steps: - run: echo BUNDLE_GEMFILE='gemfiles/rails_6.0.gemfile' > $GITHUB_ENV - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: 2.7 bundler-cache: true - run: bundle exec rake test TESTING_LEGACY=1 javascript_test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: 2.7 bundler-cache: true - run: bundle exec rake js:all postgres_test: runs-on: ubuntu-latest services: postgres: image: postgres:latest env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: postgres ports: - 5432:5432 # needed because the postgres container does not provide a healthcheck options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - run: echo BUNDLE_GEMFILE='gemfiles/rails_5.2_postgresql.gemfile' > $GITHUB_ENV - run: echo DATABASE='POSTGRESQL' > $GITHUB_ENV - run: echo PGPASSWORD='postgres' > $GITHUB_ENV - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: 2.7 bundler-cache: true - run: bundle exec rake test mongodb_test: strategy: fail-fast: false matrix: gemfile: - gemfiles/mongoid_6.gemfile - gemfiles/mongoid_7.gemfile runs-on: ubuntu-latest services: mongodb: image: mongo:3.4.23 ports: - 27017:27017 steps: - run: echo BUNDLE_GEMFILE=${{ matrix.gemfile }} > $GITHUB_ENV - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: 2.7 bundler-cache: true - run: bundle exec rake test graphql-ruby-1.11.10/.github/workflows/website.yaml000066400000000000000000000024521414121453000222110ustar00rootroot00000000000000name: Publish Website on: push: branches: [master] jobs: build: name: Publish Website runs-on: ubuntu-latest steps: - name: Checkout master uses: actions/checkout@v2 - name: Checkout GitHub pages branch uses: actions/checkout@v2 with: path: gh-pages ref: gh-pages - uses: actions/setup-ruby@v1 with: ruby-version: '2.7' - name: Bundle install run: | gem install bundler bundle config path vendor/bundle bundle install --jobs 4 --retry 3 - name: Build HTML, reindex env: ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }} run: | bundle exec rake site:fetch_latest site:build_doc site:update_search_index site:clean_html site:build_html - name: Commit changes as last committer run: | git config --global user.name "%(git log --format="%aN" -n 1)" git config --global user.email "%(git log --format="%aE" -n 1)" bundle exec rake site:commit_changes - name: Deploy to GitHub pages via gh-pages branch uses: s0/git-publish-subdir-action@master env: REPO: self BRANCH: gh-pages FOLDER: gh-pages GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} graphql-ruby-1.11.10/.gitignore000066400000000000000000000004651414121453000162600ustar00rootroot00000000000000.ruby-version doc/ .yardoc/ guides/yardoc/ pkg/ Gemfile.lock gemfiles/*.lock # Test database *.db .bundle/ vendor/ .idea/ _site .sass-cache .jekyll-metadata .jekyll-cache gh-pages/ tmp/* __*.db node_modules/ yarn.lock OperationStoreClient.js spec/integration/tmp .vscode/ *.swp *.swo javascript_client/dist/ graphql-ruby-1.11.10/.rubocop.yml000066400000000000000000000020751414121453000165410ustar00rootroot00000000000000require: - ./cop/none_without_block_cop - ./cop/no_focus_cop AllCops: DisabledByDefault: true TargetRubyVersion: 2.2 Exclude: - 'lib/graphql/language/lexer.rb' - 'lib/graphql/language/parser.rb' - 'gemfiles/**/*' - 'tmp/**/*' - 'vendor/**/*' - 'spec/integration/tmp/**/*' # def ... # end Layout/DefEndAlignment: EnforcedStyleAlignWith: def # value = if # # ... # end Layout/EndAlignment: EnforcedStyleAlignWith: variable Lint/UselessAssignment: Enabled: true Lint/DuplicateMethods: Enabled: true Metrics/ParameterLists: Max: 7 CountKeywordArgs: false Style/ClassAndModuleChildren: EnforcedStyle: nested Layout/EmptyLineBetweenDefs: AllowAdjacentOneLineDefs: true Style/FrozenStringLiteralComment: Enabled: true Layout/IndentationWidth: Width: 2 Style/LambdaCall: EnforcedStyle: call Layout/LeadingCommentSpace: Enabled: true Naming/MethodName: EnforcedStyle: snake_case Style/WordArray: EnforcedStyle: brackets # ->(...) { ... } Layout/SpaceInLambdaLiteral: Enabled: true # Default is "require_no_space" graphql-ruby-1.11.10/.yardopts000066400000000000000000000001621414121453000161300ustar00rootroot00000000000000--no-private --markup=markdown --readme=readme.md --title='GraphQL Ruby API Documentation' 'lib/**/*.rb' - '*.md' graphql-ruby-1.11.10/Appraisals000066400000000000000000000023371414121453000163120ustar00rootroot00000000000000# frozen_string_literal: true appraise 'rails_3.2' do gem 'rails', '3.2.22.5', require: 'rails/all' gem 'activerecord', '~> 3.2.21' gem 'actionpack', '~> 3.2.21' gem 'test-unit' gem 'sqlite3', "~> 1.3.6", platform: :ruby gem 'activerecord-jdbcsqlite3-adapter', platform: :jruby gem 'sequel' end appraise 'rails_4.2' do gem 'rails', '~> 4.2', require: 'rails/all' gem 'activerecord', '~> 4.2.4' gem 'actionpack', '~> 4.2.4' gem 'concurrent-ruby', '1.0.0' gem 'sqlite3', "~> 1.3.6", platform: :ruby gem 'activerecord-jdbcsqlite3-adapter', platform: :jruby gem 'sequel' end appraise 'rails_5.2_postgresql' do gem 'rails', '~> 5.2.0', require: 'rails/all' gem 'pg', platform: :ruby gem 'sequel' end appraise 'rails_6.0' do gem 'rails', '~> 6.0.0', require: 'rails/all' gem 'sqlite3', "~> 1.4", platform: :ruby gem 'activerecord-jdbcsqlite3-adapter', platform: :jruby gem 'sequel' end appraise 'rails_master' do gem 'rails', github: 'rails/rails', require: 'rails/all' gem 'sqlite3', "~> 1.4", platform: :ruby gem 'activerecord-jdbcsqlite3-adapter', platform: :jruby gem 'sequel' end appraise 'mongoid_7' do gem 'mongoid', '~> 7.0.1' end appraise 'mongoid_6' do gem 'mongoid', '~> 6.4.1' end graphql-ruby-1.11.10/CHANGELOG-pro.md000066400000000000000000000426741414121453000167070ustar00rootroot00000000000000# graphql-pro ### Breaking Changes ### Deprecations ### New Features ### Bug Fix ## 1.16.2 (21 Dec 2020) ### New Features - Subscriptions: Add `stale_ttl_s:` and `cleanup_delay_s:` to customize persistence in Redis #3252 ## 1.16.1 (3 Dec 2020) ### Bug Fix - Fix duplicate calls to `Argument#authorized?` in CanCan and Pundit integrations #3242 ## 1.16.0 (10 Nov 2020) ### New Features - Ably Subscriptions: `cipher_base:` sets up end-to-end encryption ## 1.15.7 (29 Sept 2020) ### Bug Fix - Encoder: fix Ruby 2.7 warning #3161 - Stable connections: Handle `ARRAY[...]` selections and cursors on Postgres #3166 - Pundit: properly lookup policies for list inputs #3146 ## 1.15.6 (17 Sept 2020) ### Bug Fix - Stable Connections: Use method access to get `.cursor_#{idx}` values instead of `.attributes[:cursor_#{idx}]`, fixes #3149 ## 1.15.5 ### New Features - Stable Connections: use `.to_sql` to handle orderings that use complex Arel expressions (#3109) ## 1.15.4 (28 July 2020) ### New Features - Pundit: add `pundit_policy_class_for(object, context)` and `pundit_role_for(object, context)` for custom runtime lookups ## 1.15.3 (17 July 2020) ### Bug Fix - Subscriptions: don't send empty updates when subscriptions return `:no_update` ## 1.15.2 (16 July 2020) ### New Features - OperationStore: improve handling of archived operations in index views ## 1.15.1 (16 July 2020) (Oops, bad release!) ## 1.15.0 (15 July 2020) - OperationStore: Store & display `last_used_at` for operation store clients and operations. To upgrade, add the column to your ActiveRecord table: ```ruby add_column :graphql_client_operations, :last_used_at, :datetime ``` (It works out-of-the-box with the Redis backend.) You can opt out of this feature by adding `use GraphQL::Pro::OperationStore, ... default_touch_last_used_at: false` to your schema setup. - OperationStore: Add archive/unarchive workflow for operations. To upgrade, add the column to your table: ```ruby add_column :graphql_client_operations, :is_archived, :boolean, index: true ``` (It works out-of-the-box with the Redis backend.) - OperationStore: Fix indexing of enum values ## 1.14.1 (29 June 2020) - CanCan: Accept `can_can_attribute:` configuration, which is passed as the third input to `.can?(...)` ## 1.14.0 (13 June 2020) ### New Features - Add PubnubSubscriptions - Update subscription implementations to support `broadcast: true` when available ### Bug Fix - More Ruby 2.7 warning fixes ## 1.13.6 (8 June 2020) ### Bug Fix - Return the proper `pageInfo` values when it's requested before `edges` or `nodes` (#2972) ## 1.13.5 (11 May 2020) ### Bug Fix - Fix some warnings on Ruby 2.7 ## 1.13.4 (17 Apr 2020) ### Bug Fix - StableRelationConnection: properly return `hasNextPage: true` when `before` and `max_page_size` are used. ## 1.13.3 (2 Apr 2020) ### New Features - `GraphQL::Pro::OperationStore::Migration` can be used to copy persisted operations from one backend to another (eg, ActiveRecord to Redis). See the source file, `lib/graphql/pro/operation_store/migration.rb` for docs. ## 1.13.2 (28 Mar 2020) ### Deprecations - `GraphQL::Pro::Subscriptions` is deprecated; use `GraphQL::Pro::PusherSubscriptions` instead which works the same, but better (see below). This new name avoids confusion with the later-added `AblySubscriptions`. ### New Features - `GraphQL::Pro::PusherSubscriptions` replaces `GraphQL::Pro::Subscriptions` and adds orphaned record cleanup. (No more dangling records in Redis.) ## 1.13.1 (12 Mar 2020) - Use `nonce: true` when working with cursors in new stable connections ## 1.13.0 (10 Feb 2020) ### New Features - OperationStore supports a `redis:` backend - OperationStore supports an arbitrary `backend_class:` for persistence operations ### Bug Fix - Use a loop when clearing Redis subscription state to avoid large stack traces #2701 - Handle empty subscription keys when publishing updates #2061 ## 1.12.2 (22 Jan 2020) ### Bug Fix - Improve backwards compat with OperationStore (Improve adding `.tracer`, use `.graphql_name` when indexing) ## 1.12.1 (20 Jan 2020) ### Bug Fix - Fix OperationStore on class-based schemas with query instrumenters that use the query string ## 1.12.0 (20 Jan 2020) ### Deprecations - `GraphQL::Pro::Monitoring` is deprecated; see Tracing for a replacement: https://graphql-ruby.org/queries/tracing.html - `GraphQL::Pro::Repository` is deprecated; see OperationStore for a replacement: https://graphql-ruby.org/operation_store/overview.html ### New Features - New stable connection support based on GraphQL-Ruby 1.10's new pagination implementation. New classes provide better handling of `NULL` values in order-by columns and they can be applied on a field-by-field basis(`GraphQL::Pro::SqliteStableRelationConnection`, `GraphQL::Pro::MySQLStableRelationConnection`, `GraphQL::Pro::PostgresStableRelationConnection`). ### Bug Fix - Add the Access query analyzer to class-based schemas ## 1.11.0 (10 Oct 2019) ### New Features - Forwards-compatibility for graphql 1.10.0 - Support 1.10.0.pre1's input object argument `loads:` authorization ## 1.10.8 (8 Oct 2019) ### Bug Fix - Continue authorizing input object arguments - Use millisecond-aware string format for datetimes in cursors ## 1.10.7 (22 Jul 2019) ### Bug Fix - Support multiple subscriptions in one document ## 1.10.6 (27 Jun 2019) ### New Features - Support custom `#can_can_ability` methods on query context for CanCanIntegration - Support custom `#pundit_user` method on query context for PunditIntegration ### Bug Fix - Fix off-by-one error when paginating backwards from the last item in a stable relation connection ## 1.10.5 (11 May 2019) ### New Features - Include expected HMAC digest in OperationStore debug output ## 1.10.4 (26 Mar 2019) ### Bug Fix - Include content-length and content-type headers in OperationStore JSON responses ## 1.10.3 (13 Mar 2019) ### Bug Fix - Support stable connections ordered by Arel SQL literals ## 1.10.2 (11 Mar 2019) ### Bug Fix - Support stable connections on realized views (which don't have primary keys) ## 1.10.1 (8 Mar 2019) ### Bug Fix - Pundit integration: support `pundit_policy_class` String names when scoping connections ## 1.10.0 (5 Mar 2019) ### New Features - Add `GraphQL::Pro::Defer`, implementing `@defer` for streaming responses ## 1.9.13 (4 Mar 2019) ### Bug Fix - Pundit integration: correctly authorize fields when Query root is nil ## 1.9.12 (22 Feb 2019) ### Bug Fix - Pundit integration: use overriden `pundit_policy_class` for scoping and mutation authorization ## 1.9.11 (20 Feb 2019) ### Bug Fix - Pundit integration: Fields use the owner's configured `pundit_policy_class` if there is one - Pundit integration: avoid conflicts with `#initialize` for schema classes that don't need it ## 1.9.10 (19 Feb 2019) ### Bug Fix - Support inheritance with `pundit_policy_class(...)` ## 1.9.9 (13 Feb 2019) ### New Features - Support `pundit_policy_class(...)` and `pundit_policy_class:` to manually specify a class or class name. ## 1.9.8 (30 Jan 2019) ### New Features - Inject `context` into policy lookup hooks instead of just the user ## 1.9.7 (30 Jan 2019) ### New Features - Extract `pundit_policy` and `scope_by_pundit_policy` hooks for user override ## 1.9.6 (18 Jan 2019) ### Bug Fix - Properly render subscription context in dashboard ## 1.9.5 (14 Jan 2019) ## Bug Fix - Don't pass arrays to Pundit scopes (fixes https://github.com/rmosolgo/graphql-ruby/issues/2008) ## 1.9.4 (11 Jan 2019) ## Bug Fix - Prepare for future compat with graphql-ruby 1.9 ## 1.9.3 (3 Dec 2018) ### Bug Fix - Include table name when adding a default order-by-id to ActiveRecord Relations - Raise if a required cursor attribute is missing - Improve `rake routes` output for operation store endpoint - Support already-parsed queries in subscription RedisStorage ## 1.9.2 (2 Nov 2018) ### Bug Fix - Derp, remove the dummy app's `.log` files from the gem bundle - Fix ordering bug when a SQL function call doesn't have an explicit order ## 1.9.1 (1 Nov 2018) ### Bug Fix - Fix Pusher reference in AblySubscriptions ## 1.9.0 (27 Oct 2018) ### New Features - Add `GraphQL::Pro::AblySubscriptions` for GraphQL subscriptions over Ably.io transport ## 1.8.2 (22 Oct 2018) ### Bug Fix - Support `NULLS LAST` in stable cursors ## 1.8.1 (16 Oct 2018) ### Bug Fix - Improve operation store models to work when `config.active_record.primary_key_prefix_type` is set ## 1.8.0 (11 Oct 2018) ### New Features - Support Rails 3.2 with OperationStore - Use `.select` to filter items in CanCanIntegration ### Bug Fix - Properly send an _ability_ and the configured `can_can_action` to `.accessible_by` - Use a string (not integer) for `Content-Length` header in the dashboard ## 1.7.13 (2 Oct 2018) ### Breaking Change - `PunditIntegration`: instead of raising `MutationAuthorizationFailed` when an argument fails authorization, it will send a `GraphQL::UnauthorizedError` to your `Schema.unauthorized_object` hook. (This is what all other authorization failures do.) To retain the previous behavior, in your base mutation, add: ```ruby def unauthorized_by_pundit(owner, value) # Raise a runtime error to halt query execution raise "#{value} failed #{owner}'s auth check" end ``` Otherwise, customize the handling of this behavior with `Schema.unauthorized_object`. ### Bug Fix - Auth: mutation arguments which have authorization constraints but _don't_ load an object from the database will have _mutation instance_ passed to the auth check, not the input value. ## 1.7.12 (29 Aug 2018) ### New Features - Add `GraphQL::Pro::CanCanIntegration` which leverages GraphQL-Ruby's built-in auth ## 1.7.11 (21 Aug 2018) ### Bug Fix - `PunditIntegration`: Don't try to authorize loaded objects when they're `nil` ## 1.7.10 (10 Aug 2018) ### New Features - Update `PunditIntegration` for arguments, unions, interfaces and mutations ## 1.7.9 (9 Aug 2018) ### New Features - Add a new `PunditIntegration` which leverages the built-in authorization methods ## 1.7.8 (10 July 2018) ### Bug Fix - Authorization: fix scoping lists of abstract type when there's no `#scope` method on the strategy ## 1.7.7 (10 May 2018) ### Bug Fix - Fix ordering of authorization field instrumenter (put it at the end, not the beginning of the list) ## 1.7.6 (2 May 2018) ### New Features - Authorization: Add `view`/`access`/`authorize` methods to `GraphQL::Schema::Mutation` ## 1.7.5 (19 Apr 2018) ### New Features - Authorization: when a `fallback:` configuration is given, apply it to each field which doesn't have a configuration of its own or from its return type. _Don't_ apply that configuration at schema level (it's applied to each otherwise uncovered field instead). ## 1.7.4 (16 Apr 2018) ### New Features - Support Mongoid::Criteria in authorization scoping ## 1.7.3 (12 Apr 2018) ### Bug Fix - Fix authorization code for when `ActiveRecord` is not defined ## 1.7.2 (10 Apr 2018) ### Bug Fix - Use a more permissive regexp (`/^\s*((?:[a-z._]+)\(.*\))\s*(asc|desc)?\s*$/im`) to parse SQL functions ## 1.7.1 (4 Apr 2018) ### Bug Fix - Fix route helpers to support class-based schemas ## 1.7.0 (25 Mar 2018) ### New Features - Support `1.8-pre` versions of GraphQL-Ruby ### Bug Fix - Fix OperationStore when other query instrumenters need `.query_string` ## 1.6.5 (7 Feb 2018) ### Bug Fix - Support `LEAST(...)` in stable cursors ## 1.6.4 (7 Feb 2018) ### Bug Fix - Support `CASE ... END` in stable cursors ## 1.6.3 (26 Jan 2018) ### Bug Fix - Support `FIELD(...)` in stable cursors ## 1.6.2 (13 Jan 2018) ### Bug Fix - Improve detection of `OperationStore` for the dashboard - Serve `Content-Type` and `Content-Length` headers with dashboard pages - Better `Dashboard#inspect` for Rails routes output - Use a string to apply order-by-primary-key for better Rails 3 support ## 1.6.1 (22 Nov 2017) ### New Features - Support `composite_primary_keys` gem ## 1.6.0 (13 Nov 2017) ### Breaking Changes - `GraphQL::Pro::UI` renamed to `GraphQL::Pro::Dashboard` ### Deprecations - Routing method `.ui` was renamed to `.dashboard` ### New Features - Added `GraphQL::Pro::Subscriptions` - Added subscriptions component to Dashboard ## 1.5.9 (10 Oct 2017) ### Bug Fix - Don't crash when scoping lists of abstract types with Pundit ## 1.5.8 (2 Oct 2017) ### New Features - Use `authorize(:pundit, namespace: )` to lookup policies in a namespace instead of the global namespace. ### Bug Fix - Introspection data is allowed through `fallback:` `authorize:` and `access:` filters. (It can be hidden with a `view:` filter.) ## 1.5.7 (20 Sept 2017) ### Bug Fix - Properly return `nil` when a list of authorized objects returns `nil` ## 1.5.6 (19 Sept 2017) ### New Features - Add `authorization(..., operation_store:)` option for authorizing operation store requests ## 1.5.5 (18 Sept 2017) ### New Features - Support `ConnectionType.bidrectional_pagination?` in stable RelationConnection ## 1.5.4 (18 Sept 2017) ### Bug Fix - Fix load issue when Rails is not present ## 1.5.3 (4 Sept 2017) ### Bug Fix - Fix OperationStore views on PostgresQL - Fix stable cursors when joined tables have the same column names __Note:__ This is implemented by adding extra fields to the `SELECT` clause with aliases like `cursor_#{idx}`, so you'll notice this in your SQL logs. ## 1.5.2 (4 Aug 2017) ### Bug Fix - Bump `graphql` dependency to `1.6` ## 1.5.1 (2 Aug 2017) ### New Features - Routing extensions moved to `using GraphQL::Pro::Routes` ### Deprecations - Deprecate `using GraphQL::Pro`, move extensions to `GraphQL::Pro::Routes` ## 1.5.0 (31 Jul 2017) ### New Features - Add `GraphQL::Pro::OperationStore` for persisted queries with Rails ## 1.4.8 (14 Jul 2017) ### Bug Fix - Update `authorization` to use type-level `resolve_type` hooks ## 1.4.7 (13 Jul 2017) ### Bug Fix - Update authorization instrumentation for `graphql >= 1.6.5` ## 1.4.6 (6 Jul 2017) ### Bug Fix - Fix typo in RelationConnection source ## 1.4.5 (6 Jul 2017) ### Bug Fix - Correctly fall back to offset-based cursors with `before:` argument ## 1.4.4 (15 Jun 2017) ### New Features - Add `Schema#unauthorized_object(obj, ctx)` hook for failed runtime checks ### Bug Fix - Prevent usage of `parent_role:` with `view:` or `access:` (since parent role requires a runtime check) - Fix versioned, encrypted cursors with 16-byte legacy cursors ## 1.4.3 (13 Jun 2017) ### New Features - `OrderedRelationConnection` supports ordering by joined fields ### Bug Fix - Update auth plugin for new Relay instrumenters - `Pro::Encoder` supports `encoder(...)` as documented ## 1.4.2 (2 May 2017) ### Bug Fix - Fix compatibility of `RelationConnection` and `RangeAdd` helper ## 1.4.1 (19 Apr 2017) ### New Features - Add `:datadog` monitoring ## 1.4.0 (19 Apr 2017) ### New Features - `ActiveRecord::Relation`s can be scoped by Pundit `Scope`s, CanCan `accessible_by`, or custom strategy's `#scope(gate, relation)` methods - Default authorization configuration can be provided with `authorization(..., fallback: { ... })` - Authorization's `:current_user` key can be customized with `authorization(..., current_user: ...)` ## 1.3.0 (7 Mar 2017) ### New Features - Serve static, persisted queries with `GraphQL::Pro::Repository` ## 1.2.3 (2 May 2017) ### Bug Fix - Fix compatibility of `RelationConnection` and `RangeAdd` helper ## 1.2.2 (6 Mar 2017) ### Bug Fix - Raise `GraphQL::Pro::RelationConnection::InvalidRelationError` when a grouped, unordered relation is returned from a field. (This relation can't be stably paginated.) ## 1.2.1 (3 Mar 2017) ### New Features - Formally support ActiveRecord `>= 4.1.0` ### Bug Fix - Support grouped relations in `GraphQL::Pro::RelationConnection` ## 1.2.0 (1 Mar 2017) ### New Features - Authorize fields based on their parent object, for example: ```ruby AccountType = GraphQL::ObjectType.define do name "Account" # This field is visible to all users: field :name, types.String # This is only visible when the current user is an `:owner` # of this account field :account_balance, types.Int, authorize: { parent_role: :owner } end ``` ## 1.1.1 (22 Feb 2017) ### Bug Fixes - Fix monitoring when `Query#selected_operation` is nil ## 1.1.0 (9 Feb 2017) ### New Features - Add AppSignal monitoring platform - Add type- and field-level opting in and opting out of monitoring - Add `monitor_scalars: false` to skip monitoring on scalars ### Bug Fixes - Fix `OrderedRelationConnection` when neither `first` nor `last` are provided (use `max_page_size` or don't limit) ## 1.0.4 (23 Jan 2017) ### Bug Fixes - `OrderedRelationConnection` exposes more metadata methods: `parent`, `field`, `arguments`, `max_page_size`, `first`, `after`, `last`, `before` ## 1.0.3 (23 Jan 2017) ### Bug Fixes - When an authorization check fails on a non-null field, propagate the null and add a response to the errors key (as if the field had returned null). It previously leaked the internal symbol `__graphql_pro_access_not_allowed__`. - Apply a custom Pundit policy even when the value isn't `nil`. (It previously fell back to `Pundit.policy`, skipping a `pundit_policy_name` configuration.) ## 1.0.2 ### Bug Fixes - `OrderedRelationConnection` exposes the underlying relation as `#nodes` (like `RelationConnection` does), supporting custom connection fields. ## 1.0.1 ### New Features - CanCan integration now supports a custom `Ability` class with the `ability_class:` option: ```ruby authorize :cancan, ability_class: CustomAbility ``` ## 1.0.0 - `GraphQL::Pro` released graphql-ruby-1.11.10/CHANGELOG-relay.md000066400000000000000000000066771414121453000172260ustar00rootroot00000000000000# graphql-relay ### Breaking Changes ### Deprecations ### New Features ### Bug Fix ## 0.12.0 (21 Jul 2016) ### Breaking Changes - Don't cache a global node identification config #51 To migrate, assign your node identification helper to the schema: ```ruby NodeIdentification = GraphQL::Relay::GlobalNodeIdentification.define { ... } MySchema.node_identification = NodeIdentification ``` ### New Features - Support lazy definition blocks from graphql-ruby 0.17 - Add `startCursor` and `endCursor` to `PageInfo` #60 ### Bug Fix - Support `field:` keyword for connection helper #58 ## 0.11.2 (6 Jul 2016) ### New Features - Include description for built-in objects #55 ## 0.11.1 (24 Jun 2016) ### Bug Fix - Correctly pass parent object to Connections #53 ## 0.11.0 (19 Jun 2016) ### Breaking Changes - `BaseType.define_connection` no longer caches the result to use as the default `BaseType.connection_type`. Now, store the result of `.define_connection` in a variable and pass that variable into the schema: ```ruby # Capture the returned type: SomethingCustomConnectionType = SomethingType.define_connection { ... } DifferentThingType = GraphQL::ObjectType.define do # And pass it to the connection helper: connection :somethings, SomethingCustomConnectionType end ``` ### New Features - Support for custom edge types / classes #50 - Support for multiple connection classes #50 ## 0.10.0 (31 May 2016) ### New Feature - Support `graphql` 0.14.0 #47 ### Bug Fix - Use strings as argument names, not symbols #47 ## 0.9.5 ### New Feature - Root `id` field may have a description #43 ## 0.9.4 (29 Apr 2016) ### Bug Fix - Fix Node interface to support GraphQL 0.13.0+ ## 0.9.2 (29 Apr 2016) ### Bug Fix - Fix Node interface when type_from_object returns nil ## 0.9.1 (6 Apr 2016) ### Bug Fix - Respond to connection fields without any pagination arguments - Limit by `max_page_size` even when no arguments are present ## 0.9.0 (30 Mar 2016) ### Breaking change - Remove the `order` argument from connection fields. This isn't part of the spec and shouldn't have been there in the first place! You can implement this behavior with a custom argument, for example: ```ruby field :cities, CityType.connection_type do argument :order, types.String, default_value: "name" resolve ->(obj, args, ctx) { obj.order(args[:order]) } end ``` ### Bug Fix - Include the MIT license in the project's source ## 0.8.1 (22 Mar 2016) ### Bug Fix - Accept description for Mutations ## 0.8.0 (20 Mar 2016) ### New Feature - Accept configs for `to_global_id` and `from_global_id` - Support `graphql` 0.12+ ## 0.7.1 (29 Feb 2016) ### Bug Fix - Limit the `count(*)` when testing next page with ActiveRecord #28 ## 0.7.0 (20 Feb 2016) ### New Feature - `max_page_size` option for connections - Support ActiveSupport 5.0.0.beta2 ## 0.6.2 (11 Feb 2016) ### Bug Fix - Correctly cast values from connection cursors #21 - Use class _name_ instead of class _object_ when finding a connection implementation (to support Rails autoloading) #16 ## 0.6.1 (14 Dec 2015) ### Bug Fix - Stringify `id` when passed into `to_global_id` ## 0.6.0 (11 Dec 2015) ### Breaking Change - `GlobalNodeIdentification#object_from_id(id, ctx)` now accepts context as the second argument #9 ## 0.5.1 (11 Dec 2015) ### Feature - Allow custom UUID join string #15 ### Bug Fix - Remove implicit ActiveSupport dependency #14 graphql-ruby-1.11.10/CHANGELOG.md000066400000000000000000003250321414121453000161010ustar00rootroot00000000000000# Changelog ### Breaking changes ### Deprecations ### New features ### Bug fixes # 1.11.10 (5 Nov 2021) ### Bug fixes - Properly hook up `Schema.max_validation_errors` at query runtime #3690 # 1.11.9 ### New Features - `Schema.max_validation_errors(val)` limits the number of errors that can be added during static validation #3675 # 1.11.8 (12 Feb 2021) ### Bug fixes - Improve performance of `Schema.possible_types(t)` for object types #3172 # 1.11.7 (18 January 2021) ### Breaking changes - Incoming integer values are properly bound (as per the spec) #3206 To continue receiving out-of-bound integer values, add this to your schema's `def self.type_error(err, ctx)` hook: ```ruby def self.type_error(err, ctx) if err.is_a?(GraphQL::IntegerDecodingError) return err.value # return it anyways, since this is how graphql-ruby used to work end # ... end ``` ### New features - Support Ruby 3.0 #3278 - Add validation timeout option #3234 - Support Prometheus custom_labels in GraphQLCollector #3215 ### Bug fixes - Handle `GraphQL::UnauthorizedError` in interpreter in from arguments #3276 - Set description for auto-generated `input:` argument #3141 - Improve performance of fields will merge validation #3228 - Use `Float` graphql type for ActiveRecord decimal columns #3246 - Add some custom methods to ArrayConnection #3238 - Fix generated fields for types ending Connection #3223 - Improve runtime performance #3217 - Improve argument handling when extensions shortcut the defined resolve #3212 - Bind scalar ints as per the spec #3206 - Validate that input object names are unique #3205 ## 1.11.6 (29 October 2020) ### Breaking changes FieldExtension: pass extended values instead of originals to `after_resolve` #3168 ### Deprecations ### New features - Accept additional options in `global_id_field` macro #3196 ### Bug fixes - Use `graphql_name` in `UnauthorizedError` default message (fixes #3174) #3176 - Improve error handling for base 64 decoding (in `UniqueWithinType`) #3179 - Fix `.valid_isolated_input?` on parsed schemas (fixes #3181) #3182 - Fix fields nullability in subscriptions documentation #3194 - Update `RangeAdd` to use new connections when available #3195 ## 1.11.5 (30 September 2020) ### New features - SanitizedPrinter: accept `inline_variables: false` option and add `#redact_argument_value?` and `#redacted_argument_value` hooks #3167 - GraphQL::Schema::Timeoout#max_seconds(query) can provide a per-query timeout duration #3167 - Implement Interpreter::Arguments#fetch - Assign `current_{path,field,arguments,object}` in `query.context` #3139. The values at these keys change while the query is running. - ActionCableSubscriptions: accept `use(..., namespace: "...")` for running multiple schemas in the same application #3076 - Add `deprecation_reason:` to arguments #3015 ### Bug fixes - SanitizedPrinter: Fix lists and JSON scalars #3171 - Improve retained memory in Schema.from_definition #3153 - Make it easier to cache schema parsing #3153 - Make sure deprecated arguments aren't required #3137 - Use `.empty?` instead of `.length.zero?` in lexer #3134 - Return a proper error when a stack error happens #3129 - Assert valid input types on arguments #3120 - Improve Validator#validate performance #3125 - Don't wrap `RawValue` in ConnectionExtension #3122 - Fix interface possible types visibility #3124 ## 1.11.4 (24 August 2020) ### Breaking changes ### New features - Add `node_nullable` option for `edge_type` #3083 - Use module namespacing for template generators #3098 ### Bug fixes - Rescue `SystemStackError`s during validation #3107 - Add `require 'digest/sha2'` for fingerprint #3103 - Optimize `GraphQL::Query::Context#dig` #3090 - Check if new connections before calling method on it (fixes #3059) #3100 - Thread field owner type through interpreter runtime (fixes #3086) #3099 - Check for visible interfaces on the type in warden #3096 - Update `AppOpticsTracing` with latest changes in `PlatformTracing` #3097 - Use throw instead of raise to halt subscriptions early #3084 - Optimize `GraphQL::Query::Context#fetch` #3081 ## 1.11.3 (13 August 2020) ### Breaking changes - Reverted the `required` and `default_value` argument behaviour change in 1.11.2 since it was not spec compliant #3066 ### New features - Improve resolver method conflict warning #3069, #3062 - Store arguments on `Mutation` instances after they're loaded #3073 ### Bug fixes - Fix connection wrappers on lazy lists #3070 ## 1.11.2 (1 August 2020) ### Breaking changes - Previously, GraphQL-Ruby allowed _both_ `default_value: ...` and `required: true` in argument definitions. However, this definition doesn't make sense -- a default value is never used for a `required: true` argument. This configuration now raises an error. Remove the `default_value:` to get rid of the error. #3011 ### New features - Support Date, Time and OpenStruct in Subscription::Serialize #3057 ### Bug fixes - Speed up `DELETE_NODE` check #3053 - Reject invalid enum values during definition #3055 - Fix `.trigger` from unsubscribed ActionCable channel #3051 - Fix error message from VariablesAreUsedAndDefined for anonymous queries #3050 - Fix renaming variable identifiers in AST visitor #3045 - Reject `default_value: ...` used with `required: true` during definition #3011 - Use the configured `edge_class:` with new connections #3036 - Don't call visible for unused arguments #3030, #3031 - Properly load directives from introspection results #3021 - Reject interfaces as members of unions #3024 - Load deprecation reason from introspection results #3014 - Fix arguments caching when extension modify arguments #3009 ## 1.11.1 (17 June 2020) ### New Features - Add `StatsdTracing` #2996 ### Bug Fixes - Raise the proper `InvalidNullError` when a mutation field returns an invalid `nil` #2997 ## 1.11.0 (13 June 2020) ### Breaking changes - Global tracers are removed (deprecated since 1.7.4) #2936 - Fields defined in camel case (`field :doStuff`) will not line up to methods that are underscore case (`def do_stuff`). Instead, the given symbol is used _verbatim_. #2938 To work around this: - Change the name of the method to match the field (eg, `def doStuff`) - Change the name of the field to match the method (eg, `field :do_stuff`, let graphql-ruby camelize it for you) - Or, add `resolver_method: :do_stuff` to explicitly map the field to a method on the object type definition You can probably find instances of this in your application with a regexp like `/field :[a-z]+[A-Z]/`, and review them. ### New features - `extend SubscriptionRoot` is no longer necessary #2770 - Add `broadcast: true` option to subscriptions #2959 - Add `Edge#parent` to new connection classes #2961 ### Bug fixes - Use the field name as configured for hash key or method name #2906 ## 1.10.12 (13 June 2020) ### Bug fixes - Fix compatibility of `YYYY-mm-dd` with `Types::ISO8601DateTime` #2989 - Remove unused ivar in InputObject #2987 ## 1.9.21 (12 June 2020) ### Bug fixes - Fix `extras:` on subscription fields #2983 ## 1.10.11 (11 June 2020) ### New features - Scout tracer adds transaction name to traces #2969 - `resolve_type` can optionally return a resolved object #2976 - DateTime scalar returns a `Time` for better timezone handling #2973 - Interpreter memory improvements #2980, #2978 - Support lazy values from field-level authorization hooks #2977 - Object generator infers fields from model classes #2954 - Add type-specific runtime errors #2957 ### Bug fixes - Fix for error when using `extras:` with subscription fields #2984 - Improve Schema.error_handler inheritance #2975 - Add raw_value to conflict warning list #2958 - Arguments#each_value yields ArgumentValues #2956 ## 1.10.10 (20 May 2020) ### Bug Fixes - Fix lazy `loads:` with list arguments #2949 - Show object fields even when inherited ones are hidden #2950 - Use `reverse_each` in instrumenters #2945 - Fix underscored names in introspection loader #2941 - Fix array input to Date/DateTime types #2927 - Fix method conflict warnings on schema loader #2934 - Fix some Ruby 2.7 warnings #2925 ## 1.9.20 (20 May 2020) ### Bug fixes - Fix `default_value: {}` on Ruby 2.7 ## 1.10.9 (4 May 2020) ### New features - Add `Interpreter::Arguments#dig` #2912 ## 1.10.8 (27 April 2020) ### Breaking changes - With the interpreter, `Query#arguments_for` returns `Interpreter::Arguments` instances instead of plain hashes. (They should work mostly the same, though.) #2881 ### New features - `Schema::Field#introspection?` returns true for built-in introspection-related fields ### Bug fixes - Fix Ruby 2.7 warning on `Schema.to_json` #2905 - Pass `&block` to nested method calls to reduce stack depths #2900 - Fix lazy `loads:` with list arguments #2894 - Fix `loads:` on nested input object #2895 - Rescue base64 encoding errors in the encoder #2896 ## 1.10.7 (16 April 2020) ### Breaking changes - `Schema.from_introspection(...)` builds class-based schemas #2876 ### New features - `Date` and `DateTime` types also accept well-formatted strings #2848 - `Schema.from_introspection(...)` builds class-based schemas #2876 - `Schema#to_definition` now dumps all directives that were part of the original IDL, if the schema was parsed with `.from_definition` #2879 ### Bug fixes - Fix memory leak in legacy runtime #2884 - Fix interface inheritance in legacy runtime #2882 - Fix description on `List` and `NonNull` types (for introspection) #2875 - Fix over-rescue of NoMethodError when building list responses #2887 ## 1.10.6 (6 April 2020) ### New features - Add options to `implements(...)` and inteface type visibility #2791 - Add `Query#fingerprint` for logging #2859 - Add `--playground` option to install generator #2839 - Support lazy-loaded objects from input object `loads:` #2834 ### Bug fixes - Fix `Language::Nodes` equality: move `eql?` to `==` #2861 - Make rake task properly detect rails `environment` task #2862 - Fix `nil` override for `max_page_size` #2843 - Fix `pageInfo` methods when they're called before `nodes` #2845 - Make the default development error match a normal GraphQL error #2825 - Fix `loads:` with `require: false` #2833 - Fix typeerror for `BigInt` given `nil` #2827 ## 1.10.5 (12 March 2020) ### New features - Add `#field_complexity` hook to `AST::QueryComplexity` analyzer #2807 ### Bug fixes - Pass `nonce: true` when encoding cursors #2821 - Ignore empty-string cursors #2821 - Properly pass along `Analysis::AST` to schema instances #2820 - Support filtering unreachable types in schemas from IDL #2816 - Use `Query#arguments_for` for lookahead arguments #2811 - Fix pagination bug on old connections #2799 - Support new connection system on old runtime #2798 - Add details to raise CoercionErrors #2796 ## 1.10.4 (3 March 2020) ### Breaking changes - When an argument is defined with a symbol (`argument :my_arg, ...`), that symbol is used _verbatim_ to build Ruby keyword arguments. Previously it was converted to underscore-case, but this autotransform was confusing and wrong in some cases. You may have to change the symbol in your `argument(...)` configuration if you were depending on that underscorization. #2792 - Schemas from `.from_definition` previously had half-way connection support. It's now completely removed, so you have to add connection wrappers manually. See #2782 for migration notes. ### New features - Add `Appoptics` tracing #2789 - Add `Query#sanitized_query_string` #2785 - Improved duplicate type error message #2777 ### Bug fixes - Fix arguments ending in numbers, so they're injected with the same name that they're configured with #2792 - Improve `Query#arguments_for` with interpreter #2781 - Fix visitor replacement of variable definitions #2752 - Remove half-broken connection handling from `Schema.from_definition` #2782 ## 1.10.3 (17 Feb 2020) ### New features - Support `loads:` with plain field arguments #2720 - Support `raw_value(...)` to halt execution with a certain value #2699 - `.read_subscription` can return `nil` to bypass executing a subscription #2741 ### Bug fixes - Connection wrappers are properly inherited #2750 - `prepare(...)` is properly applied to default values in subscription fields #2748 - Code tidying for RSpec warnings #2741 - Include new analysis module when generating a schema #2734 - Include directive argument types in printed schemas #2733 - Use `module_parent_name` in Rails #2713 - Fix overriding default scalars in build_from_definition #2722 - Fix some non-null errors in lists #2651 ## 1.10.2 (31 Jan 2020) ### Bug fixes - Properly wrap nested input objects in instances #2710 ## 1.10.1 (28 Jan 2020) ### Bug fixes - Include Interface-level `orphan_types` when building a schema #2705 - Properly re-enter selections in complexity analyzer #2595 - Fix input objects with null values #2690 - Fix default values of `{}` in `.define`-based schemas #2703 - Fix field extension presence check #2689 - Make new relation connections more efficient #2697 - Don't include fields `@skip(if: true)` or `@include(if: false)` in lookahead #2700 ## 1.9.19 (28 Jan 2020) ### Bug Fixes - Fix argument default value of `{}` with Ruby 2.7 argument handling #2704 ## 1.10.0 (20 Jan 2020) ### Breaking Changes - Class-based schemas using the new interpreter will now use _definition classes_ at runtime. #2363 (Previously, `.to_graphql` methods were used to generate singletons which were used at runtime.) This means: - Methods that used to receive types at runtime will now receive classes instead of those singletons. - `.name` will now call `Class#name`, which will give the class name. Use `.graphql_name` to get the name of a GraphQL type. (Fields, arguments and directives have `.graphql_name` too, so you can use it everywhere.) - Some methods that return hashes are slow because they merge hashes according to class inheritance, for example `MySchema.types` and `MyObjectType.fields`. Instead: - If you only need one item out of the Hash, use `.get_type(type_name)` or `.get_field(field_name)` instead. Those methods find a match without performing Hash merges. - If you need the whole Hash, get a cached value from `context.warden` (an instance of `GraphQL::Schema::Warden`) at runtime. Those values reflect the types and fields which are permitted for the current query, and they're cached for life of the query. Check the API docs to see methods on the `warden`. - Class-based schemas using the interpreter _must_ add `use GraphQL::Analysis::AST` to their schema (and update their custom analyzers, see https://graphql-ruby.org/queries/ast_analysis.html) #2363 - ActiveSupport::Notifications events are correctly named in event.library format #2562 - Field and Argument `#authorized?` methods now accept _three_ arguments (instead of 2). They now accept `(obj, args, ctx)`, where `args` is the arguments (for a field) or the argument value (for an argument). #2520 - Double-null `!!` is disallowed by the parser #2397 - (Non-interpreter only) The return value of subscription fields is passed along to execute the subscription. Return `nil` to get the previous behavior. #2536 - `Schema.from_definition` builds a _class-based schema_ from the definition string #2178 - Only integers are accepted for `Int` type #2404 - Custom scalars now call `.coerce_input` on all input values - previously this call was skipped for `null` values. ### Deprecations - `.define` is deprecated; class-based schema definitions should be used instead. If you're having trouble or you can't find information about an upgrade path, please open an issue on GitHub! ### New Features - Add tracing events for `.authorized?` and `.resolve_type` calls #2660 - `Schema.from_definition` accepts `using:` for installing plugins (equivalent to `use ...` in class-based schemas) #2307 - Add `$` to variable names in error messages #2531 - Add invalid value to argument error message #2531 - Input object arguments with `loads:` get the loaded object in their `authorized?` hook, as `arg` in `authorized?(obj, args, ctx)`. #2536 - `GraphQL::Pagination` auto-pagination system #2143 - `Schema.from_definition` builds a _class-based schema_ from the definition string #2178 ### Bug Fixes - Fix warnings on Ruby 2.7 #2668 - Fix Ruby keyword list to support Ruby 2.7 #2640 - Reduce memory of class-based schema #2636 - Improve runtime performance of interpreter #2630 - Big numbers (ie, greater than Ruby's `Infinity`) no longer :boom: when being reserialized #2320 - Fix `hasNextPage`/`hasPrevious` page when max_page_size limits the items returned #2608 - Return parse errors for empty documents and empty argument lists #2344 - Properly serialize `defaultValue` of input objects containing enum values #2439 - Don't crash when a query contains `!!`. #2397 - Resolver `loads:` assign the value to argument `@loads` #2364 - Only integers are accepted for `Int` type #2404 ## 1.9.18 (15 Jan 2020) ### New features - Support disabling `__type` or `__schema` individually #2657 - Support Ruby 2.7, and turn on CI for it :tada: #2665 ### Bug fixes - Fix Ruby 2.7 warnings #2653 #2669 - Properly build camelized names for directive classes #2666 - Use schema-defined context class for SDL generation #2656 - Apply visibility checks when generating SDL #2637 ## 1.9.17 (17 Dec 2019) ### New features - Scoped context for propagating values to child fields #2634 - Add `type_membership_class` with possible_type visibility #2391 ### Bug fixes - Don't return unreachable types in introspection response #2596 - Wrap more of execution with error handling #2632 - Fix InputObject `.prepare` for the interpreter #2624 - Fix Ruby keyword list to support Ruby 2.7 #2640 - Fix performance of urlsafe_encode64 backport #2643 ## 1.9.16 (2 Dec 2019) ### Breaking changes - `GraphQL::Schema::Resolver#initialize` accepts a new keyword argument, `field:`. If you have overriden this method, you'll have to add that keyword to your argument list (and pass it along to `super`.) #2605 ### Deprecations - `SkylightTracing` is disabled; the Skylight agent contains its own GraphQL support. See Skylight's docs for migration. #2601 ### New features ### Bug fixes - Fix multiplex max_depth calculation #2613 - Use monotonic time in TimeoutMiddleware #2622 - Use underscored names in Mutation generator #2617 - Fix lookahead when added to mutations in their `field(...)` definitions #2605 - Handle returned lists of errors from Mutations #2567 - Fix lexer error on block strings containing only newlines #2598 - Fix mutation generator to reference the new base class #2580 - Use the right camelization configuration when generating subscription topics #2552 ## 1.9.15 (30 Oct 2019) ### New features - Improve parser performance #2572 - Add `def prepare` API for input objects #1869 - Support `extensions` config in Resolver classes #2570 - Support custom `.connection_extension` in field classes #2561 - Warn when a field name is a Ruby keyword #2559 - Improve performance for ActiveRecord connection #2547 ### Bug fixes - Fix errantly generated `def resolve_field` method in `BaseField` #2578 - Comment out the `null_session` handling in the generated controller, for better compat with Rails API mode #2557 - Fix validation error with duplicate, self-referencing fragment #2577 - Revert the `.authorized?` behavior of InputObjects to handle cyclical references. See 1.10.0.pre1 for a better behavior. #2576 - Replace `NotImplementedError` (which is meant for operating system APIs) with `GraphQL::RequiredImplementationMissingError` #2543 ## 1.9.14 (14 Oct 2019) ### New features - Add `null_session` CSRF handing in `install` generator #2524 - Correctly report InputObjects without arguments and Objects without fields as invalid #2539 #2462 ### Bug fixes - Fix argument incompatibility #2541 - Add a `require` for `Types::ISO8691Date` #2528 - Fix errors re-raised after lazy fields #2525 ## 1.9.13 (8 Oct 2019) ### Breaking changes - Enum values were (erroneously) accepted as ID or String values, but they aren't anymore. #2505 ### New features - Add `Query#executed?` #2486 - Add `Types::ISO8601Date` #2471 ### Bug fixes - Don't accept Enums as IDs or Strings #2505 - Call `.authorized?` hooks on arguments that belong to input objects #2519 - Fix backslash parsing edge case #2510 - Improve performance #2504 #2498 - Properly stringify keys in error extensions #2508 - Fix `extras:` handling in RelayClassicMutation #2484 - Use `Types::BaseField` in scaffold #2470 ## 1.9.12 (9 Sept 2019) ### Breaking Changes - AST Analyzers follow fragments spreads as if they were inline fragments. #2463 ### New Features - `use GraphQL::Execution::Errors` provides error handling for the new interpreter. #2458 ### Bug Fixes - Fix false positive on enum value validation #2454 ## 1.9.11 (29 Aug 2019) ### Breaking Changes - Introspection fields are now considered for query depth validations, so you'll need at least `max_depth: 13` to run the introspection query #2437 ### New features - Add `extras` setter to `GraphQL::Schema::Field` #2450 - Add extensions in `CoercionError` #2431 ### Bug fixes - Make `extensions` kwarg on field on more flexible for extensions with options #2443 - Fix list validation error handling #2441 - Include introspective fields in query depth calculations #2437 - Correct the example for using 'a class method to generate fields' #2435 - Enable multiple execution errors for Fields defined to return a list #2433 ## 1.9.10 (20 Aug 2019) ### New features - Support required arguments with default values #2416 ### Bug fixes - Properly disable `max_complexity` and `max_depth` when `nil` is passed #2409 - Fix printing class-based schemas #2406 - Improve field method naming conflict check #2420 ## 1.9.9 (30 July 2019) ### New features - Memoize generated strings in `.to_query_string` #2400 - Memoize generated strings in platform tracing #2401 ### Bug fixes - Support class-based subscription type in `.define`-based schema #2403 ## 1.9.8 (24 July 2019) ### New features - Schema classes pass their configuration to subclasses #2384 - Improve memory consumption of lexer and complexity validator #2389 - The `install` generator creates a BaseArgument #2379 - When a field name conflicts with a built-in method name, give a warning #2376 ### Bug fixes - When a resolver argument uses `loads:`, the argument definition will preserve the type in `.loads` #2365 - When an required argument is hidden, it won't add a validation error #2393 - Fix handling of invalid UTF-8 #2372, #2377 - Empty block strings are parsed correctly #2381 - For resolvers, only authorize arguments once #2378 ## 1.9.7 (25 June 2019) ### Breaking changes - `Analysis::AST::Visitor#argument_definition` no longer returns the _previous_ argument definition. Instead, it returns the _current_ argument definition and `#previous_argument_definition` returns the previous one. You might have to replace calls to `.argument_definition` with `.previous_argument_definition` for compatibility. #2226 ### New features - Accept a `subscription_scope` configuration in Subscription classes #2297 - Add a `disable_introspection_entry_points` configuration in Schema classes #2327 - Add `Analysis::AST::Visitor#argument_definition` which returns the _current_ argument definition, `#previous_argument_definition` returns the _previous_ one #2226 - Run CI on Ruby 2.6 #2328 - Autogenerate base field class #2216 - Add timeout support with interpreter #2220 ### Bug fixes - Fix Stack overflow when calling `.to_json` on input objects #2343 - Fix off-by-one error with hasNextPage and ArrayConnections #2349 - Fix GraphQL-Pro operation store compatibility #2350 - Fix class-based transformer when multiple mutations are in one file #2309 - Use `default_graphql_name` for Edge classes #2224 - Support nested `loads:` with input objects #2323 - Support `max_complexity` with multiplex & AST analysis #2306 ## 1.9.6 (23 May 2019) ### Bug fixes - Backport `String#-@` for Ruby 2.2 support #2305 ## 1.9.5 (22 May 2019) ### New features - Support `rescue_from` returning `GraphQL::ExecutionError` #2140 - Accept `context:` in `Schema.validate` #2256 - Include `query:` in interpreter tracing for `execute_field` and `execute_field_lazy` #2236 - Add `Types::JSON` #2227 - Add `null:` option to `BaseEdge.node_type` #2249 ### Bug fixes - Fix Ruby 2.2 compatibility #2302 - Distinguish aliased selections in lookahead #2266 - Properly show list enum default values in introspection #2263 - Performance improvements: #2289, #2244, #2258, #2257, #2240 - Don't recursively unwrap inputs for RelayClassicMutation #2236 - Fix `Schema::Field#scoped?` when no return type #2255 - Properly forward more authorization errors #2165 - Raise `ParseError` for `.parse(nil)` #2238 ## 1.9.4 (5 Apr 2019) ### Breaking Changes - `GraphQL::Schema::Resolver::LoadApplicationObjectFailedError` was renamed to `GraphQL::LoadApplicationObjectFailedError`. (This will only break if you're referencing the class by name and running Ruby 2.5+) #2080 ### New features - Add `Types::BigInt` #2150 - Add auto-loading arguments support in Input Object types #2080 - Add analytics tag to Datadog tracing #2154 ### Bug fixes - Fix `Query#execute` when no explicit query string is passed in #2142 - Fix when a root type returns nil because unauthorized #2144 - Fix tracing `node` by threading `owner:` through field tracing #2156 - Fix interpreter handling of exceptions raised during argument preparation #2198 - Fix ActionCableLink when there are errors but no data #2176 - Provide empty hash as default option for field resolvers #2189 - Prevent argument names from overwriting Arguments methods #2171 - Include array indices in error paths #2162 - Handle non-node arrays in AST visitor #2161 ## 1.9.3 (20 Feb 2019) ### Bug fixes - Fix `Schema::Subscription` when it has no arguments #2135 - Don't try to scope `nil`, just skip scoping altogether #2134 - Fix when a root `.authorized?` returns `false` and there's no `root_value` #2136 - Fix platform tracing with interpreter & introspection #2137 - Support root Subscription types with name other than `Subscription` #2102 - Fix nested list-type input object nullability validation #2123 ## 1.9.2 (15 Feb 2019) ### Bug fixes - Properly support connection fields with resolve procs #2115 ## 1.9.1 (14 Feb 2019) ### Bug fixes - Properly pass errors to Resolver `load_application_object_failed` methods #2110 ## 1.9.0 (13 Feb 2019) ### Breaking Changes - AST nodes are immutable. To modify a parsed GraphQL query, see `GraphQL::Language::Visitor` for its mutation API, which builds a new AST with the specified mutations applied. #1338, #1740 - Cursors use urlsafe Base64. This won't break your clients (it's backwards-compatible), but it might break your tests, so it's listed here. #1698 - Add `field(..., resolver_method:)` for when GraphQL-Ruby should call a method _other than_ the one whose name matches the field name (#1961). This means that if you're using `method:` to call a different method _on the Schema::Object subclass_, you should update that configuration to `resolver_method:`. (`method:` is still used to call a different method on the _underlying application object_.) - `Int` type now applies boundaries as [described in the spec](https://facebook.github.io/graphql/June2018/#sec-Int) #2101. To preserve the previous, unbounded behavior, handle the error in your schema's `.type_error(err, ctx)` hook, for example: ```ruby class MySchema < GraphQL::Schema def self.type_error(err, ctx) if err.is_a?(GraphQL::IntegerEncodingError) # Preserve the previous unbounded behavior # by returning the out-of-bounds value err.integer_value else super end end end ``` - `field(...)` configurations don't create implicit method definitions (#1961). If one resolver method depended on the implicitly-created method from another field, you'll have to refactor that call or manually add a `def ...` for that field. - Calling `super` in a field method doesn't work anymore (#1961) - Error `"problems"` are now in `"extensions" : { "problems": ... }` #2077 - Change schema default to `error_bubbling false` #2069 ### New Features - Add class-based subscriptions with `GraphQL::Schema::Subscription` #1930 - Add `GraphQL::Execution::Interpreter` (#1394) and `GraphQL::Analysis::AST` (#1824) which together cut GraphQL overhead by half (time and memory) - Add `Schema.unauthorized_field(err)` for when `Field#authorized?` checks fail (#1994) - Add class-based custom directives for the interpreter (#2055) - Add `Schema::FieldExtension` for customizing field execution with class-based fields #1795 - Add `Query#lookahead` for root-level selection info #1931 - Validation errors have `"extensions": { ... }` which includes metadata about that error #1970 ### Bug fixes - Fix list-type arguments passed with a single value #2085 - Support `false` as an Enum value #2050 - Support `hash_key:` fields when the key isn't a valid Ruby method name #2016 ## 1.8.15 (13 Feb 2019) ### Bug fixes - Fix unwrapping inputobject types when turning arguments to hashes #2094 - Support lazy objects from `.resolve_type` hooks #2108 ## 1.8.14 (9 Feb 2019) ### Bug Fixes - Fix single-item list inputs that aren't passed as lists #2095 ## 1.8.13 (4 Jan 2019) ### Bug fixes - Fix regression in block string parsing #2032 ## 1.8.12 (3 Jan 2019) ### Breaking changes - When an input object's argument has a validation error, that error is reported on the _argument_ instead of its parent input object. #2013 ### New features - Add `error_bubbling false` Schema configuration for nicer validation of compound inputs #2013 - Print descriptions as block strings in SDL #2011 - Improve string-to-constant resolution #1810 - Add `Query::Context#to_hash` for splatting #1955 - Add `#dig` to `Schema::InputObject` and `Query::Arguments` #1968 - Add `.*_execution_strategy` methods to class-based schemas #1914 - Accept multiple errors when adding `.rescue_from` handlers #1991 ### Bug fixes - Fix scalar tracing in NewRelic and Skylight #1954 - Fix lexer for multiple block strings #1937 - Add `unscope(:order)` when counting relations #1911 - Improve build-from-definition error message #1998 - Fix regression in legacy compat #2000 ## 1.8.11 (16 Oct 2018) ### New features - `extras: [:lookahead]` injects a `GraphQL::Execution::Lookahead` ### Bug fixes - Fix type printing in Printer #1902 - Rescue `GraphQL::ExecutionError` in `.before_query` hooks #1898 - Properly load default values that are lists of input objects from the IDL #1874 ## 1.8.10 (21 Sep 2018) ### Bug fixes - When using `loads:` with a nullable mutation input field, allow `null` values to be provided. #1851 - When an invalid Base64 encoded cursor is provided, raise a `GraphQL::ExecutionError` instead of `ArgumentError`. #1855 - Fix an issue with `extras: [:path]` would use the field's `path` instead of the `context`. #1859 ### New features - Add scalar type generator `rails g graphql:scalar` #1847 - Add `#dig` method to `Query::Context` #1861 ## 1.8.9 (13 Sep 2018) ### Breaking changes - When `field ... ` is called with a block and the block has one argument, the field is yielded, but `self` inside the block is _not_ changed to the field. #1843 ### New features - `extras: [...]` can inject values from the field instance #1808 - Add `ISO8601DateTime.time_precision` for customization #1845 - Fix input objects with default values of enum #1827 - `Schema.sync_lazy(value)` hook for intercepting lazy-resolved objects #1784 ### Bug fixes - When a field block is provided with an arity of `1`, yield the field #1843 ## 1.8.8 (27 Aug 2018) ### Bug fixes - When using `RelayClassicMutation`, `client_mutation_id` will no longer be passed to `authorized?` method #1771 - Fix issue in schema upgrader script which would cause `.to_non_null_type` calls in type definition to be ignored #1783 - Ensure enum values respond to `graphql_name` #1792 - Fix infinite resolution bug that could occur when an exception not inheriting from `StandardError` is thrown #1804 ### New features - Add `#path` method to schema members #1766 - Add `as:` argument to allow overriding the name of the argument when using `loads:` #1773 - Add support for list of IDs when using `loads:` in an argument definition #1797 ## 1.8.7 (9 Aug 2018) ### Breaking changes - Some mutation authorization hooks added in 1.8.5 were changed, see #1736 and #1737. Roughly: - `before_prepare` was changed to `#ready?` - `validate_*` hooks were replaced with a single `#authorized?` method ### Bug fixes - Argument default values include nested default values #1728 - Clean up duplciate method defs #1739 ### New features - Built-in support for Mongoid 5, 6, 7 #1754 - Mutation `#ready?` and `#authorized?` may halt flow and/or return data #1736, #1737 - Add `.scope_items(items, ctx)` hook for filtering lists - Add `#default_graphql_name` for overriding default logic #1729 - Add `#add_argument` for building schemas #1732 - Cursors are decoded using `urlsafe_decode64` to future-proof for urlsafe cursors #1748 ## 1.8.6 (31 July 2018) ### Breaking changes - Only allow Objects to implement actual Interfaces #1715. Use `include` instead for plain Ruby modules. - Revert extending interface methods onto Objects #1716. If you were taking advantage of this feature, you can create a plain Ruby module with the functionality and include it in both the interface and object. ### Deprecations ### New features - Support string descriptions (from June 2018 GraphQL spec) #1725 - Add some accessors to Schema members #1722 - Yield argument for definition block with arity of one #1714 - Yield field for definition blocks with arity of one #1712 - Support grouping by "endpoint" with skylight instrumentation #1663 - Validation: Don't traverse irep if no handlers are registered #1696 - Add `nodes_field` option to `edge_type` to hide nodes field #1693 - Add `GraphQL::Types::ISO8601DateTime` to documentation #1694 - Conditional Analyzers #1690 - Improve error messages in `ActionCableSubscriptions` #1675 - Add Prometheus tracing #1672 - Add `map` to `InputObject` #1669 ### Bug fixes - Improve the mutation generator #1718 - Fix method inheritance for interfaces #1709 - Fix Interface inheritance chain #1686 - Fix require in `tracing.rb` #1685 - Remove delegate for `FieldResolutionContext#schema` #1682 - Remove duplicated `object_class` method #1667 ## 1.8.5 (10 July 2018) ### Breaking changes - GraphQL validation errors now include `"filename"` if the parsed document had a `filename` #1618 ### Deprecations - `TypeKind#resolves?` is deprecated in favor of `TypeKind#abstract?` #1619 ### New features - Add Mutation loading/authorization system #1609 - Interface `definition_methods` are inherited by object type classes #1635 - include `"filename"` in GraphQL errors if the parsed document has a filename #1618 - Add `Schema::InputObject#empty?` #1651 - require `ISO8601DateTime` by default #1660 - Support `extend` in the parser #1620 - Improve generator to have nicer error handling in development ### Bug fixes - Fix `@skip`/`@include` with default value of `false` #1617 - Fix lists of abstract types with promises #1613 - Don't check the type of `nil` when it's in a list #1610 - Fix NoMethodError when `variables: nil` is passed to `execute(...)` #1661 - Objects returned from `Schema.unauthorized_objects` are properly wrapped by their type proxies #1662 ## 1.8.4 (21 June 2018) ### New features - Add class-based definitions for Relay types #1568 - Add a built-in auth system #1494 ### Bug fixes - Properly rescue coercion errors in variable values #1602 ## 1.8.3 (14 June 2018) ### New features - Add an ISO 8601 DateTime scalar: `Types::ISO8601DateTime`. #1566 - Use classes under the hood for built-in scalars. These are now accessible via `Types::` namespace. #1565 - Add `possible_types` helpers to abstract types #1580 ### Bug fixes - Fix `Language::Visitor` when visiting `InputObjectTypeDefinition` nodes to include child `Directive` nodes. #1584 - Fix an issue preventing proper subclassing of `TimeoutMiddleware`. #1579 - Fix `graphql:interface` generator such that it generates working code. #1577 - Update the description of auto-generated `before` and `after` arguments to better describe their input type. #1572 - Add `Language::Nodes::DirectiveLocation` AST node to represent directive locations in directive definitions. #1564 ## 1.8.2 (6 June 2018) ### Breaking changes - `Schema::InputObject#to_h` recursively transforms hashes to underscorized, symbolized keys. #1555 ### New features - Generators create class-based types #1562 - `Schema::InputObject#to_h` returns a underscorized, symbolized hash #1555 ### Bug fixes - Support `default_mask` in class-based schemas #1563 - Fix null propagation for list types #1558 - Validate unique arguments in queries #1557 - Fix `RelayClassicMutation`s with no arguments #1543 ## 1.8.1 (1 June 2018) ### Breaking changes - When filtering items out of a schema, Unions will now be hidden if their possible types are all hidden or if all fields returning it are hidden. #1515 ### New features - `GraphQL::ExecutionError.new` accepts an `extensions:` option which will be merged into the `"extensions"` key in that error's JSON #1552 ### Bug fixes - When filtering items out of a schema, Unions will now be hidden if their possible types are all hidden or if all fields returning it are hidden. #1515 - Require that fields returning interfaces have selections made on them #1551 - Correctly mark introspection types and fields as `introspection?` #1535 - Remove unused introspection objects #1534 - use `object`/`context` in the upgrader instead of `@object`/`@context` #1529 - (Development) Don't require mongodb for non-mongo tests #1548 - Track position of union member nodes in the parser #1541 ## 1.8.0 (17 May 2018) `1.8.0` has been in prerelease for 6 months. See the prerelease changelog for change-by-change details. Here's a high-level changelog, followed by a detailed list of changes since the last prerelease. ### High-level changes #### Breaking Changes - GraphQL-Ruby is not tested on Ruby 2.1. #1070 Because Ruby 2.1 doesn't garbage collect Symbols, it's possible that GraphQL-Ruby will introduce a OOM vulnerability where unique symbols are dynamically created, for example, turning user input into Symbols. No instances of this are known in GraphQL-Ruby ... yet! - `GraphQL::Delegate`, a duplicate of Ruby's `Forwardable`, was removed. Use `Forwardable` instead, and update your Ruby if you're on `2.4.0`, due to a performance regression in `Forwardable` in that version. - `MySchema.subscriptions.trigger` asserts that its inputs are valid arguments #1400. So if you were previously passing invalid options there, you'll get an error. Remove those options. #### New Features - A new class-based API for schema definition. The old API is completely supported, but the new one is much nicer to use. If you migrate, some schema extensions may require a bit of extra work. - Built-in support for Mongoid-backed Relay connections - `.execute(variables: ...)` and `subscriptions.trigger` both accept Symbol-keyed hashes - Lots of other small things around SDL parsing, tracing, runtime ... everything. Read the details below for a full list. #### Bug Fixes - Many, many bug fixes. See the detailed list if you're curious about specific bugs. ### Changes since `1.8.0.pre11`: #### Breaking Changes - `GraphQL::Schema::Field#initialize`'s signature changed to accept keywords and a block only. `type:`, `description:` and `name:` were moved to keywords. See `Field.from_options` for how the `field(...)` helper's arguments are merged to go to `Field.new`. #1508 #### New Features - `Schema::Resolver` is a replacement for `GraphQL::Function` #1472 - Fix subscriptions with class-based schema #1478 - `Tracing::NewRelicTracing` accepts `set_transaction_name:` to use the GraphQL operation name as the NewRelic transaction name #1430 #### Bug fixes - Backported `accepts_definition`s are inherited #1514 - Fix Schema generator's `resolve_type` method #1481 - Fix constant assignment warnings with interfaces including multiple other interfaces #1465 - InputObject types loaded from SDL have the proper AST node assigned to them #1512 ## 1.8.0.pre11 (3 May 2018) ### Breaking changes - `Schema::Mutation.resolve_mutation` was moved to an instance method; see changes to `Schema::RelayClassicMutation` in #1469 for an example refactor - `GraphQL::Delegate` was removed, use Ruby's `Forwardable` instead (warning: bad performance on Ruby 2.4.0) - `GraphQL::Schema::Interface` is a module, not a class #1372. To refactor, use a base module instead of a base class: ```ruby module BaseInterface include GraphQL::Schema::Interface end ``` And include that in your interface types: ```ruby module Reservable include BaseInterface field :reservations, ... end ``` In object types, no change is required; use `implements` as before: ```ruby class EventVenue < BaseObject implements Reservable end ``` ### New features - `GraphQL::Schema::Interface` is a module - Support `prepare:` and `as:` argument options #1469 - First-class support for Mongoid connections #1452 - More type inspection helpers for class-based types #1446 - Field methods may call `super` to get the default behavior #1437 - `variables:` accepts symbol keys #1401 - Reprint any directives which were parsed from SDL #1417 - Support custom JSON scalars #1398 - Subscription `trigger` accepts symbol, underscored arguments and validates their presence #1400 - Mutations accept a `null(true | false)` setting to affect field nullability #1406 - `RescueMiddleware` uses inheritance to match errors #1393 - Resolvers may return a list of errors #1231 ### Bug fixes - Better error for anonymous class names #1459 - Input Objects correctly inherit arguments #1432 - Fix `.subscriptions` for class-based Schemas #1391 ## 1.8.0.pre10 (4 Apr 2018) ### New features - Add `Schema::Mutation` and `Schema::RelayClassicMutation` base classes #1360 ### Bug fixes - Fix using anonymous classes for field types #1358 ## 1.8.0.pre9 (19 Mar 2018) - New version number. (I needed this because I messed up build tooling for 1.8.0.pre8). ## 1.8.0.pre8 (19 Mar 2018) ### New Features - Backport `accepts_definition` for configurations #1357 - Add `#owner` method to Schema objects - Add `Interface.orphan_types` config for orphan types #1346 - Add `extras: :execution_errors` for `add_error` #1313 - Accept a block to `Schema::Argument#initialize` #1356 ### Bug Fixes - Support `cursor_encoder` #1357 - Don't double-count lazy/eager field time in Tracing #1321 - Fix camelization to support single leading underscore #1315 - Fix `.resolve_type` for Union and Interface classes #1342 - Apply kwargs before block in `Argument.from_dsl` #1350 ## 1.8.0.pre7 (27 Feb 2018) ### New features - Upgrader improvements #1305 - Support `global_id_field` for interfaces #1299 - Add `camelize: false` #1300 - Add readers for `context`, `object` and `arguments` #1283 - Replace `Schema.method_missing` with explicit whitelist #1265 ## 1.8.0.pre6 (1 Feb 2018) ### New features - Custom enum value classes #1264 ### Bug fixes - Properly print SDL type directives #1255 ## 1.8.0.pre5 (1 Feb 2018) ### New features - Upgrade argument access with the upgrader #1251 - Add `Schema#find(str)` for finding schema members by name #1232 ### Bug fixes - Fix `Schema.max_complexity` #1246 - Support cyclical connections/edges #1253 ## 1.8.0.pre4 (18 Jan 2018) ### Breaking changes - `Type.fields`, `Field.arguments`, `Enum.values` and `InputObject.arguments` return a Hash instead of an Array #1222 ### New features - By default, fields try hash keys which match their name, as either a symbol or a string #1225 - `field do ... end` instance_evals on the Field instance, not a FieldProxy #1227 - `[T, null: true]` creates lists with nullable items #1229 - Upgrader improvements #1223 ### Bug fixes - Don't require `parser` unless the upgrader is run #1218 ## 1.8.0.pre3 (12 Jan 2018) ### New Features - Custom `Context` classes for class-based schemas #1161 - Custom introspection for class-based schemas #1170 - Improvements to upgrader tasks and internals #1151, #1178, #1212 - Allow description inside field blocks #1175 ## 1.8.0.pre2 (29 Nov 2017) ### New Features - Add `rake graphql:upgrade[app/graphql]` for automatic upgrade #1110 - Automatically camelize field names and argument names #1143, #1126 - Improved error message when defining `name` instead of `graphql_name` #1104 ### Bug fixes - Fix list wrapping when value is `nil` #1117 - Fix ArgumentError typo #1098 ## 1.8.0.pre1 (14 Nov 2017) ### Breaking changes - Stop official support for Ruby 2.1 #1070 ### New features - Add class-based schema definition API #1037 ## 1.7.14 (4 Apr 2018) ### New features - Support new IDL spec for `&` for interfaces #1304 - Schema members built from IDL have an `#ast_node` #1367 ### Bug fixes - Fix paging backwards with `hasNextPage` #1319 - Add hint for `orphan_types` in error message #1380 - Use an empty hash for `result` when a query has unhandled errors #1382 ## 1.7.13 (28 Feb 2018) ### Bug fixes - `Schema#as_json` returns a hash, not a `GraphQL::Query::Result` #1288 ## 1.7.12 (13 Feb 2018) ### Bug fixes - `typed_children` should always return a Hash #1278 ## 1.7.11 (13 Feb 2018) ### Bug fixes - Fix compatibility of `irep_node.typed_children` on leaf nodes #1277 ## 1.7.10 (13 Feb 2018) ### Breaking Changes - Empty selections (`{ }`) are invalid in the GraphQL spec, but were previously allowed by graphql-ruby. They now return a parse error. #1268 ### Bug fixes - Fix error when inline fragments are spread on scalars #1268 - Fix printing SDL when types have interfaces and directives #1255 ## 1.7.9 (1 Feb 2018) ## New Features - Support block string inputs #1219 ## Bug fixes - Fix deprecation regression in schema printer #1250 - Fix resource names in DataDog tracing #1208 - Fix passing `context` to multiplex in `Query#result` #1200 ## 1.7.8 (11 Jan 2018) ### New features - Refactor `Schema::Printer` to use `Language::Printer` #1159 - Add `ArgumentValue#default_used?` and `Arguments#default_used?` #1152 ### Bug fixes - Fix Scout Tracing #1187 - Call `#inspect` for `EnumType::UnresolvedValueError` #1179 - Parse empty field sets in IDL parser #1145 ## 1.7.7 (29 Nov 2017) ### New features - `Schema#to_document` returns a `Language::Nodes::Document` #1134 - Add `trace_scalars` and `trace: true|false` to monitoring #1103 - Add `Tracing::DataDogPlatform` monitoring #1129 - Support namespaces in `rails g graphql:function` and `:loader` #1127 - Support `serializer:` option for `ActionCableSubscriptions` #1085 ### Bug fixes - Properly count the column after a closing quote #1136 - Fix default value input objects in `Schema.from_definition` #1135 - Fix `rails destroy graphql:mutation` #1119 - Avoid unneeded query in RelationConnection with Sequel #1101 - Improve & document instrumentation stack behavior #1101 ## 1.7.6 (13 Nov 2017) ### Bug fixes - Serialize symbols in with `GraphQL::Subscriptions::Serialize` #1084 ## 1.7.5 (7 Nov 2017) ### Breaking changes - Rename `Backtrace::InspectResult#inspect` to `#inspect_result` #1022 ### New features - Improved website search with Algolia #934 - Support customized generator directory #1047 - Recursively serialize `GlobalID`-compliant objects in Arrays and hashes #1030 - Add `Subscriptions#build_id` helper #1046 - Add `#non_null?` and `#list?` helper methods to type objects #1054 ### Bug fixes - Fix infinite loop in query instrumentation when error is raised #1074 - Don't try to trace error when it's not raised during execution - Improve validation of query variable definitions #1073 - Fix Scout tracing module load order #1064 ## 1.7.4 (9 Oct 2017) ### Deprecations - `GraphQL::Tracing.install` is deprecated, use schema-local or query-local tracers instead #996 ### New features - Add monitoring plugins for AppSignal, New Relic, Scout and Skylight #994, #1013 - Custom coercion errors for custom scalars #988 - Extra `options` for `GraphQL::ExecutionError` #1002 - Use `GlobalID` for subscription serialization when available #1004 - Schema- and query-local, threadsafe tracers #996 ### Bug fixes - Accept symbol-keyed arguments to `.trigger` #1009 ## 1.7.3 (20 Sept 2017) ### Bug fixes - Fix arguments on `Query.__type` field #978 - Fix `Relay::Edge` objects in `Backtrace` tables #975 ## 1.7.2 (20 Sept 2017) ### Bug fixes - Correctly skip connections that return `ctx.skip` #972 ## 1.7.1 (18 Sept 2017) ### Bug fixes - Properly release changes from 1.7.0 ## 1.7.0 (18 Sept 2017) ### Breaking changes - `GraphQL::Result` is the returned from GraphQL execution. #898 `Schema#execute` and `Query#result` both return a `GraphQL::Result`. It implements Hash-like methods to preserve compatibility. ### New features - `puts ctx.backtrace` prints out a GraphQL backtrace table #946 - `GraphQL::Backtrace.enable` wraps unhandled errors with GraphQL backtraces #946 - `GraphQL::Relay::ConnectionType.bidrectional_pagination = true` turns on _true_ bi-directional pagination checks for `hasNextPage`/`hasPreviousPage` fields. This will become the default behavior in a future version. #960 - Field arguments may be accessed as methods on the `args` object. This is an alternative to `#[]` syntax which provides did-you-mean behavior instead of returning `nil` on a typo. #924 For example: ```ruby # using hash syntax: args[:limit] # => 10 args[:limittt] # => nil # using method syntax: args.limit # => 10 args.limittt # => NoMethodError ``` The old syntax is _not_ deprecated. - Improvements to schema filters #919 - If a type is not referenced by anything, it's hidden - If a type is an abstract type, but has no visible members, it's hidden - `GraphQL::Argument.define` builds re-usable arguments #948 - `GraphQL::Subscriptions` provides hooks for subscription platforms #672 - `GraphQL::Subscriptions::ActionCableSubscriptions` implements subscriptions over ActionCable #672 - More runtime values are accessble from a `ctx` object #923 : - `ctx.parent` returns the `ctx` from the parent field - `ctx.object` returns the current `obj` for that field - `ctx.value` returns the resolved GraphQL value for that field These can be used together, for example, `ctx.parent.object` to get the parent object. - `GraphQL::Tracing` provides more hooks into gem internals for performance monitoring #917 - `GraphQL::Result` provides access to the original `query` and `context` after executing a query #898 ### Bug fixes - Prevent passing _both_ query string and parsed document to `Schema#execute` #957 - Prevent invalid names for types #947 ## 1.6.8 (8 Sept 2017) ### Breaking changes - Validate against EnumType value names to match `/^[_a-zA-Z][_a-zA-Z0-9]*$/` #915 ### New features - Use stdlib `forwardable` when it's not Ruby 2.4.0 #926 - Improve `UnresolvedTypeError` message #928 - Add a default field to the Rails generated mutation type #922 ### Bug fixes - Find types via directive arguments when traversing the schema #944 - Assign `#connection?` when building a schema from IDL #941 - Initialize `@edge_class` to `nil` #942 - Disallow invalid enum values #915 - Disallow doubly-nested non-null types #916 - Fix `Query#selected_operation_name` when no selections are present #899 - Fix needless `COUNT` query for `hasNextPage` #906 - Fix negative offset with `last` argument #907 - Fix line/col for `ArgumentsAreDefined` validation #890 - Fix Sequel error when limit is `0` #892 ## 1.6.7 (11 Aug 2017) ### New features - Add `GraphQL.parse_file` and `AbstractNode#filename` #873 - Support `.graphql` filepaths with `Schema.from_definition` #872 ### Bug fixes - Fix variable usage inside non-null list #888 - Fix unqualified usage of ActiveRecord::Relation #885 - Fix `FieldsWillMerge` handling of equivalent input objects - Fix to call `prepare:` on nested input types ## 1.6.6 (14 Jul 2017) ### New features - Validate `graphql-pro` downloads with `rake graphql:pro:validate[$VERSION]` #846 ### Bug fixes - Remove usage of Rails-only `Array.wrap` #840 - Fix `RelationConnection` to count properly when relation contains an alias #838 - Print name of Enum type when a duplicate value is added #843 ## 1.6.5 (13 Jul 2017) ### Breaking changes - `Schema#types[](type_name)` returns `nil` when there's no type named `type_name` (it used to raise `RuntimeError`). To get an error for missing types, use `.fetch` instead, for example: ```ruby # Old way: MySchema.types[type_name] # => may raise RuntimeError # New way: MySchema.types.fetch(type_name) # => may raise KeyError ``` - Schema build steps happen in one pass instead of two passes #819 . This means that `instrument(:field)` hooks may not access `Schema#types`, `Schema#possible_types` or `Schema#get_field`, since the underlying data hasn't been prepared yet. There's not really a clear upgrade path here. It's a bit of a mess. If you're affected by this, feel free to open an issue and we'll try to find something that works! ### Deprecations - `Schema#resolve_type` is now called with `(abstract_type, obj, ctx)` instead of `(obj, ctx)` #834 . To update, add an unused parameter to the beginning of your `resolve_type` hook: ```ruby MySchema = GraphQL::Schema.define do # Old way: resolve_type ->(obj, ctx) { ... } # New way: resolve_type ->(type, obj, ctx) { ... } end ``` ### New features - `rails g graphql:mutation` will add Mutation boilerplate if it wasn't added already #812 - `InterfaceType` and `UnionType` both accept `resolve_type ->(obj, ctx) { ... }` functions for type-specific resolution. This function takes precedence over `Schema#resolve_type` #829 #834 - `Schema#resolve_type` is called with three arguments, `(abstract_type, obj, ctx)`, so you can distinguish object type based on interface or union. - `Query#operation_name=` may be assigned during query instrumentation #833 - `query.context.add_error(err)` may be used to add query-level errors #833 ### Bug fixes - `argument(...)` DSL accepts custom keywords #809 - Use single-query `max_complexity` overrides #812 - Return a client error when `InputObjectType` receives an array as input #803 - Properly handle raised errors in `prepare` functions #805 - Fix using `as` and `prepare` in `argument do ... end` blocks #817 - When types are added to the schema with `instrument(:field, ...)`, make sure they're in `Schema#types` #819 - Raise an error when duplicate `EnumValue` is created #831 - Properly resolve all query levels breadth-first when using `lazy_resolve` #835 - Fix tests to run on PostgresQL; Run CI on PostgresQL #814 - When no query string is present, return a client error instead of raising `ArgumentError` #833 - Properly validate lists containing variables #824 ## 1.6.4 (20 Jun 2017) ### New features - `Schema.to_definition` sorts fields and arguments alphabetically #775 - `validate: false` skips static validations in query execution #790 ### Bug fixes - `graphql:install` adds `operation_name: params[:operationName]` #786 - `graphql:install` skips `graphiql-rails` for API-only apps #772 - `SerialExecution` calls `.is_a?(Skip)` to avoid user-defined `#==` methods #794 - `prepare:` functions which return `ExecutionError` are properly handled when default values are present #801 ## 1.6.3 (7 Jun 2017) ### Bug fixes - Run multiplex instrumentation when running a single query with a legacy execution strategy #766 - Check _each_ strategy when looking for overridden execution strategy #765 - Correctly wrap `Method`s with BackwardsCompatibility #763 - Various performance improvements #764 - Don't call `#==(other)` on user-provided objects (use `.is_a?` instead) #761 - Support lazy object from custom connection `#edge_nodes` #762 - If a lazy field returns an invalid null, stop evaluating its siblings #767 ## 1.6.2 (2 Jun 2017) ### New features - `Schema.define { default_max_page_size(...) }` provides a Connection `max_page_size` when no other is provided #752 - `Schema#get_field(type, field)` accepts a string type name #756 - `Schema.define { rescue_from(...) }` accepts multiple error classes for the handler #758 ### Bug fixes - Use `*_execution_strategy` when executing a single query (doesn't support `Schema#multiplex`) #755 - Fix NameError when `ActiveRecord` isn't loaded #747 - Fix `Query#mutation?` etc to support lazily-loaded AST #754 ## 1.6.1 (28 May 2017) ### New Features - `Query#selected_operation_name` returns the operation to execute, even if it was inferred (not provided as `operation_name:`) #746 ### Bug fixes - Return `nil` from `Query#operation_name` if no `operation_name:` was provided #746 ## 1.6.0 (27 May 2017) ### Breaking changes - `InternalRepresentation::Node#return_type` will now return the wrapping type. Use `return_type.unwrap` to access the old value #704 - `instrument(:query, ...)` instrumenters are applied as a stack instead of a queue #735. If you depend on queue-based behavior, move your `before_query` and `after_query` hooks to separate instrumenters. - In a `Relay::Mutation`, Raising or returning a `GraphQL::Execution` will nullify the mutation field, not the field's children. #731 - `args.to_h` returns a slightly different hash #714 - keys are always `String`s - if an argument is aliased with `as:`, the alias is used as the key - `InternalRepresentation::Node#return_type` includes the original "wrapper" types (non-null or list types), call `.unwrap` to get the inner type #20 ```ruby # before irep_node.return_type # after irep_node.return_type.unwrap ``` ### Deprecations - Argument `prepare` functions which take one argument are deprecated #730 ```ruby # before argument :id, !types.ID, prepare: ->(val) { ... } # after argument :id, !types.ID, prepare: ->(val, ctx) { ... } ``` ### New features - `Schema#multiplex(queries)` runs multiple queries concurrently #691 - `GraphQL::RakeTask` supports dumping the schema to IDL or JSON #687 - Improved support for `Schema.from_definition` #699 : - Custom scalars are supported with `coerce_input` and `coerce_result` functions - `resolve_type` function will be used for abstract types - Default resolve behavior is to check `obj` for a method and call it with 0, 1, or 2 arguments. - `ctx.skip` may be returned from field resolve functions to exclude the field from the response entirely #688 - `instrument(:field, ..., after_built_ins: true)` to apply field instrumentation after Relay wrappers #740 - Argument `prepare` functions are invoked with `(val, ctx)` (previously, it was only `(val)`) #730 - `args.to_h` returns stringified, aliased arguments #714 - `ctx.namespace(:my_namespace)` provides namespaced key-value storage #689 - `GraphQL::Query` can be initialized without a query_string; it can be added after initialization #710 - Improved filter support #713 - `Schema.execute(only:, except:)` accept a callable _or_ an array of callables (multiple filters) - Filters can be added to a query via `Query#merge_filters(only:, except:)`. You can add a filter to every query by merging it in during query instrumentation. ### Bug fixes - Correctly apply cursors and `max_page_size` in `Relay::RelationConnection` and `Relay::ArrayConnection` #728 - Nullify a mutation field when it raises or returns an error #731 ## 1.5.14 (27 May 2017) ### New features - `UniqueWithinType` Relay ID generator supports `-` in the ID #742 - `assign_metadata_key` assigns `true` when the definition method is called without arguments #724 - Improved lexer performance #737 ### Bug fixes - Assign proper `parent` when a `connection` resolve returns a promise #736 ## 1.5.13 (11 May 2017) - Fix raising `ExecutionError` inside mutation resolve functions (it nullifies the field) #722 ## 1.5.12 (9 May 2017) - Fix returning `nil` from connection resolve functions (now they become `null`) #719 - Fix duplicate AST nodes when merging fragments #721 ## 1.5.11 (8 May 2017) ### New features - `Schema.from_definition` accepts a `parser:` option (to work around lack of schema parser in `graphql-libgraphqlparser`) #712 - `Query#internal_representation` exposes an `InternalRepresentation::Document` #701 - Update generator usage of `graphql-batch` #697 ### Bug fixes - Handle fragments with the same name as operations #706 - Fix type generator: ensure type name is camelized #718 - Fix `Query#operation_name` to return the operation name #707 - Fix pretty-print of non-null & list types #705 - Fix single input objects passed to list-type arguments #716 ## 1.5.10 (25 Apr 2017) ### New features - Support Rails 5.1 #693 - Fall back to `String#encode` for non-UTF-8/non-ASCII strings #676 ### Bug Fixes - Correctly apply `Relay::Mutation`'s `return_field ... property:` argument #692 - Handle Rails 5.1's `ActionController::Parameters` #693 ## 1.5.9 (19 Apr 2017) ### Bug Fixes - Include instrumentation-related changes in introspection result #681 ## 1.5.8 (18 Apr 2017) ### New features - Use Relay PageInfo descriptions from graphql-js #673 ### Bug Fixes - Allow fields with different arguments when fragments are included within inline fragments of non-overlapping types #680 - Run `lazy_resolve` instrumentation for `connection` fields #679 ## 1.5.7 (14 Apr 2017) ### Bug fixes - `InternalRepresentation::Node#definition` returns `nil` instead of raising NoMethodError for operation fields #675 - `Field#function` is properly populated for fields derived from `GraphQL::Function`s #674 ## 1.5.6 (9 Apr 2017) ## Breaking Changes - Returned strings which aren't encoded as UTF-8 or ASCII will raise `GraphQL::StringEncodingError` instead of becoming `nil` #661 To preserve the previous behavior, Implement `Schema#type_error` to return `nil` for this error, eg: ```ruby GraphQL::Schema.define do type_error ->(err, ctx) { case err # ... when GraphQL::StringEncodingError nil end } ``` - `coerce_non_null_input` and `validate_non_null_input` are private #667 ## Deprecations - One-argument `coerce_input` and `coerce_result` functions for custom scalars are deprecated. #667 Those functions now accept a second argument, `ctx`. ```ruby # From ->(val) { val.to_i } # To: ->(val, ctx) { val.to_i } ``` - Calling `coerce_result`, `coerce_input`, `valid_input?` or `validate_input` without a `ctx` is deprecated. #667 Use `coerce_isolated_result` `coerce_isolated_input`, `valid_isolated_input?`, `validate_input` to explicitly bypass `ctx`. ## New Features - Include `#types` in `GraphQL::Function` #654 - Accept `prepare:` function for arguments #646 - Scalar coerce functions receive `ctx` #667 ## Bug Fixes - Properly apply default values of `false` #658 - Fix application of argument options in `GraphQL::Relay::Mutation` #660 - Support concurrent-ruby `>1.0.0` #663 - Only raise schema validation errors on `#execute` to avoid messing with Rails constant loading #665 ## 1.5.5 (31 Mar 2017) ### Bug Fixes - Improve threadsafety of `lazy_resolve` cache, use `Concurrent::Map` if it's available #631 - Properly handle unexpeced input objects #638 - Handle errors during definition by preseriving the definition #632 - Fix `nil` input for nullable list types #637, #639 - Handle invalid schema IDL with a validation error #647 - Properly serialize input object default values #635 - Fix `as:` on mutation `input_field` #650 - Fix null propagation for `nil` members of non-null list types #649 ## 1.5.4 (22 Mar 2017) ### Breaking Changes - Stop supporting deprecated one-argument schema masks #616 ### Bug Fixes - Return a client error for unknown variable types when default value is provided or when directives are present #627 - Fix validation performance regression on nested abstract fragment conditions #622, #624 - Put back `InternalRepresentation::Node#parent` and fix it for fragment fields #621 - Ensure enum names are strings #619 ## 1.5.3 (20 Mar 2017) ### Bug Fixes - Fix infinite loop triggered by user input. #620 This query would cause an infinite loop: ```graphql query { ...frag } fragment frag on Query { __typename } fragment frag on Query { ...frag } ``` - Validate fragment name uniqueness #618 ## 1.5.2 (16 Mar 2017) ### Breaking Changes - Parse errors are no longer raised to the application. #607 Instead, they're returned to the client in the `"errors"` key. To preserve the previous behavior, you can implement `Schema#parse_error` to raise the error: ```ruby MySchema = GraphQL::Schema.define do # ... parse_error ->(err, ctx) { raise(err) } end ``` ### New Features - Add `graphq:enum` generator #611 - Parse errors are returned to the client instead of raised #607 ### Bug Fixes - Handle negative cursor pagination args as `0` #612 - Properly handle returned `GraphQL::ExecutionError`s from connection resolves #610 - Properly handle invalid nulls in lazy scalar fields #609 - Properly handle invalid input objects passed to enum arguments #604 - Fix introspection response of enum default values #605 - Allow `Schema.from_definition` default resolver hashes to have defaults #608 ## 1.5.1 (12 Mar 2017) ### Bug fixes - Fix rewrite performance regressions from 1.5.0 #599 - Remove unused `GraphQL::Execution::Lazy` initialization API #597 ## 1.5.0 (10 Mar 2017), yanked ### Breaking changes - _Only_ UTF-8-encoded strings will be returned by `String` fields. Strings with other encodings (or objects whose `#to_s` method returns a string with a different encoding) will return `nil` instead of that string. #517 To opt into the _previous_ behavior, you can modify `GraphQL::STRING_TYPE`: ```ruby # app/graphql/my_schema.rb # Restore previous string behavior: GraphQL::STRING_TYPE.coerce_result = ->(value) { value.to_s } MySchema = GraphQL::Schema.define { ... } ``` - Substantial changes to the internal query representation (#512, #536). Query analyzers may notice some changes: - Nodes skipped by directives are not visited - Nodes are always on object types, so `Node#owner_type` always returns an object type. (Interfaces and Unions are replaced with concrete object types which are valid in the current scope.) See [changes to `Analysis::QueryComplexity`](https://github.com/rmosolgo/graphql-ruby/compare/v1.4.5...v1.5.0#diff-8ff2cdf0fec46dfaab02363664d0d201) for an example migration. Here are some other specific changes: - Nodes are tracked on object types only, not interface or union types - Deprecated, buggy `Node#children` and `Node#path` were removed - Buggy `#included` was removed - Nodes excluded by directives are entirely absent from the rewritten tree - Internal `InternalRepresentation::Selection` was removed (no longer needed) - `Node#spreads` was replaced by `Node#ast_spreads` which returns a Set ### New features - `Schema#validate` returns a list of errors for a query string #513 - `implements ...` adds interfaces to object types _without_ inherit-by-default #548, #574 - `GraphQL::Relay::RangeAdd` for implementing `RANGE_ADD` mutations #587 - `use ...` definition method for plugins #565 - Rails generators #521, #580 - `GraphQL::Function` for reusable resolve behavior with arguments & return type #545 - Support for Ruby 2.4 #475 - Relay `node` & `nodes` field can be extended with a custom block #552 - Performance improvements: - Resolve fragments only once when validating #504 - Reuse `Arguments` objects #500 - Skip needless `FieldResult`s #482 - Remove overhead from `ensure_defined` #483 - Benchmark & Profile tasks for gem maintenance #520, #579 - Fetch `has_next_page` while fetching items in `RelationConnection` #556 - Merge selections on concrete object types ahead of time #512 - Support runnable schemas with `Schema.from_definition` #567, #584 ### Bug fixes - Support different arguments on non-overlapping typed fragments #512 - Don't include children of `@skip`ped nodes when parallel branches are not skipped #536 - Fix offset in ArrayConnection when it's larger than the array #571 - Add missing `frozen_string_literal` comments #589 ## 1.4.5 (6 Mar 2017) ### Bug Fixes - When an operation name is provided but no such operation is present, return an error (instead of executing the first operation) #563 - Require unique operation names #563 - Require selections on root type #563 - If a non-null field returns `null`, don't resolve any more sibling fields. #575 ## 1.4.4 (17 Feb 2017) ### New features - `Relay::Node.field` and `Relay::Node.plural_field` accept a custom `resolve:` argument #550 - `Relay::BaseConnection#context` provides access to the query context #537 - Allow re-assigning `Field#name` #541 - Support `return_interfaces` on `Relay::Mutation`s #533 - `BaseType#to_definition` stringifies the type to IDL #539 - `argument ... as:` can be used to alias an argument inside the resolve function #542 ### Bug fixes - Fix negative offset from cursors on PostgresQL #510 - Fix circular dependency issue on `.connection_type`s #535 - Better error when `Relay::Mutation.resolve` doesn't return a Hash ## 1.4.3 (8 Feb 2017) ### New features - `GraphQL::Relay::Node.plural_field` finds multiple nodes by UUID #525 ### Bug fixes - Properly handle errors from lazy mutation results #528 - Encode all parsed strings as UTF-8 #516 - Improve error messages #501 #519 ## 1.4.2 (23 Jan 2017) ### Bug fixes - Absent variables aren't present in `args` (_again_!) #494 - Ensure definitions were executed when accessing `Field#resolve_proc` #502 (This could have caused errors when multiple instrumenters modified the same field in the schema.) ## 1.4.1 (16 Jan 2017) ### Bug fixes - Absent variables aren't present in `args` #479 - Fix grouped ActiveRecord relation with `last` only #476 - `Schema#default_mask` & query `only:`/`except:` are combined, not overriden #485 - Root types can be hidden with dynamic filters #480 ## 1.4.0 (8 Jan 2017) ### Breaking changes ### Deprecations - One-argument schema filters are deprecated. Schema filters are now called with _two_ arguments, `(member, ctx)`. #463 To update, add a second argument to your schema filter. - The arity of middleware `#call` methods has changed. Instead of `next_middleware` being the last argument, it is passed as a block. To update, call `yield` to continue the middleware chain or use `&next_middleware` to capture `next_middleware` into a local variable. ```ruby # Previous: def call(*args, next_middleware) next_middleware.call end # Current def call(*args) yield end # Or def call(*args, &next_middleware) next_middleware.call end ``` ### New features - You can add a `nodes` field directly to a connection. #451 That way you can say `{ friends { nodes } }` instead of `{ freinds { edges { node } } }`. Either pass `nodes_field: true` when defining a custom connection type, for example: ```ruby FriendsConnectionType = FriendType.define_connection(nodes_field: true) ``` Or, set `GraphQL::Relay::ConnectionType.default_nodes_field = true` before defining your schema, for example: ```ruby GraphQL::Relay::ConnectionType.default_nodes_field = true MySchema = GraphQL::Schema.define { ... } ``` - Middleware performance was dramatically improved by reducing object allocations. #462 `next_middleware` is now passed as a block. In general, [`yield` is faster than calling a captured block](https://github.com/JuanitoFatas/fast-ruby#proccall-and-block-arguments-vs-yieldcode). - Improve error messages for wrongly-typed variable values #423 - Cache the value of `resolve_type` per object per query #462 - Pass `ctx` to schema filters #463 - Accept whitelist schema filters as `only:` #463 - Add `Schema#to_definition` which accepts `only:/except:` to filter the schema when printing #463 - Add `Schema#default_mask` as a default `except:` filter #463 - Add reflection methods to types #473 - `#introspection?` marks built-in introspection types - `#default_scalar?` marks built-in scalars - `#default_relay?` marks built-in Relay types - `#default_directive?` marks built-in directives ### Bug fixes - Fix ArrayConnection: gracefully handle out-of-bounds cursors #452 - Fix ArrayConnection & RelationConnection: properly handle `last` without `before` #362 ## 1.3.0 (8 Dec 2016) ### Deprecations - As per the spec, `__` prefix is reserved for built-in names only. This is currently deprecated and will be invalid in a future version. #427, #450 ### New features - `Schema#lazy_resolve` allows you to define handlers for a second pass of resolution #386 - `Field#lazy_resolve` can be instrumented to track lazy resolution #429 - `Schema#type_error` allows you to handle `InvalidNullError`s and `UnresolvedTypeErrors` in your own way #416 - `Schema#cursor_encoder` can be specified for transforming cursors from built-in Connection implementations #345 - Schema members `#dup` correctly: they shallowly copy their state into new instances #444 - `Query#provided_variables` is now public #430 ### Bug fixes - Schemas created from JSON or strings with custom scalars can validate queries (although they still can't check if inputs are valid for those custom scalars) #445 - Always use `quirks_mode: true` when serializing values (to support non-stdlib `JSON`s) #449 - Calling `#redefine` on a Schema member copies state outside of previous `#define` blocks (uses `#dup`) #444 ## 1.2.6 (1 Dec 2016) ### Bug fixes - Preserve connection behaviors after `redefine` #421 - Implement `respond_to_missing?` on `DefinedObjectProxy` (which is `self` inside `.define { ... }`) #414 ## 1.2.5 (22 Nov 2016) ### Breaking changes - `Visitor` received some breaking changes, though these are largely-private APIs (#401): - Global visitor hooks (`Visitor#enter` and `Visitor#leave`) have been removed - Returning `SKIP` from a visitor hook no longer skips sibling nodes ### New features - `Schema#instrument` may be called outside of `Schema.define` #399 - Validation: assert that directives on a node are unique #409 - `instrument(:query)` hooks are executed even if the query raises an error #412 ### Bug fixes - `Mutation#input_fields` should trigger lazy definition #392 - `ObjectType#connection` doesn't modify the provided `GraphQL::Field` #411 - `Mutation#resolve` may return a `GraphQL::ExecutionError` #405 - `Arguments` can handle nullable arguments passed as `nil` #410 ## 1.2.4 (14 Nov 2016) ### Bug fixes - For invalid enum values, print the enum name in the error message (not a Ruby object dump) #403 - Improve detection of invalid UTF-8 escapes #394 ## 1.2.3 (14 Nov 2016) ### Bug fixes - `Lexer` previous token should be a local variable, not a method attribute #396 - `Arguments` should wrap values according to their type, not their value #398 ## 1.2.2 (7 Nov 2016) ### New features - `Schema.execute` raises an error if `variables:` is a string ### Bug fixes - Dynamic fields `__schema`, `__type` and `__typename` are properly validated #391 ## 1.2.1 (7 Nov 2016) ### Bug fixes - Implement `Query::Context#strategy` and `FieldResolutionContext#strategy` to support GraphQL::Batch #382 ## 1.2.0 (7 Nov 2016) ### Breaking changes - A breaking change from 1.1.0 was reverted: two-character `"\\u"` _is_ longer treated as the Unicode escape character #372 - Due to the execution bug described below, the internal representation of a query has changed. Although `Node` responds to the same methods, tree is built differently and query analyzers visit it differently. #373, #379 The difference is in cases like this: ```graphql outer { ... on A { inner1 { inner2 } } ... on B { inner1 { inner3 } } } ``` Previously, visits would be: - `outer`, which has one child: - `inner1`, which has two definitions (one on `A`, another on `B`), then visit its two `children`: - `inner2` which has one definition (on the return type of `inner1`) - `inner3` which has one definition (on the return type of `inner1`) This can be wrong for some cases. For example, if `A` and `B` are mutually exclusive (both object types, or union types with no shared members), then `inner2` and `inner3` will never be executed together. Now, the visit goes like this: - `outer` which has two entries in `typed_children`, one on `A` and another on `B`. Visit each `typed_chidren` branch: - `inner1`, then its one `typed_children` branch: - `inner2` - `inner1`, then its one `typed_children` branch: - `inner3` As you can see, we visit `inner1` twice, once for each type condition. `inner2` and `inner3` are no longer visited as siblings. Instead they're visited as ... cousins? (They share a grandparent, not a parent.) Although `Node#children` is still present, it may not contain all children actually resolved at runtime, since multiple `typed_children` branches could apply to the same runtime type (eg, two branches on interface types can apply to the same object type). To track all children, you have to do some bookkeeping during visitation, see `QueryComplexity` for an example. You can see PR #373 for how built-in analyzers were changed to reflect this. ### Deprecations - `InternalRepresentation::Node#children` and `InternalRepresentation::Node#definitions` are deprecated due to the bug described below and the breaking change described above. Instead, use `InternalRepresentation::Node#typed_children` and `InternalRepresentation::Node#defininition`. #373 ### New features - `null` support for the whole library: as a query literal, variable value, and argument default value. To check for the presence of a nullable, use `Arguments#key?` #369 - `GraphQL::Schema::UniqueWithinType.default_id_separator` may be assigned to a custom value #381 - `Context#add_error(err)` may be used to add a `GraphQL::ExecutionError` to the response's `"errors"` key (and the resolve function can still return a value) #367 - The third argument of `resolve` is now a `FieldResolutionContext`, which behaves just like a `Query::Context`, except that it is not modified during query execution. This means you can capture a reference to that context and access some field-level details after the fact: `#path`, `#ast_node`, `#irep_node`. (Other methods are delegated to the underlying `Query::Context`) #379 - `TimeoutMiddleware`'s second argument is a _proxied_ query object: it's `#context` method returns the `FieldResolutionContext` (see above) for the timed-out field. Other methods are delegated to the underlying `Query` #379 ### Bug fixes - Fix deep selection merging on divergently-typed fragments. #370, #373, #379 Previously, nested selections on different fragments were not distinguished. Consider a case like this: ```graphql ... on A { inner1 { inner2 } } ... on B { inner1 { inner3 } } ``` Previously, an object of type `A` would resolve `inner1`, then the result would receive _both_ `inner2` and `inner3`. The same was true for an object of type `B`. Now, those are properly distinguished. An object of type `A` resolves `inner1`, then its result receives `inner2`. An object of type `B` receives `inner1`, then `inner3`. ## 1.1.0 (1 Nov 2016) ### Breaking changes - Two-character `"\\u"` is no longer treated as the Unicode escape character, only the Unicode escape character `"\u"` is treated that way. (This behavior was a bug, the migration path is to use the Unicode escape character.) #366 - `GraphQL::Language::ParserTests` was removed, use `GraphQL::Compatibility` instead. #366 - Non-null arguments can't be defined with default values, because those values would never be used #361 ### New features - `Schema.from_definition(definition_string)` builds a `GraphQL::Schema` out of a schema definition. #346 - Schema members (types, fields, arguments, enum values) can be hidden on a per-query basis with the `except:` option #300 - `GraphQL::Compatibility` contains `.build_suite` functions for testing user-provided parsers and execution strategies with GraphQL internals. #366 - Schema members respond to `#redefine { ... }` for making shallow copies with extended definitions. #357 - `Schema#instrument` provides an avenue for observing query and field resolution with no overhead. - Some `SerialExecution` objects were converted to functions, resulting in a modest performance improvement for query resolution. ### Bug fixes - `NonNullType` and `ListType` have no name (`nil`), as per the spec #355 - Non-null arguments can't be defined with default values, because those values would never be used #361 ## 1.0.0 (25 Oct 2016) ### Breaking changes - `validate: false` option removed from `Schema.execute` (it didn't work anyways) #338 - Some deprecated methods were removed: #349 - `BaseConnection#object` was removed, use `BaseConnection#nodes` - `BaseConnection.connection_for_items` was removed, use `BaseConnection#connection_for_nodes` - Two-argument resolve functions for `Relay::Mutation`s are not supported, use three arguments instead: `(root_obj, input, ctx)` - `Schema.new` no longer accepts initialization options, use `Schema.define` instead - `GraphQL::ObjectType::UnresolvedTypeError` was removed, use `GraphQL::UnresolvedTypeError` instead - Fragment type conditions should be parsed as `TypeName` nodes, not strings. (Users of `graphql-libgraphqlparser` should update to `1.0.0` of that gem.) #342 ### New Features - Set `ast_node` and `irep_node` on query context before sending it to middleware #348 - Enum values can be extended with `.define` #341 ### Bug Fixes - Use `RelationConnection` for Rails 3 relations (which also extend `Array`) #343 - Fix schema printout when arguments have comments #335 ## 0.19.4 (18 Oct 2016) ### Breaking changes - `Relay::BaseConnection#order` was removed (it always returned `nil`) #313 - In the IDL, Interface names & Union members are parsed as `TypeName` nodes instead of Strings #322 ### New features - Print and parse descriptions in the IDL #305 - Schema roots from IDL are omitted when their names match convention #320 - Don't add `rescue_middleware` to a schema if it's not using `rescue_from` #328 - `Query::Arguments#each_value` yields `Query::Argument::ArgumentValue` instances which contain key, value and argument definition #331 ### Bug fixes - Use `JSON.generate(val, quirks_mode: true)` for compatibility with other JSON implementations #316 - Improvements for compatibility with 1.9.3 branch #315 #314 #313 - Raise a descriptive error when calculating a `cursor` for a node which isn't present in the connection's members #327 ## 0.19.3 (13 Oct 2016) ### Breaking Changes - `GraphQL::Query::Arguments.new` requires `argument_definitions:` of type `{String => GraphQL::Argument }` #304 ### Deprecations - `Relay::Mutation#resolve` has a new signature. #301 Previously, it was called with two arguments: ```ruby resolve ->(inputs, ctx) { ... } ``` Now, it's called with three inputs: ```ruby resolve ->(obj, inputs, ctx) { ... } ``` `obj` is the value of `root_value:` given to `Schema#execute`, as with other root-level fields. Two-argument resolvers are still supported, but they are deprecated and will be removed in a future version. ### New features - `Relay::Mutation` accepts a user-defined `return_type` #310 - `Relay::Mutation#resolve` receives the `root_value` passed to `Schema#execute` #301 - Derived `Relay` objects have descriptions #303 ### Bug fixes - Introspection query is 7 levels deep instead of 3 #308 - Unknown variable types cause validation errors, not runtime errors #310 - `Query::Arguments` doesn't wrap hashes from parsed scalars (fix for user-defined "JSONScalar") #304 ## 0.19.2 (6 Oct 2016) ### New features - If a list entry has a `GraphQL::ExecutionError`, replace the entry with `nil` and return the error #295 ### Bug fixes - Support graphql-batch rescuing `InvalidNullError`s #296 - Schema printer prints Enum names, not Ruby values for enums #297 ## 0.19.1 (4 Oct 2016) ### Breaking changes - Previously-deprecated `InterfaceType#resolve_type` hook has been removed, use `Schema#resolve_type` instead #290 ### New features - Eager-load schemas at definition time, validating types & schema-level hooks #289 - `InvalidNullError`s contain the type & field name that returned null #293 - If an object is resolved with `Schema#resolve_type` and the resulting type is not a member of the expected possible types, raise an error #291 ### Bug fixes - Allow `directive` as field or argument name #288 ## 0.19.0 (30 Sep 2016) ### Breaking changes - `GraphQL::Relay::GlobalNodeIdentification` was removed. Its features were moved to `GraphQL::Schema` or `GraphQL::Relay::Node`. The new hooks support more robust & flexible global IDs. #243 - Relay's `"Node"` interface and `node(id: "...")` field were both moved to `GraphQL::Relay::Node`. To use them in your schema, call `.field` and `.interface`. For example: ```ruby # Adding a Relay-compliant `node` field: field :node, GraphQL::Relay::Node.field ``` ```ruby # This object type implements Relay's `Node` interface: interfaces [GraphQL::Relay::Node.interface] ``` - UUID hooks were renamed and moved to `GraphQL::Schema`. You should define `id_from_object` and `object_from_id` in your `Schema.define { ... }` block. For example: ```ruby MySchema = GraphQL::Schema.define do # Fetch an object by UUID object_from_id ->(id, ctx) { MyApp::RelayLookup.find(id) } # Generate a UUID for this object id_from_object ->(obj, type_defn, ctx) { MyApp::RelayLookup.to_id(obj) } end ``` - The new hooks have no default implementation. To use the previous default, use `GraphQL::Schema::UniqueWithinType`, for example: ```ruby MySchema = GraphQL::Schema.define do object_from_id ->(id, ctx) { # Break the id into its parts: type_name, object_id = GraphQL::Schema::UniqueWithinType.decode(id) # Fetch the identified object # ... } id_from_object ->(obj, type_defn, ctx) { # Provide the type name & the object's `id`: GraphQL::Schema::UniqueWithinType.encode(type_defn.name, obj.id) } end ``` If you were using a custom `id_separator`, it's now accepted as an input to `UniqueWithinType`'s methods, as `separator:`. For example: ```ruby # use "---" as a ID separator GraphQL::Schema::UniqueWithinType.encode(type_name, object_id, separator: "---") GraphQL::Schema::UniqueWithinType.decode(relay_id, separator: "---") ``` - `type_from_object` was previously deprecated and has been replaced by `Schema#resolve_type`. You should define this hook in your schema to return a type definition for a given object: ```ruby MySchema = GraphQL::Schema.define do # ... resolve_type ->(obj, ctx) { # based on `obj` and `ctx`, # figure out which GraphQL type to use # and return the type } end ``` - `Schema#node_identification` has been removed. - `Argument` default values have been changed to be consistent with `InputObjectType` default values. #267 Previously, arguments expected GraphQL values as `default_value`s. Now, they expect application values. (`InputObjectType`s always worked this way.) Consider an enum like this one, where custom values are provided: ```ruby PowerStateEnum = GraphQL::EnumType.define do name "PowerState" value("ON", value: 1) value("OFF", value: 0) end ``` __Previously__, enum _names_ were provided as default values, for example: ```ruby field :setPowerState, PowerStateEnum do # Previously, the string name went here: argument :newValue, default_value: "ON" end ``` __Now__, enum _values_ are provided as default values, for example: ```ruby field :setPowerState, PowerStateEnum do # Now, use the application value as `default_value`: argument :newValue, default_value: 1 end ``` Note that if you __don't have custom values__, then there's no change, because the name and value are the same. Here are types that are affected by this change: - Custom scalars (previously, the `default_value` was a string, now it should be the application value, eg `Date` or `BigDecimal`) - Enums with custom `value:`s (previously, the `default_value` was the name, now it's the value) If you can't replace `default_value`s, you can also use a type's `#coerce_input` method to translate a GraphQL value into an application value. For example: ```ruby # Using a custom scalar, "Date" # PREVIOUSLY, provide a string: argument :starts_on, DateType, default_value: "2016-01-01" # NOW, transform the string into a Date: argument :starts_on, DateType, default_value: DateType.coerce_input("2016-01-01") ``` ### New features - Support `@deprecated` in the Schema language #275 - Support `directive` definitions in the Schema language #280 - Use the same introspection field descriptions as `graphql-js` #284 ### Bug fixes - Operation name is no longer present in execution error `"path"` values #276 - Default values are correctly dumped & reloaded in the Schema language #267 ## 0.18.15 (20 Sep 2016) ### Breaking changes - Validation errors no longer have a `"path"` key in their JSON. It was renamed to `"fields"` #264 - `@skip` and `@include` over multiple selections are handled according to the spec: if the same field is selected multiple times and _one or more_ of them would be included, the field will be present in the response. Previously, if _one or more_ of them would be skipped, it was absent from the response. #256 ### New features - Execution errors include a `"path"` key which points to the field in the response where the error occurred. #259 - Parsing directives from the Schema language is now supported #273 ### Bug fixes - `@skip` and `@include` over multiple selections are now handled according to the spec #256 ## 0.18.14 (20 Sep 2016) ### Breaking changes - Directives are no longer considered as "conflicts" in query validation. This is in conformity with the spec, but a change for graphql-ruby #263 ### Features - Query analyzers may emit errors by raising `GraphQL::AnalysisError`s during `#call` or returning a single error or an array of errors from `#final_value` #262 ### Bug fixes - Merge fields even when `@skip` / `@include` are not identical #263 - Fix possible infinite loop in `FieldsWillMerge` validation #261 ## 0.18.13 (19 Sep 2016) ### Bug fixes - Find infinite loops in nested contexts, too #258 ## 0.18.12 (19 Sep 2016) ### New features - `GraphQL::Analysis::FieldUsage` can be used to check for deprecated fields in the query analysis phase #245 ### Bug fixes - If a schema receives a query on `mutation` or `subscription` but that root doesn't exist, return a validation error #254 - `Query::Arguments#to_h` only includes keys that were provided in the query or have a default value #251 ## 0.18.11 (11 Sep 2016) ### New features - `GraphQL::Language::Nodes::Document#slice(operation_name)` finds that operation and its dependencies and puts them in a new `Document` #241 ### Bug fixes - Validation errors for non-existent fields have the location of the field usage, not the parent field #247 - Properly `require "forwardable"` #242 - Remove `ALLOWED_CONSTANTS` for boolean input, use a plain comparison #240 ## 0.18.10 (9 Sep 2016) ### New features - Assign `#mutation` on objects which are derived from a `Relay::Mutation` #239 ## 0.18.9 (6 Sep 2016) ### Bug fixes - fix backward compatibility for `type_from_object` #238 ## 0.18.8 (6 Sep 2016) ### New features - AST nodes now respond to `#eql?(other)` to test value equality #231 ### Bug fixes - The `connection` helper no longer adds a duplicate field #235 ## 0.18.7 (6 Sep 2016) ### New features - Support parsing nameless fragments (but not executing them) #232 ### Bug fixes - Allow `__type(name: "Whatever")` to return null, as per the spec #233 - Include a Relay mutation's description with a mutation field #225 ## 0.18.6 (29 Aug 2016) ### New features - ` GraphQL::Schema::Loader.load(schema_json)` turns an introspection result into a `GraphQL::Schema` #207 - `.define` accepts plural definitions for: object fields, interface fields field arguments, enum values #222 ## 0.18.5 (27 Aug 2016) ### Deprecations - `Schema.new` is deprecated; use `Schema.define` instead. Before: ```ruby schema = GraphQL::Schema.new( query: QueryType, mutation: MutationType, max_complexity: 100, types: [ExtraType, OtherType] ) schema.node_identification = MyGlobalID schema.rescue_from(ActiveRecord::RecordNotFound) { |err| "..." } ``` After: ```ruby schema = GraphQL::Schema.define do query QueryType mutation MutationType max_complexity 100 node_identification MyGlobalID rescue_from(ActiveRecord::RecordNotFound) { |err| "..." } # Types was renamed to `orphan_types` to avoid conflict with the `types` helper orphan_types [ExtraType, OtherType] end ``` This unifies the disparate methods of configuring a schema and provides new, more flexible design space. It also adds `#metadata` to schemas for user-defined storage. - `UnionType#resolve_type`, `InterfaceType#resolve_type`, and `GlobalNodeIdentification#type_from_object` are deprecated, unify them into `Schema#resolve_type` instead. Before: ```ruby GraphQL::Relay::GlobalNodeIdentification.define do type_from_object ->(obj) { ... } end GraphQL::InterfaceType.define do resolve_type ->(obj, ctx) { ... } end ``` After: ```ruby GraphQL::Schema.define do resolve_type ->(obj, ctx) { ... } end ``` This simplifies type inference and prevents unexpected behavior when different parts of the schema resolve types differently. ### New features - Include expected type in Argument errors #221 - Define schemas with `Schema.define` #208 - Define a global object-to-type function with `Schema#resolve_type` #216 ### Bug fixes ## 0.18.4 (25 Aug 2016) ### New features - `InvalidNullError`s expose a proper `#message` #217 ### Bug fixes - Return an empty result for queries with no operations #219 ## 0.18.3 (22 Aug 2016) ### Bug fixes - `Connection.new(:field)` is optional, not required #215 - 0.18.2 introduced a more restrictive approach to resolving interfaces & unions; revert that approach #212 ## 0.18.2 (17 Aug 2016) ### New features - Connection objects expose the `GraphQL::Field` that created them via `Connection#field` #206 ## 0.18.1 (7 Aug 2016) ### Deprecations - Unify `Relay` naming around `nodes` as the items of a connection: - `Relay::BaseConnection.connection_for_nodes` replaces `Relay::BaseConnection.connection_for_items` - `Relay::BaseConnection#nodes` replaces `Relay::BaseConnection#object` ### New features - Connection fields' `.resolve_proc` is an instance of `Relay::ConnectionResolve` #204 - Types, fields and arguments can store arbitrary values in their `metadata` hashes #203 ## 0.18.0 (4 Aug 2016) ### Breaking changes - `graphql-relay` has been merged with `graphql`, you should remove `graphql-relay` from your gemfile. #195 ### Deprecations ### New features - `GraphQL.parse` can turn schema definitions into a `GraphQL::Language::Nodes::Document`. The document can be stringified again with `Document#to_query_string` #191 - Validation errors include a `path` to the part of the query where the error was found #198 - `.define` also accepts keywords for each helper method, eg `GraphQL::ObjectType.define(name: "PostType", ...)` ### Bug fixes - `global_id_field`s have default complexity of 1, not `nil` - Relay `pageInfo` is correct for connections limited by `max_page_size` - Rescue invalid variable errors & missing operation name errors during query analysis ## 0.17.2 (26 Jul 2016) ### Bug fixes - Correctly spread fragments when nested inside other fragments #194 ## 0.17.1 (26 Jul 2016) ### Bug fixes - Fix `InternalRepresentation::Node#inspect` ## 0.17.0 (21 Jul 2016) ### Breaking changes - `InternalRepresentation::Node` API changes: - `#definition_name` returns the field name on field nodes (while `#name` may have an alias) - `#definitions` returns `{type => field}` pairs for possible fields on this node - `#definition` is gone, it is equivalent to `node.definitions.values.first` - `#on_types` is gone, it is equivalent to `node.definitions.keys` ### New features - Accept `hash_key:` field option - Call `.define { }` block lazily, so `-> { }` is not needed for circular references #182 ### Bug fixes - Support `on` as an Enum value - If the same field is requested on multiple types, choose the maximum complexity among them (not the first) ## 0.16.1 (20 Jul 2016) ### Bug fixes - Fix merging fragments on Union types (see #190, broken from #180) ## 0.16.0 (14 Jul 2016) ### Breaking changes & deprecations - I don't _know_ that this breaks anything, but `GraphQL::Query::SerialExecution` now iterates over a tree of `GraphQL::InternalRepresentation::Node`s instead of an AST (`GraphQL::Language::Nodes::Document`). ### New features - Query context keys can be assigned with `Context#[]=` #178 - Cancel further field resolution with `TimeoutMiddleware` #179 - Add `GraphQL::InternalRepresentation` for normalizing queries from AST #180 - Analyze the query before running it #180 - Assign complexity cost to fields, enforce max complexity before running it #180 - Log max complexity or max depth with `MaxComplexity` or `MaxDepth` analyzers #180 - Query context exposes `#irep_node`, the internal representation of the current node #180 ### Bug fixes - Non-null errors are propagated to the next nullable field, all the way up to `data` #174 ## 0.15.3 (28 Jun 2016) ### New features - `EnumValue`s can receive their properties after instantiation #171 ## 0.15.2 (16 Jun 2016) ### New features - Support lazy type arguments in Object's `interfaces` and Union's `possible_types` #169 ### Bug fixes - Support single-member Unions, as per the spec #170 ## 0.15.1 (15 Jun 2016) ### Bug fixes - Whitelist operation types in `lexer.rb` ## 0.15.0 (11 Jun 2016) ### Breaking changes & deprecations - Remove `debug:` option, propagate all errors. #161 ## 0.14.1 (11 Jun 2016) ### Breaking changes & deprecations - `debug:` is deprecated (#165). Propagating errors (`debug: true`) will become the default behavior. You can get a similar implementation of error gobbling with `CatchallMiddleware`. Add it to your schema: ```ruby MySchema.middleware << GraphQL::Schema::CatchallMiddleware ``` ### New features ### Bug fixes - Restore previous introspection fields on DirectiveType as deprecated #164 - Apply coercion to input default values #162 - Proper Enum behavior when a value isn't found ## 0.14.0 (31 May 2016) ### Breaking changes & deprecations ### New features - `GraphQL::Language::Nodes::Document#to_query_string` will re-serialize a query AST #151 - Accept `root_value:` when running a query #157 - Accept a `GraphQL::Language::Nodes::Document` to `Query.new` (this allows you to cache parsed queries on the server) #152 ### Bug fixes - Improved parse error messages #149 - Improved build-time validation #150 - Raise a meaningful error when a Union or Interface can't be resolved during query execution #155 ## 0.13.0 (29 Apr 2016) ### Breaking changes & deprecations - "Dangling" object types are not loaded into the schema. The must be passed in `GraphQL::Schema.new(types: [...])`. (This was deprecated in 0.12.1) ### New features - Update directive introspection to new spec #121 - Improved schema validation errors #113 - 20x faster parsing #119 - Support inline fragments without type condition #123 - Support multiple schemas composed of the same types #142 - Accept argument `description` and `default_value` in the block #138 - Middlewares can send _new_ arguments to subsequent middlewares #129 ### Bug fixes - Don't leak details of internal errors #120 - Default query `context` to `{}` #133 - Fixed list nullability validation #131 - Ensure field names are strings #128 - Fix `@skip` and `@include` implementation #124 - Interface membership is not shared between schemas #142 ## 0.12.1 (26 Apr 2016) ### Breaking changes & deprecations - __Connecting object types to the schema _only_ via interfaces is deprecated.__ It will be unsupported in the next version of `graphql`. Sometimes, object type is only connected to the Query (or Mutation) root by being a member of an interface. In these cases, bugs happen, especially with Rails development mode. (And sometimes, the bugs don't appear until you deploy to a production environment!) So, in a case like this: ```ruby HatInterface = GraphQL::ObjectType.define do # ... end FezType = GraphQL::ObjectType.define do # ... interfaces [HatInterface] end QueryType = GraphQL::ObjectType.define do field :randomHat, HatInterface # ... end ``` `FezType` can only be discovered by `QueryType` _through_ `HatInterface`. If `fez_type.rb` hasn't been loaded by Rails, `HatInterface.possible_types` will be empty! Now, `FezType` must be passed to the schema explicitly: ```ruby Schema.new( # ... types: [FezType] ) ``` Since the type is passed directly to the schema, it will be loaded right away! ### New features ### Bug fixes ## 0.12.0 (20 Mar 2016) ### Breaking changes & deprecations - `GraphQL::DefinitionConfig` was replaced by `GraphQL::Define` #116 - Many scalar types are more picky about which inputs they allow (#115). To get the previous behavior, add this to your program: ```ruby # Previous coerce behavior for scalars: GraphQL::BOOLEAN_TYPE.coerce = ->(value) { !!value } GraphQL::ID_TYPE.coerce = ->(value) { value.to_s } GraphQL::STRING_TYPE.coerce = ->(value) { value.to_s } # INT_TYPE and FLOAT_TYPE were unchanged ``` - `GraphQL::Field`s can't be renamed because `#resolve` may depend on that name. (This was only a problem if you pass the _same_ `GraphQL::Field` instance to `field ... field:` definitions.) - `GraphQL::Query::DEFAULT_RESOLVE` was removed. `GraphQL::Field#resolve` handles that behavior. ### New features - Can override `max_depth:` from `Schema#execute` - Base `GraphQL::Error` for all graphql-related errors ### Bug fixes - Include `""` for String default values (so it's encoded as a GraphQL string literal) ## 0.11.1 (6 Mar 2016) ### New features - Schema `max_depth:` option #110 - Improved validation errors for input objects #104 - Interfaces provide field implementations to object types #108 ## 0.11.0 (28 Feb 2016) ### Breaking changes & deprecations - `GraphQL::Query::BaseExecution` was removed, you should probably extend `SerialExecution` instead #96 - `GraphQL::Language::Nodes` members no longer raise if they don't get inputs during `initialize` #92 - `GraphQL.parse` no longer accepts `as:` for parsing partial queries. #92 ### New features - `Field#property` & `Field#property=` can be used to access & modify the method that will be sent to the underlying object when resolving a field #88 - When defining a field, you can pass a string for as `type`. It will be looked up in the global namespace. - `Query::Arguments#to_h` unwraps `Arguments` objects recursively - If you raise `GraphQL::ExecutionError` during field resolution, it will be rescued and the message will be added to the response's `errors` key. #93 - Raise an error when non-null fields are `nil` #94 ### Bug fixes - Accept Rails params as input objects - Don't get a runtime error when input contains unknown key #100 ## 0.10.9 (15 Jan 2016) ### Bug fixes - Handle re-assignment of `ObjectType#interfaces` #84 - Fix merging queries on interface-typed fields #85 ## 0.10.8 (14 Jan 2016) ### Bug fixes - Fix transform of nested lists #79 - Fix parse & transform of escaped characters #83 ## 0.10.7 (22 Dec 2015) ### New features - Support Rubinius ### Bug fixes - Coerce values into one-item lists for ListTypes ## 0.10.6 (20 Dec 2015) ### Bug fixes - Remove leftover `puts`es ## 0.10.5 (19 Dec 2015) ### Bug fixes - Accept enum value description in definition #71 - Correctly parse empty input objects #75 - Correctly parse arguments preceded by newline - Find undefined input object keys during static validation ## 0.10.4 (24 Nov 2015) ### New features - Add `Arguments#to_h` #66 ### Bug fixes - Accept argument description in definition - Correctly parse empty lists ## 0.10.3 (11 Nov 2015) ### New features - Support root-level `subscription` type ### Bug fixes - Require Set for Schema::Printer ## 0.10.2 (10 Nov 2015) ### Bug fixes - Handle blank strings in queries - Raise if a field is configured without a proper type #61 ## 0.10.1 (22 Oct 2015) ### Bug fixes - Properly merge fields on fragments within fragments - Properly delegate enumerable-ish methods on `Arguments` #56 - Fix & refactor literal coersion & validation #53 ## 0.10.0 (17 Oct 2015) ### New features - Scalars can have distinct `coerce_input` and `coerce_result` methods #48 - Operations don't require a name #54 ### Bug fixes - Big refactors and fixes to variables and arguments: - Correctly apply argument default values - Correctly apply variable default values - Raise at execution-time if non-null variables are missing - Incoming values are coerced to their proper types before execution ## 0.9.5 (1 Oct 2015) ### New features - Add `Schema#middleware` to wrap field access - Add `RescueMiddleware` to handle errors during field execution - Add `Schema::Printer` for printing the schema definition #45 ### Bug fixes ## 0.9.4 (22 Sept 2015) ### New features - Fields can return `GraphQL::ExecutionError`s to add errors to the response ### Bug fixes - Fix resolution of union types in some queries #41 ## 0.9.3 (15 Sept 2015) ### New features - Add `Schema#execute` shorthand for running queries - Merge identical fields in fragments so they're only resolved once #34 - An error during parsing raises `GraphQL::ParseError` #33 ### Bug fixes - Find nested input types in `TypeReducer` #35 - Find variable usages inside fragments during static validation ## 0.9.2, 0.9.1 (10 Sept 2015) ### Bug fixes - remove Celluloid dependency ## 0.9.0 (10 Sept 2015) ### Breaking changes & deprecations - remove `GraphQL::Query::ParallelExecution` (use [`graphql-parallel`](https://github.com/rmosolgo/graphql-parallel)) ## 0.8.1 (10 Sept 2015) ### Breaking changes & deprecations - `GraphQL::Query::ParallelExecution` has been extracted to [`graphql-parallel`](https://github.com/rmosolgo/graphql-parallel) ## 0.8.0 (4 Sept 2015) ### New features - Async field resolution with `context.async { ... }` - Access AST node during resolve with `context.ast_node` ### Bug fixes - Fix for validating arguments returning up too soon - Raise if you try to define 2 types with the same name - Raise if you try to get a type by name but it doesn't exist ## 0.7.1 (27 Aug 2015) ### Bug fixes - Merge nested results from different fragments instead of using the latest one only ## 0.7.0 (26 Aug 2015) ### Breaking changes & deprecations - Query keyword argument `params:` was removed, use `variables:` instead. ### Bug fixes - `@skip` has precedence over `@include` - Handle when `DEFAULT_RESOVE` returns nil ## 0.6.2 (20 Aug 2015) ### Bug fixes - Fix whitespace parsing in input objects ## 0.6.1 (16 Aug 2015) ### New features - Parse UTF-8 characters & escaped characters ### Bug fixes - Properly parse empty strings - Fix argument / variable compatibility validation ## 0.6.0 (14 Aug 2015) ### Breaking changes & deprecations - Deprecate `params` option to `Query#new` in favor of `variables` - Deprecated `.new { |obj, types, fields, args| }` API was removed (use `.define`) ### New features - `Query#new` accepts `operation_name` argument - `InterfaceType` and `UnionType` accept `resolve_type` configs ### Bug fixes - Gracefully handle blank-string & whitespace-only queries - Handle lists in variable definitions and arguments - Handle non-null input types ## 0.5.0 (12 Aug 2015) ### Breaking changes & deprecations - Deprecate definition API that yielded a bunch of helpers #18 ### New features - Add new definition API #18 graphql-ruby-1.11.10/CNAME000066400000000000000000000000211414121453000150220ustar00rootroot00000000000000graphql-ruby.org graphql-ruby-1.11.10/Gemfile000066400000000000000000000007331414121453000155610ustar00rootroot00000000000000# frozen_string_literal: true source "https://rubygems.org" gemspec gem 'bootsnap' # required by the Rails apps generated in tests gem 'ruby-prof', platform: :ruby gem 'pry' gem 'pry-stack_explorer', platform: :ruby if RUBY_VERSION >= "2.4" gem 'pry-byebug' end # Required for running `jekyll algolia ...` (via `rake site:update_search_index`) group :jekyll_plugins do if RUBY_VERSION >= "2.3" gem 'jekyll-algolia', '~> 1.0' end gem 'jekyll-redirect-from' end graphql-ruby-1.11.10/Guardfile000066400000000000000000000025011414121453000161060ustar00rootroot00000000000000# frozen_string_literal: true guard :minitest, test_file_patterns: ["*_spec.rb"] do # with Minitest::Spec watch(%r{^spec/(.*)_spec\.rb}) watch(%r{^lib/(.+)\.rb}) { |m| # When a project file changes, run any of: # - Corresponding `spec/` file # - Corresponding `spec/` file # for the file named in `test_via:` to_run = [] matching_spec = "spec/#{m[1]}_spec.rb" if File.exist?(matching_spec) to_run << matching_spec end # If the file was deleted, it won't exist anymore if File.exist?(m[0]) # Find a `# test_via:` macro to automatically run another test body = File.read(m[0]) test_via_match = body.match(/test_via: (.*)/) if test_via_match test_via_path = test_via_match[1] companion_file = Pathname.new(m[0] + "/../" + test_via_path) .cleanpath .to_s .sub(/.rb/, "_spec.rb") .sub("lib/", "spec/") to_run << companion_file end end # 0+ files to_run } watch(%r{^spec/spec_helper\.rb}) { "spec" } watch(%r{^spec/support/.*\.rb}) { "spec" } end guard "rake", task: "build_parser" do watch("lib/graphql/language/parser.y") watch("lib/graphql/language/lexer.rl") end guard :rubocop, all_on_start: false do watch(%r{^spec/.*.\rb}) watch(%r{^lib/.*\.rb}) end graphql-ruby-1.11.10/MIT-LICENSE000066400000000000000000000020361414121453000157200ustar00rootroot00000000000000Copyright 2015 Robert Mosolgo 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. graphql-ruby-1.11.10/Rakefile000066400000000000000000000064331414121453000157360ustar00rootroot00000000000000# frozen_string_literal: true require "bundler/setup" Bundler.require Bundler::GemHelper.install_tasks require "rake/testtask" require_relative "guides/_tasks/site" require_relative "lib/graphql/rake_task/validate" Rake::TestTask.new do |t| t.libs << "spec" << "lib" exclude_integrations = [] ['Mongoid', 'Rails'].each do |integration| begin Object.const_get(integration) rescue NameError exclude_integrations << integration.downcase end end t.test_files = Dir['spec/**/*_spec.rb'].reject do |f| next unless f.start_with?("spec/integration/") excluded = exclude_integrations.any? do |integration| f.start_with?("spec/integration/#{integration}/") end puts "+ #{f}" unless excluded excluded end t.warning = false end require 'rubocop/rake_task' RuboCop::RakeTask.new default_tasks = [:test, :rubocop] if ENV["SYSTEM_TESTS"] task(default: ["test:system"] + default_tasks) else task(default: default_tasks) end desc "Use Racc & Ragel to regenerate parser.rb & lexer.rb from configuration files" task :build_parser do def assert_dependency_version(dep_name, required_version, check_script) version = `#{check_script}` if !version.include?(required_version) raise <<-ERR build_parser requires #{dep_name} version "#{required_version}", but found: $ #{check_script} > #{version} To fix this issue: - Update #{dep_name} to the required version - Update the assertion in `Rakefile` to match the current version ERR end end assert_dependency_version("Ragel", "7.0.0.9", "ragel -v") assert_dependency_version("Racc", "1.4.16", %|ruby -e "require 'racc'; puts Racc::VERSION"|) `rm -f lib/graphql/language/parser.rb lib/graphql/language/lexer.rb ` `racc lib/graphql/language/parser.y -o lib/graphql/language/parser.rb` `ragel -R -F1 lib/graphql/language/lexer.rl` end namespace :bench do def prepare_benchmark $LOAD_PATH << "./lib" << "./spec/support" require_relative("./benchmark/run.rb") end desc "Benchmark the introspection query" task :query do prepare_benchmark GraphQLBenchmark.run("query") end desc "Benchmark validation of several queries" task :validate do prepare_benchmark GraphQLBenchmark.run("validate") end desc "Generate a profile of the introspection query" task :profile do prepare_benchmark GraphQLBenchmark.profile end desc "Run benchmarks on a very large result" task :profile_large_result do prepare_benchmark GraphQLBenchmark.profile_large_result end end namespace :test do desc "Run system tests for ActionCable subscriptions" task :system do success = Dir.chdir("spec/dummy") do system("bundle install") system("bundle exec bin/rails test:system") end success || abort end task js: "js:test" end namespace :js do client_dir = "./javascript_client" desc "Run the tests for javascript_client" task :test do success = Dir.chdir(client_dir) do system("yarn run test") end success || abort end desc "Install JS dependencies" task :install do Dir.chdir(client_dir) do system("yarn install") end end desc "Compile TypeScript to JavaScript" task :build do Dir.chdir(client_dir) do system("yarn tsc") end end task all: [:install, :build, :test] end graphql-ruby-1.11.10/benchmark/000077500000000000000000000000001414121453000162155ustar00rootroot00000000000000graphql-ruby-1.11.10/benchmark/abstract_fragments.graphql000066400000000000000000000006741414121453000234550ustar00rootroot00000000000000query AbstractFragments { node(id: "1") { ...Frag1 } } fragment Frag1 on Commentable { id __typename ...Frag2 } fragment Frag2 on Commentable { id __typename ...Frag3 } fragment Frag3 on Commentable { id __typename ...Frag4 } fragment Frag4 on Commentable { id __typename ...Frag5 } fragment Frag5 on Commentable { id __typename ...Frag6 } fragment Frag6 on Commentable { comments { body } } graphql-ruby-1.11.10/benchmark/abstract_fragments_2.graphql000066400000000000000000000012301414121453000236630ustar00rootroot00000000000000query AbstractFragments { node(id: "1") { ...Frag1 } } fragment Frag1 on Commentable { id __typename ...Frag9 ...Frag2 } fragment Frag2 on Commentable { id __typename ...Frag9 ...Frag3 } fragment Frag3 on Commentable { id __typename ...Frag9 ...Frag4 } fragment Frag4 on Commentable { id __typename ...Frag9 ...Frag5 } fragment Frag5 on Commentable { id __typename ...Frag9 ...Frag6 } fragment Frag6 on Commentable { ...Frag7 ...Frag9 name id comments { ...Frag8 ...Frag7 id } } fragment Frag7 on Node { id } fragment Frag8 on Comment { body } fragment Frag9 on Named { name } graphql-ruby-1.11.10/benchmark/big_query.graphql000066400000000000000000000155611414121453000215730ustar00rootroot00000000000000query Anc_inbox_layout($first_0:Int!,$first_5:Int!,$first_6:Int!,$first_a:Int!,$first_c:Int!,$order_by_1:ReportOrderInput!,$substate_2:ReportStateEnum!,$pre_submission_review_states_3:[ReportPreSubmissionReviewStateEnum]!,$size_4:ProfilePictureSizes!,$size_9:ProfilePictureSizes!,$not_types_7:[ActivityTypes]!,$order_by_8:ActivityOrderInput!,$order_by_b:TeamOrderInput!) { query { id, ...FE } } fragment F0 on User { username, _profile_picturePkPpF:profile_picture(size:$size_4), impact, reputation, signal, id } fragment F1 on Report { reporter { username, _profile_picturePkPpF:profile_picture(size:$size_4), impact, reputation, signal, _duplicate_users2Nhzxe:duplicate_users(first:$first_5) { pageInfo { hasNextPage, hasPreviousPage }, total_count, edges { node { id, ...F0 }, cursor } }, id }, id } fragment F2 on Report { id } fragment F3 on Node { id, __typename } fragment F4 on ReportActivityInterface { automated_response, genius_execution_id, report { team { handle, id }, id }, __typename, ...F3 } fragment F5 on Team { url, internet_bug_bounty, _profile_pictureihzmG:profile_picture(size:$size_9), name, id } fragment F6 on User { username, url, _profile_pictureihzmG:profile_picture(size:$size_9), id } fragment F7 on ActivitiesBugDuplicate { original_report_id, id } fragment F8 on ActivitiesReferenceIdAdded { reference, reference_url, id } fragment F9 on ActivitiesCveIdAdded { cve_ids, id } fragment Fa on ActivitiesAgreedOnGoingPublic { first_to_agree, id } fragment Fb on ActivitiesBugCloned { original_report_id, id } fragment Fc on ActivitiesUserAssignedToBug { assigned_user { url, username, id }, id } fragment Fd on ActivitiesGroupAssignedToBug { assigned_group { name, id }, id } fragment Fe on ActivitiesExternalUserInvited { email, id } fragment Ff on ActivitiesExternalUserInvitationCancelled { email, id } fragment Fg on ActivitiesExternalUserRemoved { removed_user { id }, id } fragment Fh on ActivitiesUserBannedFromProgram { removed_user { id }, id } fragment Fi on ActivitiesBountyAwarded { bounty_amount, bounty_currency, bonus_amount, report { reporter { username, url, id }, id }, id } fragment Fj on ActivitiesBountySuggested { bounty_amount, bounty_currency, bonus_amount, id } fragment Fk on ActivitiesBugResolved { report { reporter { username, url, id }, id }, id } fragment Fl on ActivitiesSwagAwarded { report { reporter { username, url, id }, id }, id } fragment Fm on ActivitiesChangedScope { old_scope { asset_identifier, id }, new_scope { asset_identifier, id }, id } fragment Fn on ActivityInterface { _id, internal, i_can_edit, __typename, message, markdown_message, created_at, updated_at, actor { __typename, ...F5, ...F6, ...F3 }, attachments { _id, file_name, content_type, expiring_url, id }, ...F7, ...F8, ...F9, ...Fa, ...Fb, ...Fc, ...Fd, ...Fe, ...Ff, ...Fg, ...Fh, ...Fi, ...Fj, ...Fk, ...Fl, ...Fm, ...F3 } fragment Fo on User { username, url, __typename, id } fragment Fp on TeamMemberGroup { name, __typename, id } fragment Fq on Report { _id, url, title, state, substate, created_at, assignee { __typename, ...Fo, ...Fp, ...F3 }, cloned_from { _id, id }, reporter { username, url, id }, team { _id, url, handle, name, twitter_handle, website, about, offers_bounties, id }, id } fragment Fr on Report { state, stage, disclosed_at, cve_ids, singular_disclosure_disabled, disclosed_at, bug_reporter_agreed_on_going_public_at, team_member_agreed_on_going_public_at, comments_closed, mediation_requested_at, vulnerability_information, vulnerability_information_html, reporter { disabled, username, url, _profile_picture2g6hJa:profile_picture(size:$size_4), id }, weakness { id, name }, original_report { id, url }, attachments { _id, file_name, expiring_url, content_type, id }, allow_singular_disclosure_at, allow_singular_disclosure_after, singular_disclosure_allowed, severity { rating, score, author_type, id }, structured_scope { _id, asset_type, asset_identifier, max_severity, id }, _activities4z6spP:activities(first:$first_6,not_types:$not_types_7,order_by:$order_by_8) { edges { node { __typename, ...F4, ...Fn, ...F3 }, cursor }, pageInfo { hasNextPage, hasPreviousPage } }, id, ...Fq } fragment Fs on Report { id, ...Fr } fragment Ft on Report { title, id } fragment Fu on Report { _id, pre_submission_review_state, i_can_anc_review, reporter { username, id }, team { handle, id }, id, ...F2 } fragment Fv on Report { team { policy_html, id }, structured_scope { asset_identifier, asset_type, instruction, id }, id } fragment Fw on Report { weakness { name, id }, id } fragment Fx on Report { severity { rating, score, id }, id } fragment Fy on Report { latest_activity_at, created_at, id, ...Fq } fragment Fz on Query { me { username, _teamsWbVmT:teams(order_by:$order_by_b,first:$first_c) { edges { node { name, handle, id }, cursor }, pageInfo { hasNextPage, hasPreviousPage } }, id }, id } fragment FA on Query { _reports1t04lE:reports(page:$first_0,first:$first_a,limit:$first_a,order_by:$order_by_1,substate:$substate_2,pre_submission_review_states:$pre_submission_review_states_3) { total_count, edges { node { _id, id, ...Fy }, cursor }, pageInfo { hasNextPage, hasPreviousPage } }, id, ...Fz } fragment FB on Query { id, ...Fz } fragment FC on Query { id } fragment FD on Query { me { username, _profile_pictureihzmG:profile_picture(size:$size_9), id }, id, ...FC } fragment FE on Query { _reports3QQXft:reports(first:$first_0,order_by:$order_by_1,substate:$substate_2,pre_submission_review_states:$pre_submission_review_states_3) { edges { node { id, ...F1, ...F2, ...Fs, ...Ft, ...Fu, ...Fv, ...Fw, ...Fx }, cursor }, pageInfo { hasNextPage, hasPreviousPage } }, id, ...FA, ...FB, ...FD } graphql-ruby-1.11.10/benchmark/big_schema.graphql000066400000000000000000003372231414121453000216700ustar00rootroot00000000000000# Autogenerated input type of AcceptInvitation input AcceptInvitationInput { # A unique identifier for the client performing the mutation. clientMutationId: String handle: String! } # Autogenerated return type of AcceptInvitation type AcceptInvitationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String me: User } # Autogenerated input type of AcknowledgeProgramHealthAcknowledgementMutation input AcknowledgeProgramHealthAcknowledgementMutationInput { # A unique identifier for the client performing the mutation. clientMutationId: String program_health_acknowledgement_id: ID! } # Autogenerated return type of AcknowledgeProgramHealthAcknowledgementMutation type AcknowledgeProgramHealthAcknowledgementMutationPayload { acknowledged_program_health_acknowledgement_id: String! # A unique identifier for the client performing the mutation. clientMutationId: String errors: [Error] me: User was_successful: Boolean! } # A Activities::AgreedOnGoingPublic activity for a report type ActivitiesAgreedOnGoingPublic implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! first_to_agree: Boolean! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BountyAwarded activity for a report type ActivitiesBountyAwarded implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean bonus_amount: String bounty_amount: String bounty_currency: String created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BountySuggested activity for a report type ActivitiesBountySuggested implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean bonus_amount: String bounty_amount: String bounty_currency: String created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BugCloned activity for a report type ActivitiesBugCloned implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String original_report: Report original_report_id: Int @deprecated(reason: "Deprecated in favor of .original_report") report: Report updated_at: String! } # A Activities::BugDuplicate activity for a report type ActivitiesBugDuplicate implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String original_report: Report original_report_id: Int @deprecated(reason: "Deprecated in favor of .original_report") report: Report updated_at: String! } # A Activities::BugFiled activity for a report type ActivitiesBugFiled implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BugInformative activity for a report type ActivitiesBugInformative implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BugNeedsMoreInfo activity for a report type ActivitiesBugNeedsMoreInfo implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BugNew activity for a report type ActivitiesBugNew implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BugNotApplicable activity for a report type ActivitiesBugNotApplicable implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BugReopened activity for a report type ActivitiesBugReopened implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BugResolved activity for a report type ActivitiesBugResolved implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BugSpam activity for a report type ActivitiesBugSpam implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::BugTriaged activity for a report type ActivitiesBugTriaged implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ChangedScope activity for a report type ActivitiesChangedScope implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String new_scope: StructuredScope old_scope: StructuredScope report: Report updated_at: String! } # A Activities::Comment activity for a report type ActivitiesComment implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::CommentsClosed activity for a report type ActivitiesCommentsClosed implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::CveIdAdded activity for a report type ActivitiesCveIdAdded implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! cve_ids: [String] genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ExternalAdvisoryAdded activity for a report type ActivitiesExternalAdvisoryAdded implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ExternalUserInvitationCancelled activity for a report type ActivitiesExternalUserInvitationCancelled implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! email: String genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ExternalUserInvited activity for a report type ActivitiesExternalUserInvited implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! email: String genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ExternalUserJoined activity for a report type ActivitiesExternalUserJoined implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! duplicate_report: Report duplicate_report_id: Int @deprecated(reason: "Deprecated in favor of .duplicate_report") genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ExternalUserRemoved activity for a report type ActivitiesExternalUserRemoved implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String removed_user: User! report: Report updated_at: String! } # A Activities::GroupAssignedToBug activity for a report type ActivitiesGroupAssignedToBug implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! assigned_group: TeamMemberGroup attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") group: TeamMemberGroup @deprecated(reason: "deprecated in favor of assigned group") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::HackerRequestedMediation activity for a report type ActivitiesHackerRequestedMediation implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ManuallyDisclosed activity for a report type ActivitiesManuallyDisclosed implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::MediationRequested activity for a report type ActivitiesMediationRequested implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # An Activities::NobodyAssignedToBug activity for a report type ActivitiesNobodyAssignedToBug implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::NotEligibleForBounty activity for a report type ActivitiesNotEligibleForBounty implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ReassignedToTeam activity for a report type ActivitiesReassignedToTeam implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ReferenceIdAdded activity for a report type ActivitiesReferenceIdAdded implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String reference: String reference_url: String report: Report updated_at: String! } # A Activities::ReportBecamePublic activity for a report type ActivitiesReportBecamePublic implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ReportCollaboratorInvited activity for a report type ActivitiesReportCollaboratorInvited implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ReportCollaboratorJoined activity for a report type ActivitiesReportCollaboratorJoined implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ReportSeverityUpdated activity for a report type ActivitiesReportSeverityUpdated implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::ReportTitleUpdated activity for a report type ActivitiesReportTitleUpdated implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String new_title: String! old_title: String! report: Report updated_at: String! } # A Activities::ReportVulnerabilityTypesUpdated activity for a report type ActivitiesReportVulnerabilityTypesUpdated implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String new_weakness: Weakness old_weakness: Weakness report: Report updated_at: String! } # A Activities::SwagAwarded activity for a report type ActivitiesSwagAwarded implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report swag: Swag! updated_at: String! } # A Activities::TeamPublished activity for a team type ActivitiesTeamPublished implements ActivityInterface, Node { _id: ID! actor: ActorUnion! attachments: [Attachment] created_at: String! i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String updated_at: String! } # A Activities::UserAssignedToBug activity for a report type ActivitiesUserAssignedToBug implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! assigned_user: User! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String report: Report updated_at: String! } # A Activities::UserBannedFromProgram activity for a report type ActivitiesUserBannedFromProgram implements ActivityInterface, Node, ReportActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] automated_response: Boolean created_at: String! genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String removed_user: User! report: Report updated_at: String! } # A Activities::UserJoined activity for a user type ActivitiesUserJoined implements ActivityInterface, Node { _id: ID! actor: ActorUnion! attachments: [Attachment] created_at: String! i_can_edit: Boolean! id: ID! internal: Boolean markdown_message: String message: String updated_at: String! } # The connection type for ActivityUnion. type ActivityConnection { # A list of edges. edges: [ActivityUnionEdge] max_updated_at: String # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # A interface for the common fields on an HackerOne Activity interface ActivityInterface { _id: ID! actor: ActorUnion! attachments: [Attachment] created_at: String! i_can_edit: Boolean! internal: Boolean markdown_message: String message: String updated_at: String! } # Fields on which a collection of activities can be ordered enum ActivityOrderField { created_at updated_at } input ActivityOrderInput { direction: OrderDirection! field: ActivityOrderField! } # Possible types for an activity enum ActivityTypes { AgreedOnGoingPublic BountyAwarded BountySuggested BugCloned BugDuplicate BugFiled BugInformative BugNeedsMoreInfo BugNew BugNotApplicable BugReopened BugResolved BugSpam BugTriaged ChangedScope Comment CommentsClosed CveIdAdded ExternalAdvisoryAdded ExternalUserInvitationCancelled ExternalUserInvited ExternalUserJoined ExternalUserRemoved GroupAssignedToBug HackerRequestedMediation ManuallyDisclosed MediationRequested NobodyAssignedToBug NotEligibleForBounty ReassignedToTeam ReferenceIdAdded ReportBecamePublic ReportCollaboratorInvited ReportCollaboratorJoined ReportSeverityUpdated ReportTitleUpdated ReportVulnerabilityTypesUpdated SwagAwarded TeamPublished UserAssignedToBug UserBannedFromProgram UserJoined } # Activities can be of multiple types union ActivityUnion = ActivitiesAgreedOnGoingPublic | ActivitiesBountyAwarded | ActivitiesBountySuggested | ActivitiesBugCloned | ActivitiesBugDuplicate | ActivitiesBugFiled | ActivitiesBugInformative | ActivitiesBugNeedsMoreInfo | ActivitiesBugNew | ActivitiesBugNotApplicable | ActivitiesBugReopened | ActivitiesBugResolved | ActivitiesBugSpam | ActivitiesBugTriaged | ActivitiesChangedScope | ActivitiesComment | ActivitiesCommentsClosed | ActivitiesCveIdAdded | ActivitiesExternalAdvisoryAdded | ActivitiesExternalUserInvitationCancelled | ActivitiesExternalUserInvited | ActivitiesExternalUserJoined | ActivitiesExternalUserRemoved | ActivitiesGroupAssignedToBug | ActivitiesHackerRequestedMediation | ActivitiesManuallyDisclosed | ActivitiesMediationRequested | ActivitiesNobodyAssignedToBug | ActivitiesNotEligibleForBounty | ActivitiesReassignedToTeam | ActivitiesReferenceIdAdded | ActivitiesReportBecamePublic | ActivitiesReportCollaboratorInvited | ActivitiesReportCollaboratorJoined | ActivitiesReportSeverityUpdated | ActivitiesReportTitleUpdated | ActivitiesReportVulnerabilityTypesUpdated | ActivitiesSwagAwarded | ActivitiesTeamPublished | ActivitiesUserAssignedToBug | ActivitiesUserBannedFromProgram | ActivitiesUserJoined # The connection type for ActivityUnion. type ActivityUnionConnection { # A list of edges. edges: [ActivityUnionEdge] # Information to aid in pagination. pageInfo: PageInfo! } # An edge in a connection. type ActivityUnionEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: ActivityUnion } # The actor of an activity can be multiple types union ActorUnion = Team | User # A HackerOne user's address used for submitting swag type Address implements Node { _id: ID! city: String! country: String! created_at: String! id: ID! name: String! phone_number: String postal_code: String! state: String! street: String! tshirt_size: TshirtSizeEnum @deprecated(reason: "Query tshirt size on User instead") } # Autogenerated input type of ArchiveStructuredScope input ArchiveStructuredScopeInput { # A unique identifier for the client performing the mutation. clientMutationId: String structured_scope_id: ID! } # Autogenerated return type of ArchiveStructuredScope type ArchiveStructuredScopePayload { # A unique identifier for the client performing the mutation. clientMutationId: String query: Query structured_scope: StructuredScope } # Report can be assigned to either a user or a team member group union AssigneeUnion = TeamMemberGroup | User # A HackerOne attachment for a report type Attachment implements Node { _id: ID! content_type: String! created_at: String! expiring_url: String! file_name: String! file_size: Int! id: ID! } # Types of authentication methods for users enum AuthenticationServiceEnum { database saml token } # A HackerOne badge type Badge implements Node { _id: ID! description: String! id: ID! image_path: String! name: String! } # Represents a badge earned by a user type BadgesUsers implements Node { _id: ID! badge: Badge! created_at: String! id: ID! user: User! } # The connection type for BadgesUsers. type BadgesUsersConnection { # A list of edges. edges: [BadgesUsersEdge] # Information to aid in pagination. pageInfo: PageInfo! } # An edge in a connection. type BadgesUsersEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: BadgesUsers } # Resources for setting up the Bank Transfer payment method type BankTransferReference implements Node { beneficiary_required_details(currency: String!, bank_account_country: String!, beneficiary_country: String!): BeneficiaryRequiredDetail countries: [Country] currencies: [Currency] id: ID! } # A specification of information needed to create a bank transfer payment preference type BeneficiaryRequiredDetail implements Node { bank_account_country: String! beneficiary_country: String! beneficiary_required_details( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): BeneficiaryRequiredDetailsConnection currency: String! id: ID! } input BeneficiaryRequiredDetailInput { field: String! value: String! } # A specification of the possibilities for creating a bank transfer payout preference type BeneficiaryRequiredDetails implements Node { beneficiary_entity_type: String! beneficiary_required_fields( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): BeneficiaryRequiredFieldConnection description: String! fee: String! id: ID! payment_type: String! } # The connection type for BeneficiaryRequiredDetails. type BeneficiaryRequiredDetailsConnection { # A list of edges. edges: [BeneficiaryRequiredDetailsEdge] # Information to aid in pagination. pageInfo: PageInfo! } # An edge in a connection. type BeneficiaryRequiredDetailsEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: BeneficiaryRequiredDetails } # A specification of the format of a field used to create a bank transfer payout preference type BeneficiaryRequiredField implements Node { description: String! field: String! id: ID! regex: String! } # The connection type for BeneficiaryRequiredField. type BeneficiaryRequiredFieldConnection { # A list of edges. edges: [BeneficiaryRequiredFieldEdge] # Information to aid in pagination. pageInfo: PageInfo! } # An edge in a connection. type BeneficiaryRequiredFieldEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: BeneficiaryRequiredField } # A HackerOne bounty for a report type Bounty implements Node { _id: ID! amount: String! awarded_amount: String! awarded_bonus_amount: String! awarded_currency: String! bonus_amount: String! created_at: String! id: ID! report: Report! status: BountyStatusEnum! } # The connection type for Bounty. type BountyConnection { # A list of edges. edges: [BountyEdge] # Information to aid in pagination. pageInfo: PageInfo! total_amount: Float! total_count: Int! } # An edge in a connection. type BountyEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: Bounty } # Status which reflect the state of a bounty enum BountyStatusEnum { cancelled confirmed failed failed_ofac_check needs_payout_method needs_tax_form no_mileage_account no_status no_tax_form ofac_reject pending pending_ofac_check rejected sent } # A subset of weaknesses that share a common characteristic type Cluster implements Node { created_at: String! description: String id: ID! name: String! updated_at: String! weaknesses( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int order_by: WeaknessOrder = {direction: ASC, field: name} search: String = null team_handle: String = null team_weakness_state: [TeamWeaknessStates] = [enabled, disabled, hidden] ): ClusterWeaknessConnection } # The connection type for Cluster. type ClusterConnection { # A list of edges. edges: [ClusterEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type ClusterEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: Cluster } input ClusterOrder { direction: OrderDirection! field: ClusterOrderField! } # Fields on which a collection of Cluster can be ordered enum ClusterOrderField { BROWSING_FRIENDLY } # The connection type for Weakness. type ClusterWeaknessConnection { # A list of edges. edges: [ClusterWeaknessEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type ClusterWeaknessEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: Weakness team_weakness(team_handle: String!): TeamWeakness } # A Coinbase Payout Preference type CoinbasePayoutPreferenceType implements Node, PayoutPreferenceInterface { _id: ID! default: Boolean email: String id: ID! } # A common response type CommonResponse implements Node { # The primary key from the database _id: ID! created_at: String! id: ID! message: String! team: Team! title: String! updated_at: String! } # The connection type for CommonResponse. type CommonResponseConnection { # A list of edges. edges: [CommonResponseEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type CommonResponseEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: CommonResponse } input CommonResponseOrder { direction: OrderDirection! field: CommonResponseOrderField! } # Fields on which a collection of common responses can be ordered enum CommonResponseOrderField { title } # Will only return values for valid SeverityRatingEnum and null. scalar CountBySeverity # A country as specified in ISO 3166 type Country implements Node { alpha2: String! currency_code: String id: ID! name: String! } # Autogenerated input type of CreateActivityComment input CreateActivityCommentInput { # A unique identifier for the client performing the mutation. clientMutationId: String internal: Boolean! message: String! report_id: ID! } # Autogenerated return type of CreateActivityComment type CreateActivityCommentPayload { activities: ActivityUnionConnection activity: ActivitiesComment # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash new_activity_edge: ActivityUnionEdge report: Report } # Autogenerated input type of CreateBounty input CreateBountyInput { amount: Float bonus_amount: Float # A unique identifier for the client performing the mutation. clientMutationId: String message: String report_id: ID! } # Autogenerated return type of CreateBounty type CreateBountyPayload { bounty: Bounty! # A unique identifier for the client performing the mutation. clientMutationId: String } # Autogenerated input type of CreateBountySuggestion input CreateBountySuggestionInput { amount: Float bonus_amount: Float # A unique identifier for the client performing the mutation. clientMutationId: String message: String report_id: ID! } # Autogenerated return type of CreateBountySuggestion type CreateBountySuggestionPayload { activity: ActivitiesBountySuggested! # A unique identifier for the client performing the mutation. clientMutationId: String } # Autogenerated input type of CreateCoinbasePayoutPreference input CreateCoinbasePayoutPreferenceInput { # A unique identifier for the client performing the mutation. clientMutationId: String coinbase_email: String! default_method: Boolean! } # Autogenerated return type of CreateCoinbasePayoutPreference type CreateCoinbasePayoutPreferencePayload { # A unique identifier for the client performing the mutation. clientMutationId: String me: User } # Autogenerated input type of CreateCurrencycloudBankTransferPayoutPreference input CreateCurrencycloudBankTransferPayoutPreferenceInput { bank_account_country: String! bank_account_holder_name: String! beneficiary_entity_type: CurrencycloudBankTransferEntityType! beneficiary_required_details: [BeneficiaryRequiredDetailInput]! # A unique identifier for the client performing the mutation. clientMutationId: String currency: String! default_method: Boolean! payment_type: CurrencycloudBankTransferPaymentType! } # Autogenerated return type of CreateCurrencycloudBankTransferPayoutPreference type CreateCurrencycloudBankTransferPayoutPreferencePayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # Autogenerated input type of CreateJiraOauthUrl input CreateJiraOauthUrlInput { # A unique identifier for the client performing the mutation. clientMutationId: String site: String! team_id: ID! } # Autogenerated return type of CreateJiraOauthUrl type CreateJiraOauthUrlPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash team: Team url: String } # Autogenerated input type of CreateJiraWebhookToken input CreateJiraWebhookTokenInput { # A unique identifier for the client performing the mutation. clientMutationId: String team_id: ID! } # Autogenerated return type of CreateJiraWebhookToken type CreateJiraWebhookTokenPayload { # A unique identifier for the client performing the mutation. clientMutationId: String team: Team! webhook_url: String! } # Autogenerated input type of CreateMailingAddress input CreateMailingAddressInput { city: String! # A unique identifier for the client performing the mutation. clientMutationId: String country: String! name: String! phone_number: String! postal_code: String! state: String! street: String! } # Autogenerated return type of CreateMailingAddress type CreateMailingAddressPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [Error] me: User was_successful: Boolean! } # Autogenerated input type of CreateOrUpdateHackeroneToJiraEventsConfiguration input CreateOrUpdateHackeroneToJiraEventsConfigurationInput { assignee_changes: Boolean # A unique identifier for the client performing the mutation. clientMutationId: String comments: Boolean public_disclosures: Boolean rewards: Boolean state_changes: Boolean team_id: ID! } # Autogenerated return type of CreateOrUpdateHackeroneToJiraEventsConfiguration type CreateOrUpdateHackeroneToJiraEventsConfigurationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String team: Team! } # Autogenerated input type of CreateOrUpdateJiraIntegration input CreateOrUpdateJiraIntegrationInput { assignee: String base_url: String! # A unique identifier for the client performing the mutation. clientMutationId: String custom: String description: String! generate_webhook_in_jira_if_does_not_exist: Boolean issue_type: Int! labels: String pid: Int! summary: String! team_id: ID! } # Autogenerated return type of CreateOrUpdateJiraIntegration type CreateOrUpdateJiraIntegrationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash team: Team } # Autogenerated input type of CreatePaypalPreference input CreatePaypalPreferenceInput { # A unique identifier for the client performing the mutation. clientMutationId: String default_method: Boolean! paypal_email: String! } # Autogenerated return type of CreatePaypalPreference type CreatePaypalPreferencePayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [Error] me: User was_successful: Boolean! } # Autogenerated input type of CreateSlackPipeline input CreateSlackPipelineInput { channel: String! # A unique identifier for the client performing the mutation. clientMutationId: String descriptive_label: String notification_report_agreed_on_going_public: Boolean! notification_report_assignee_changed: Boolean! notification_report_became_public: Boolean! notification_report_bounty_paid: Boolean! notification_report_bounty_suggested: Boolean! notification_report_bug_closed_as_spam: Boolean! notification_report_bug_duplicate: Boolean! notification_report_bug_informative: Boolean! notification_report_bug_needs_more_info: Boolean! notification_report_bug_new: Boolean! notification_report_bug_not_applicable: Boolean! notification_report_closed_as_resolved: Boolean! notification_report_comments_closed: Boolean! notification_report_created: Boolean! notification_report_internal_comment_added: Boolean! notification_report_manually_disclosed: Boolean! notification_report_not_eligible_for_bounty: Boolean! notification_report_public_comment_added: Boolean! notification_report_reopened: Boolean! notification_report_swag_awarded: Boolean! notification_report_triaged: Boolean! team_id: ID! } # Autogenerated return type of CreateSlackPipeline type CreateSlackPipelinePayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash new_slack_pipeline_edge: SlackPipelineEdge slack_pipelines_connection: SlackPipelineConnection team: Team! } # Autogenerated input type of CreateStructuredScope input CreateStructuredScopeInput { asset_identifier: String! asset_type: StructuredScopedAssetTypeEnum availability_requirement: String # A unique identifier for the client performing the mutation. clientMutationId: String confidentiality_requirement: String eligible_for_bounty: Boolean eligible_for_submission: Boolean instruction: String integrity_requirement: String reference: String team_id: ID! } # Autogenerated return type of CreateStructuredScope type CreateStructuredScopePayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash team: Team } # Autogenerated input type of CreateSurveyAnswer input CreateSurveyAnswerInput { # A unique identifier for the client performing the mutation. clientMutationId: String feedback: String invitation_token: String structured_response_ids: [ID]! } # Autogenerated return type of CreateSurveyAnswer type CreateSurveyAnswerPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [Error] me: User was_successful: Boolean! } # Autogenerated input type of CreateSwag input CreateSwagInput { # A unique identifier for the client performing the mutation. clientMutationId: String message: String report_id: ID! } # Autogenerated return type of CreateSwag type CreateSwagPayload { # A unique identifier for the client performing the mutation. clientMutationId: String swag: Swag! } # Autogenerated input type of CreateTaxForm input CreateTaxFormInput { # A unique identifier for the client performing the mutation. clientMutationId: String type: TaxFormTypeEnum! } # Autogenerated return type of CreateTaxForm type CreateTaxFormPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [String] me: User } # Autogenerated input type of CreateUserBountiesReport input CreateUserBountiesReportInput { # A unique identifier for the client performing the mutation. clientMutationId: String } # Autogenerated return type of CreateUserBountiesReport type CreateUserBountiesReportPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # Autogenerated input type of CreateUserLufthansaAccount input CreateUserLufthansaAccountInput { # A unique identifier for the client performing the mutation. clientMutationId: String first_name: String! last_name: String! number: String! } # Autogenerated return type of CreateUserLufthansaAccount type CreateUserLufthansaAccountPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # A currency as defined by ISO 4217 type Currency implements Node { code: String! id: ID! name: String! } # Possible currencies codes for bounties enum CurrencyCode { USD XLA } # Different entity types for currencycloud payout preferences enum CurrencycloudBankTransferEntityType { company individual } # Different payment types for currencycloud payout preferences enum CurrencycloudBankTransferPaymentType { priority regular } # A CurrencyCloud Bank Transfer Payout Preference type CurrencycloudBankTransferPayoutPreferenceType implements Node, PayoutPreferenceInterface { _id: ID! default: Boolean id: ID! name: String } # Autogenerated input type of DeleteBiDirectionalJiraIntegration input DeleteBiDirectionalJiraIntegrationInput { # A unique identifier for the client performing the mutation. clientMutationId: String team_id: ID! } # Autogenerated return type of DeleteBiDirectionalJiraIntegration type DeleteBiDirectionalJiraIntegrationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String team: Team } # Autogenerated input type of DeleteJiraWebhook input DeleteJiraWebhookInput { # A unique identifier for the client performing the mutation. clientMutationId: String jira_webhook_id: ID! } # Autogenerated return type of DeleteJiraWebhook type DeleteJiraWebhookPayload { # A unique identifier for the client performing the mutation. clientMutationId: String team: Team } # Autogenerated input type of DeletePhabricatorIntegration input DeletePhabricatorIntegrationInput { # A unique identifier for the client performing the mutation. clientMutationId: String team_id: ID! } # Autogenerated return type of DeletePhabricatorIntegration type DeletePhabricatorIntegrationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String team: Team } # Autogenerated input type of DeleteSlackPipeline input DeleteSlackPipelineInput { # A unique identifier for the client performing the mutation. clientMutationId: String slack_pipeline_id: String! } # Autogenerated return type of DeleteSlackPipeline type DeleteSlackPipelinePayload { # A unique identifier for the client performing the mutation. clientMutationId: String deleted_slack_pipeline_id: String! team: Team } # Autogenerated input type of DeleteTeamMember input DeleteTeamMemberInput { # A unique identifier for the client performing the mutation. clientMutationId: String team_member_id: ID! } # Autogenerated return type of DeleteTeamMember type DeleteTeamMemberPayload { # A unique identifier for the client performing the mutation. clientMutationId: String deletedMembershipId: ID errors: Hash me: User } # Autogenerated input type of DeleteTeamSlackIntegration input DeleteTeamSlackIntegrationInput { # A unique identifier for the client performing the mutation. clientMutationId: String slack_integration_id: ID! } # Autogenerated return type of DeleteTeamSlackIntegration type DeleteTeamSlackIntegrationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String team: Team } # Autogenerated input type of DeleteUserLufthansaAccount input DeleteUserLufthansaAccountInput { # A unique identifier for the client performing the mutation. clientMutationId: String } # Autogenerated return type of DeleteUserLufthansaAccount type DeleteUserLufthansaAccountPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # Autogenerated input type of DisableTwoFactorAuthentication input DisableTwoFactorAuthenticationInput { # A unique identifier for the client performing the mutation. clientMutationId: String password: String! totp_code: String! } # Autogenerated return type of DisableTwoFactorAuthentication type DisableTwoFactorAuthenticationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [String] me: User } # Autogenerated input type of DismissProgramHealthAcknowledgementMutation input DismissProgramHealthAcknowledgementMutationInput { # A unique identifier for the client performing the mutation. clientMutationId: String program_health_acknowledgement_id: ID! } # Autogenerated return type of DismissProgramHealthAcknowledgementMutation type DismissProgramHealthAcknowledgementMutationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String dismissed_program_health_acknowledgement_id: String! errors: [Error] me: User was_successful: Boolean! } # Autogenerated input type of EnableTwoFactorAuthentication input EnableTwoFactorAuthenticationInput { backup_codes: [String]! # A unique identifier for the client performing the mutation. clientMutationId: String password: String! signature: String! totp_code: String! totp_secret: String! } # Autogenerated return type of EnableTwoFactorAuthentication type EnableTwoFactorAuthenticationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [String] me: User } # Autogenerated input type of EnableUser input EnableUserInput { # A unique identifier for the client performing the mutation. clientMutationId: String } # Autogenerated return type of EnableUser type EnableUserPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # Autogenerated input type of EnrollForProgram input EnrollForProgramInput { # A unique identifier for the client performing the mutation. clientMutationId: String team_id: ID! } # Autogenerated return type of EnrollForProgram type EnrollForProgramPayload { # A unique identifier for the client performing the mutation. clientMutationId: String deleted_recommended_program_edge: TeamEdge query: Query! teams_connection: TeamConnection } # An error type Error implements Node { field: String id: ID! message: String! type: ErrorTypeEnum! } # Types of errors that can occur enum ErrorTypeEnum { ARGUMENT AUTHORIZATION } # An External Program type ExternalProgram implements Node { # The primary key from the database _id: ID! about: String handle: String! # ID of the object. id: ID! name: String! profile_picture(size: ProfilePictureSizes!): String! } # Autogenerated input type of GenerateMfaOtp input GenerateMfaOtpInput { # A unique identifier for the client performing the mutation. clientMutationId: String } # Autogenerated return type of GenerateMfaOtp type GenerateMfaOtpPayload { backup_codes: [String] # A unique identifier for the client performing the mutation. clientMutationId: String me: User qrcode: Hash signature: String totp_secret: String } # Autogenerated input type of GenerateTaxFormUrl input GenerateTaxFormUrlInput { # A unique identifier for the client performing the mutation. clientMutationId: String } # Autogenerated return type of GenerateTaxFormUrl type GenerateTaxFormUrlPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # A HackeronePayroll Payout Preference type HackeronePayrollPayoutPreferenceType implements Node, PayoutPreferenceInterface { _id: ID! default: Boolean email: String id: ID! } # Configuration for the events sent from HackerOne to JIRA type HackeroneToJiraEventsConfiguration implements Node { assignee_changes: Boolean! comments: Boolean! id: ID! public_disclosures: Boolean! rewards: Boolean! state_changes: Boolean! team: Team! } scalar Hash # An interface for the common fields on a HackerOne Invitation interface InvitationInterface { _id: ID! } # User invitation preference type enum InvitationPreferenceTypeEnum { always bounty_only never } # Invitations can be of multiple types union InvitationUnion = InvitationsSoftLaunch # The connection type for InvitationUnion. type InvitationUnionConnection { # A list of edges. edges: [InvitationUnionEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type InvitationUnionEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: InvitationUnion } # An invitation to join a private team as a hacker type InvitationsSoftLaunch implements InvitationInterface, Node { _id: ID! expires_at: String id: ID! markdown_message: String message: String team: Team! token: String } # A JIRA integration for a team type JiraIntegration implements Node { assignee: String base_url: String! created_at: String! custom: String description: String! id: ID! issue_type: Int! labels: String pid: Int! summary: String! team: Team! updated_at: String! } # A JIRA Oauth for a team type JiraOauth implements Node { # Assignables for a project assignables(project_id: Int!): [Hash] configured: Boolean! created_at: String! id: ID! issue_types: [Hash] projects: [Hash] site: String team: Team! updated_at: String! } # A JIRA webhook for a team type JiraWebhook implements Node { created_at: String! id: ID! last_event_received_at: String last_token_issued_at: String process_assignee_change: Boolean! process_comment_add: Boolean! process_priority_change: Boolean! process_resolution_change: Boolean! process_status_change: Boolean! team: Team! updated_at: String! } # Autogenerated input type of LeavePrivateProgram input LeavePrivateProgramInput { # A unique identifier for the client performing the mutation. clientMutationId: String handle: String! } # Autogenerated return type of LeavePrivateProgram type LeavePrivateProgramPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [Error] me: User was_successful: Boolean! } # Autogenerated input type of LockReport input LockReportInput { # A unique identifier for the client performing the mutation. clientMutationId: String report_id: ID! } # Autogenerated return type of LockReport type LockReportPayload { activities: ActivityUnionConnection activity: ActivitiesComment # A unique identifier for the client performing the mutation. clientMutationId: String new_activity_edge: ActivityUnionEdge report: Report } # Settings for a user's Lufthansa Account type LufthansaAccount implements Node { created_at: String! first_name: String! id: ID! last_name: String! number: String! updated_at: String! } # Autogenerated input type of MarkReportAsNeedsMoreInfo input MarkReportAsNeedsMoreInfoInput { # A unique identifier for the client performing the mutation. clientMutationId: String message: String! report_id: ID! } # Autogenerated return type of MarkReportAsNeedsMoreInfo type MarkReportAsNeedsMoreInfoPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [Error] query: Query report: Report report_id: ID! was_successful: Boolean! } # Autogenerated input type of MarkReportAsNoise input MarkReportAsNoiseInput { # A unique identifier for the client performing the mutation. clientMutationId: String message: String! report_id: ID! } # Autogenerated return type of MarkReportAsNoise type MarkReportAsNoisePayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [Error] query: Query report: Report was_successful: Boolean! } # Autogenerated input type of MarkReportAsSignal input MarkReportAsSignalInput { # A unique identifier for the client performing the mutation. clientMutationId: String message: String report_id: ID! } # Autogenerated return type of MarkReportAsSignal type MarkReportAsSignalPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [Error] query: Query report: Report was_successful: Boolean! } type Mutation { acceptInvitation(input: AcceptInvitationInput!): AcceptInvitationPayload acknowledgeProgramHealthAcknowledgement(input: AcknowledgeProgramHealthAcknowledgementMutationInput!): AcknowledgeProgramHealthAcknowledgementMutationPayload archiveStructuredScope(input: ArchiveStructuredScopeInput!): ArchiveStructuredScopePayload createActivityComment(input: CreateActivityCommentInput!): CreateActivityCommentPayload createBounty(input: CreateBountyInput!): CreateBountyPayload createBountySuggestion(input: CreateBountySuggestionInput!): CreateBountySuggestionPayload createCoinbasePayoutPreference(input: CreateCoinbasePayoutPreferenceInput!): CreateCoinbasePayoutPreferencePayload createCurrencycloudBankTransferPayoutPreference(input: CreateCurrencycloudBankTransferPayoutPreferenceInput!): CreateCurrencycloudBankTransferPayoutPreferencePayload createJiraOauthUrl(input: CreateJiraOauthUrlInput!): CreateJiraOauthUrlPayload createJiraWebhookToken(input: CreateJiraWebhookTokenInput!): CreateJiraWebhookTokenPayload createMailingAddress(input: CreateMailingAddressInput!): CreateMailingAddressPayload createOrUpdateHackeroneToJiraEventsConfiguration(input: CreateOrUpdateHackeroneToJiraEventsConfigurationInput!): CreateOrUpdateHackeroneToJiraEventsConfigurationPayload createOrUpdateJiraIntegration(input: CreateOrUpdateJiraIntegrationInput!): CreateOrUpdateJiraIntegrationPayload createPaypalPreference(input: CreatePaypalPreferenceInput!): CreatePaypalPreferencePayload createSlackPipeline(input: CreateSlackPipelineInput!): CreateSlackPipelinePayload createStructuredScope(input: CreateStructuredScopeInput!): CreateStructuredScopePayload createSurveyAnswer(input: CreateSurveyAnswerInput!): CreateSurveyAnswerPayload createSwag(input: CreateSwagInput!): CreateSwagPayload createTaxForm(input: CreateTaxFormInput!): CreateTaxFormPayload createUserBountiesReport(input: CreateUserBountiesReportInput!): CreateUserBountiesReportPayload createUserLufthansaAccount(input: CreateUserLufthansaAccountInput!): CreateUserLufthansaAccountPayload deleteBiDirectionalJiraIntegration(input: DeleteBiDirectionalJiraIntegrationInput!): DeleteBiDirectionalJiraIntegrationPayload deleteJiraWebhook(input: DeleteJiraWebhookInput!): DeleteJiraWebhookPayload deletePhabricatorIntegration(input: DeletePhabricatorIntegrationInput!): DeletePhabricatorIntegrationPayload deleteSlackPipeline(input: DeleteSlackPipelineInput!): DeleteSlackPipelinePayload deleteTeamMember(input: DeleteTeamMemberInput!): DeleteTeamMemberPayload deleteTeamSlackIntegration(input: DeleteTeamSlackIntegrationInput!): DeleteTeamSlackIntegrationPayload deleteUserLufthansaAccount(input: DeleteUserLufthansaAccountInput!): DeleteUserLufthansaAccountPayload disableTwoFactorAuthentication(input: DisableTwoFactorAuthenticationInput!): DisableTwoFactorAuthenticationPayload dismissProgramHealthAcknowledgement(input: DismissProgramHealthAcknowledgementMutationInput!): DismissProgramHealthAcknowledgementMutationPayload enableTwoFactorAuthentication(input: EnableTwoFactorAuthenticationInput!): EnableTwoFactorAuthenticationPayload enableUser(input: EnableUserInput!): EnableUserPayload enrollForProgram(input: EnrollForProgramInput!): EnrollForProgramPayload generateMfaOtp(input: GenerateMfaOtpInput!): GenerateMfaOtpPayload generateTaxFormUrl(input: GenerateTaxFormUrlInput!): GenerateTaxFormUrlPayload leavePrivateProgram(input: LeavePrivateProgramInput!): LeavePrivateProgramPayload lockReport(input: LockReportInput!): LockReportPayload markReportAsNeedsMoreInfo(input: MarkReportAsNeedsMoreInfoInput!): MarkReportAsNeedsMoreInfoPayload markReportAsNoise(input: MarkReportAsNoiseInput!): MarkReportAsNoisePayload markReportAsSignal(input: MarkReportAsSignalInput!): MarkReportAsSignalPayload rejectInvitation(input: RejectInvitationInput!): RejectInvitationPayload updateAutomaticInvites(input: UpdateAutomaticInvitesInput!): UpdateAutomaticInvitesPayload updateBackupCodes(input: UpdateBackupCodesInput!): UpdateBackupCodesPayload updateInvitationPreferences(input: UpdateInvitationPreferencesInput!): UpdateInvitationPreferencesPayload updateJiraWebhook(input: UpdateJiraWebhookInput!): UpdateJiraWebhookPayload updateMe(input: UpdateMeInput!): UpdateMePayload updatePhabricatorIntegration(input: UpdatePhabricatorIntegrationInput!): UpdatePhabricatorIntegrationPayload updateReportStateToNeedsMoreInfo(input: UpdateReportStateToNeedsMoreInfoInput!): UpdateReportStateToNeedsMoreInfoPayload updateReportStructuredScope(input: UpdateReportStructuredScopeInput!): UpdateReportStructuredScopePayload updateSlackPipeline(input: UpdateSlackPipelineInput!): UpdateSlackPipelinePayload updateSlackUser(input: UpdateSlackUserInput!): UpdateSlackUserPayload updateStructuredScope(input: UpdateStructuredScopeInput!): UpdateStructuredScopePayload updateTeamFancySlackIntegration(input: UpdateTeamFancySlackIntegrationInput!): UpdateTeamFancySlackIntegrationPayload updateTeamMemberVisibility(input: UpdateTeamMemberVisibilityInput!): UpdateTeamMemberVisibilityPayload updateTeamSubmissionState(input: UpdateTeamSubmissionStateInput!): UpdateTeamSubmissionStatePayload updateTeamSubscription(input: UpdateTeamSubscriptionInput!): UpdateTeamSubscriptionPayload updateTeamWeakness(input: UpdateTeamWeaknessInput!): UpdateTeamWeaknessPayload updateUserEmail(input: UpdateUserEmailInput!): UpdateUserEmailPayload updateUserLufthansaAccount(input: UpdateUserLufthansaAccountInput!): UpdateUserLufthansaAccountPayload updateUserPassword(input: UpdateUserPasswordInput!): UpdateUserPasswordPayload } # An object with an ID. interface Node { # ID of the object. id: ID! } # Possible directions for sorting a collection enum OrderDirection { ASC DESC } # Information about pagination in a connection. type PageInfo { # When paginating forwards, the cursor to continue. endCursor: String # When paginating forwards, are there more items? hasNextPage: Boolean! # When paginating backwards, are there more items? hasPreviousPage: Boolean! # When paginating backwards, the cursor to continue. startCursor: String } # The connection type for User. type ParticipantConnection { # A list of edges. edges: [ParticipantWithReputationEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! year_range: [Int] } # An edge in a connection. type ParticipantWithReputationEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: User # The participant's rank within the team rank: Int # The participant's reputation within the team reputation: Int } # A interface for the common fields on an Payout Preference interface PayoutPreferenceInterface { _id: ID! default: Boolean id: ID! } # A user can have payout preferences for different payment services union PayoutPreferenceUnion = CoinbasePayoutPreferenceType | CurrencycloudBankTransferPayoutPreferenceType | HackeronePayrollPayoutPreferenceType | PaypalPayoutPreferenceType # A Paypal Payout Preference type PaypalPayoutPreferenceType implements Node, PayoutPreferenceInterface { _id: ID! default: Boolean email: String id: ID! } # All available permissions enum PermissionEnum { program_management } # A Phabricator integration for a team type PhabricatorIntegration implements Node { base_url: String created_at: String! description: String id: ID! process_h1_comment_added: Boolean! process_h1_status_change: Boolean! process_phabricator_comment_added: Boolean! process_phabricator_status_change: Boolean! project_tags: String team: Team! title: String updated_at: String! } # Different possible profile picture sizes enum ProfilePictureSizes { # 110x110 large # 82x82 medium # 62x62 small # 260x260 xtralarge } # A program_health_acknowledgement for a team_member type ProgramHealthAcknowledgement implements Node { acknowledged: Boolean created_at: String dismissed: Boolean id: ID! reason: ProgramHealthAcknowledgementReasonEnum team_member: TeamMember! } # The connection type for ProgramHealthAcknowledgement. type ProgramHealthAcknowledgementConnection { # A list of edges. edges: [ProgramHealthAcknowledgementEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type ProgramHealthAcknowledgementEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: ProgramHealthAcknowledgement } # reason which reflect the state of a program health acknowledgement enum ProgramHealthAcknowledgementReasonEnum { failed ok paused review } # Root entity of the Hackerone Schema type Query implements Node { bank_transfer_reference: BankTransferReference clusters( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int order_by: ClusterOrder = {direction: ASC, field: BROWSING_FRIENDLY} ): ClusterConnection external_program(handle: String!): ExternalProgram id: ID! me: User # Fetches an object given its ID. node( # ID of the object. id: ID! ): Node query: Query! report(id: Int!): Report @deprecated(reason: "Query for a Report node at the root level is not recommended.") reports( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String database_id: Int = null # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int # Relay classic does not have support for starting paginationsomewhere in the # middle, see https://github.com/facebook/relay/issues/1312 Workaround is to # add a page argument till Relay supports this limit: Int = null order_by: ReportOrderInput = {direction: DESC, field: id} # Relay classic does not have support for starting paginationsomewhere in the # middle, see https://github.com/facebook/relay/issues/1312 Workaround is to # add a page argument till Relay supports this page: Int = null pre_submission_review_states: [ReportPreSubmissionReviewStateEnum] = null substate: ReportStateEnum without_scope: Boolean = null ): ReportConnection resource(url: URI): ResourceInterface session: Session severity_calculator: SeverityCalculator surveys( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String category: SurveyCategoryEnum # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): SurveyConnectionType team(handle: String!): Team @deprecated(reason: "Query for a Team node at the root level is not recommended. Ref T12456") teams( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String enrollable: Boolean # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int order_by: TeamOrderInput permissions: [PermissionEnum] = [] ): TeamConnection user(username: String!): User @deprecated(reason: "Query for a User node at the root level is not recommended. Ref T12456") users( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): UserConnection } # Autogenerated input type of RejectInvitation input RejectInvitationInput { # A unique identifier for the client performing the mutation. clientMutationId: String handle: String! } # Autogenerated return type of RejectInvitation type RejectInvitationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String me: User } # A HackerOne report type Report implements Node, ResourceInterface { _id: ID! activities( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int not_types: [ActivityTypes] order_by: ActivityOrderInput types: [ActivityTypes] # A timestamp encoded as a string that. When provided, only activities with a # updated_at greater than this value will be resolved. Example: # activities(updated_at_after: "2017-11-30 13:37:12 UTC") updated_at_after: String ): ActivityConnection allow_singular_disclosure_after: String allow_singular_disclosure_at: String assignee: AssigneeUnion attachments: [Attachment] bounties: [Bounty] bounty_awarded_at: String bug_reporter_agreed_on_going_public_at: String cloned_from: Report closed_at: String comments_closed: Boolean created_at: String! cve_ids: [String] disclosed_at: String first_program_activity_at: String i_can_anc_review: Boolean id: ID! latest_activity_at: String latest_public_activity_at: String latest_public_activity_of_reporter_at: String latest_public_activity_of_team_at: String mediation_requested_at: String original_report: Report pre_submission_review_state: String reference: ID reference_link: String reporter: User severity: Severity singular_disclosure_allowed: Boolean singular_disclosure_disabled: Boolean stage: String stale: Boolean state: String structured_scope: StructuredScope substate: String! suggested_bounty: String summaries: [Summary] swag: [Swag] swag_awarded_at: String team: Team team_member_agreed_on_going_public_at: String title: String triaged_at: String # A pre-submission trigger that notified the hacker before submission. This # field is only present for reports filed after February 14, 2016. triggered_pre_submission_trigger: Trigger url: URI! vulnerability_information: String vulnerability_information_html: String weakness: Weakness } # A interface for the common fields on an HackerOne Report Activity interface ReportActivityInterface { attachments: [Attachment] automated_response: Boolean genius_execution_id: ID @deprecated(reason: "This is about to be replaced by .genius_execution") report: Report } # The connection type for Report. type ReportConnection { # Groups and counts reports by the severity rating count_by_severity: [CountBySeverity]! # A list of edges. edges: [ReportEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type ReportEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: Report } # Filters which can be used to query reports enum ReportFilterEnum { assigned assigned_to_me bounty_awarded going_public_team going_public_user hacker_requested_mediation ineligible_for_bounty needs_first_program_response no_bounty_awarded no_swag_awarded not_public public reporter_is_active stale swag_awarded unassigned } # Fields on which a collection of reports can be ordered enum ReportOrderField { created_at id latest_activity_at } input ReportOrderInput { direction: OrderDirection! field: ReportOrderField! } # Pre submission review states a report can be in enum ReportPreSubmissionReviewStateEnum { pre_submission_accepted pre_submission_needs_more_info pre_submission_pending pre_submission_rejected } # States a report can be in enum ReportStateEnum { closed duplicate informative needs_more_info new not_applicable open pre_submission resolved spam triaged } # The connection type for User. type ReporterConnection { # A list of edges. edges: [UserEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # Represents a type that can be retrieved by a URL. interface ResourceInterface { url: URI! } # A HackerOne session type Session implements Node { csrf_token: String! @deprecated(reason: "This token is used to support legacy operations in HackerOne's frontend") id: ID! } # A HackerOne severity for a report type Severity implements Node { _id: ID! attack_complexity: SeverityAttackComplexityEnum attack_vector: SeverityAttackVectorEnum author_type: SeverityAuthorEnum availability: SeverityAvailabilityEnum confidentiality: SeverityConfidentialityEnum created_at: String id: ID! integrity: SeverityIntegrityEnum privileges_required: SeverityPrivilegesRequiredEnum rating: SeverityRatingEnum scope: SeverityScopeEnum score: Float user_id: Int user_interaction: SeverityUserInteractionEnum } # Severity attack complexity enum SeverityAttackComplexityEnum { high low } # Severity attack vector enum SeverityAttackVectorEnum { adjacent local network physical } # Severity author enum SeverityAuthorEnum { Team User } # Severity availability enum SeverityAvailabilityEnum { high low none } # Calculate CVSS Severity score and rating type SeverityCalculator implements Node { calculated_max_severity(availability_requirement: SeveritySecurityRequirementEnum, confidentiality_requirement: SeveritySecurityRequirementEnum, integrity_requirement: SeveritySecurityRequirementEnum): SeverityRatingEnum id: ID! } # Severity confidentiality enum SeverityConfidentialityEnum { high low none } # Severity integrity enum SeverityIntegrityEnum { high low none } # Severity privileges required enum SeverityPrivilegesRequiredEnum { high low none } # Severity rating enum SeverityRatingEnum { critical high low medium none } # Severity scope enum SeverityScopeEnum { changed unchanged } # Severity security requirement rating enum SeveritySecurityRequirementEnum { high low medium none } # Severity user interaction enum SeverityUserInteractionEnum { none required } # SLA types enum SlaTypeEnum { first_program_response report_triage } # Slack channel type SlackChannel implements Node { # ID of the object. id: ID! name: String! } # A Slack integration for a team type SlackIntegration implements Node { channel: String @deprecated(reason: "this field is not used in our new Slack integration") channels: [SlackChannel] id: ID! should_fetch_slack_channels: Boolean! should_fetch_slack_users: Boolean! team: Team! team_url: String! users: [SlackUser] } # A Slack Pipeline Configuration for notifications type SlackPipeline implements Node, ResourceInterface { # The primary key from the database _id: ID! channel: String! descriptive_label: String id: ID! notification_report_agreed_on_going_public: Boolean! notification_report_assignee_changed: Boolean! notification_report_became_public: Boolean! notification_report_bounty_paid: Boolean! notification_report_bounty_suggested: Boolean! notification_report_bug_closed_as_spam: Boolean! notification_report_bug_duplicate: Boolean! notification_report_bug_informative: Boolean! notification_report_bug_needs_more_info: Boolean! notification_report_bug_new: Boolean! notification_report_bug_not_applicable: Boolean! notification_report_closed_as_resolved: Boolean! notification_report_comments_closed: Boolean! notification_report_created: Boolean! notification_report_internal_comment_added: Boolean! notification_report_manually_disclosed: Boolean! notification_report_not_eligible_for_bounty: Boolean! notification_report_public_comment_added: Boolean! notification_report_reopened: Boolean! notification_report_swag_awarded: Boolean! notification_report_triaged: Boolean! team: Team! updated_at: String! url: URI! } # The connection type for SlackPipeline. type SlackPipelineConnection { # A list of edges. edges: [SlackPipelineEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type SlackPipelineEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: SlackPipeline } # Slack user type SlackUser implements Node { # The id provided by Slack _id: String! avatar_small: String! deleted: Boolean! email: String! # ID of the object. id: ID! name: String! real_name: String } # A static participant for a team type StaticParticipant implements Node { _id: ID! avatar(size: ProfilePictureSizes!): String! bio: String external_url: String id: ID! name: String! year: String } # The connection type for StaticParticipant. type StaticParticipantConnection { # A list of edges. edges: [StaticParticipantEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type StaticParticipantEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: StaticParticipant } # A defined scope of a HackerOne program type StructuredScope implements Node, ResourceInterface { _id: ID! archived_at: String asset_identifier: String! asset_type: StructuredScopedAssetTypeEnum availability_requirement: SeveritySecurityRequirementEnum confidentiality_requirement: SeveritySecurityRequirementEnum created_at: String! eligible_for_bounty: Boolean eligible_for_submission: Boolean id: ID! instruction: String integrity_requirement: SeveritySecurityRequirementEnum max_severity: SeverityRatingEnum reference: String rendered_instruction: String reports( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): ReportConnection structured_scope_versions( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): StructuredScopeVersionConnection team: Team updated_at: String url: URI! } # An edge in a connection. type StructuredScopeEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: StructuredScope } # A versioned log of a scope of a HackerOne program type StructuredScopeVersion implements Node, ResourceInterface { _id: ID! archived_at: String created_at: String! id: ID! instruction: String max_severity: String team: Team url: URI! } # The connection type for StructuredScopeVersion. type StructuredScopeVersionConnection { # A list of edges. edges: [StructuredScopeVersionEdge] # Information to aid in pagination. pageInfo: PageInfo! } # An edge in a connection. type StructuredScopeVersionEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: StructuredScopeVersion } # Structured Scope asset type enum enum StructuredScopedAssetTypeEnum { APPLE_STORE_APP_ID CIDR DOWNLOADABLE_EXECUTABLES GOOGLE_PLAY_APP_ID HARDWARE OTHER OTHER_APK OTHER_IPA SOURCE_CODE TESTFLIGHT URL WINDOWS_APP_STORE_APP_ID } # The connection type for StructuredScope. type StructuredScopesConnection { # A list of edges. edges: [StructuredScopeEdge] max_updated_at: String # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # Submission states enum SubmissionStateEnum { disabled open paused } # Team subscription action enum enum SubscriptionActionEnum { subscribe_to_all unsubscribe_from_all } # A HackerOne summary for a report type Summary implements Node { _id: ID! category: String! @deprecated(reason: "The implementation of this field contains hard to reason about polymorphism") content: String! created_at: String! id: ID! updated_at: String! user: User! } # A HackerOne survey type Survey implements Node, ResourceInterface { category: String! id: ID! question: String! structured_responses( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): SurveyStructuredResponseConnectionType url: URI! } # Survey categories enum SurveyCategoryEnum { invitation_rejection } # The connection type for Survey. type SurveyConnectionType { # A list of edges. edges: [SurveyEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type SurveyEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: Survey } # Prepared survey response reasons type SurveyStructuredResponse implements Node { _id: ID! enabled: Boolean! helper_text: String id: ID! reason: String! survey: Survey } # The connection type for SurveyStructuredResponse. type SurveyStructuredResponseConnectionType { # A list of edges. edges: [SurveyStructuredResponseEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type SurveyStructuredResponseEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: SurveyStructuredResponse } # A HackerOne swag awarded for a report type Swag implements Node { _id: ID! address: Address @deprecated(reason: "This field is deprecated. The preferred way to access this data is using swag.user.address.") created_at: String id: ID! report: Report sent: Boolean! team: Team user: User } # The connection type for Swag. type SwagConnection { # A list of edges. edges: [SwagEdge] # Information to aid in pagination. pageInfo: PageInfo! } # An edge in a connection. type SwagEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: Swag } # A tax form for a user type TaxForm implements Node { created_at: String! hello_sign_client_id: String id: ID! signed_at: String status: TaxFormStateEnum! type: TaxFormTypeEnum! url: String } # Status of a tax form enum TaxFormStateEnum { expired needs_review rejected requested unavailable valid } # Type of a tax form enum TaxFormTypeEnum { W8BEN W8BENE W9 W9Corporate } # A HackerOne team type Team implements Node, ResourceInterface { # The primary key from the database _id: ID! about: String abuse: Boolean activities( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int not_types: [ActivityTypes] order_by: ActivityOrderInput types: [ActivityTypes] # A timestamp encoded as a string that. When provided, only activities with a # updated_at greater than this value will be resolved. Example: # activities(updated_at_after: "2017-11-30 13:37:12 UTC") updated_at_after: String ): ActivityConnection automatic_invites: Boolean base_bounty: Int bug_count: Int child_teams( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): TeamConnection common_responses( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int order_by: CommonResponseOrder = {direction: ASC, field: title} ): CommonResponseConnection created_at: String currency: String fancy_slack_integration: Boolean fancy_slack_integration_enabled: Boolean! first_response_time: Float hackerone_to_jira_events_configuration: HackeroneToJiraEventsConfiguration handle: String! has_avatar: Boolean has_policy: Boolean i_can_create_jira_webhook: Boolean i_can_destroy_jira_webhook: Boolean! i_can_view_base_bounty: Boolean i_can_view_jira_integration: Boolean i_can_view_jira_webhook: Boolean i_can_view_phabricator_integration: Boolean i_can_view_program_health: Boolean! i_can_view_reports_resolved: Boolean i_can_view_weaknesses: Boolean i_cannot_create_jira_webhook_reasons: [TeamCannotCreateJiraWebhookReasons]! id: ID! inbox_views( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int order_by: TeamInboxViewOrder = {direction: ASC, field: position} visible: Boolean ): TeamInboxViewConnection internet_bug_bounty: Boolean jira_integration: JiraIntegration jira_oauth: JiraOauth jira_phase_3_enabled: Boolean! jira_webhook: JiraWebhook maximum_number_of_team_mediation_requests: Float name: String! new_staleness_threshold: Int offers_bounties: Boolean offers_swag: Boolean open_soft_launch_invitations( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): InvitationUnionConnection @deprecated(reason: "This should be a generic invitation connection.\n Used interim until generic invitation type is defined") participants( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int limit: Int = null year: Int = null ): ParticipantConnection phabricator_integration: PhabricatorIntegration policy_html: String profile_picture(size: ProfilePictureSizes!): String! report_submission_form_intro: String report_template: String reporters( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): ReporterConnection reports( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String database_id: Int = null # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int # Relay classic does not have support for starting paginationsomewhere in the # middle, see https://github.com/facebook/relay/issues/1312 Workaround is to # add a page argument till Relay supports this limit: Int = null order_by: ReportOrderInput = {direction: DESC, field: id} # Relay classic does not have support for starting paginationsomewhere in the # middle, see https://github.com/facebook/relay/issues/1312 Workaround is to # add a page argument till Relay supports this page: Int = null pre_submission_review_states: [ReportPreSubmissionReviewStateEnum] = null state: ReportStateEnum substate: ReportStateEnum violates_minimum_sla: SlaTypeEnum without_scope: Boolean = null ): ReportConnection review_requested_at: String sla_failed_count: Int sla_missed_count: Int slack_integration: SlackIntegration slack_pipelines( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): SlackPipelineConnection state: TeamState! static_participants( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): StaticParticipantConnection structured_scopes( # Returns the elements in the list that come after the specified cursor. after: String archived: Boolean = null asset_type: String = null # Returns the elements in the list that come before the specified cursor. before: String eligible_for_submission: Boolean = null # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int search: String = null ): StructuredScopesConnection submission_state: SubmissionStateEnum! target_signal: Float team_member_groups( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): TeamMemberGroupConnection team_members( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int order_by: TeamMemberOrder = {direction: ASC, field: username} ): TeamMemberConnection triage_active: Boolean triage_time: Float triaged_staleness_threshold: Int twitter_handle: String updated_at: String url: URI! weaknesses( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String cluster_id: ID = null # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int order_by: WeaknessOrder = {direction: ASC, field: name} search: String = null team_weakness_state: [TeamWeaknessStates] = [enabled, disabled, hidden] ): TeamWeaknessConnection website: String whitelisted_hackers( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): ReporterConnection } # Different reasons why a team cannot create a jira webhook enum TeamCannotCreateJiraWebhookReasons { CANNOT_VIEW FEATURE_GATED PROGRAM_PERMISSION_REQUIRED } # The connection type for Team. type TeamConnection { # A list of edges. edges: [TeamEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type TeamEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: Team } # A team report filter preset type TeamInboxView implements Node { _id: ID! assigned_to_group_ids: [Int!]! assigned_to_user_ids: [Int!]! built_in: Boolean! created_at: String! filters: [ReportFilterEnum!]! hackathons: [Int!]! id: ID! key: String! name: String! position: Int! reporter_ids: [Int!]! severities: [String!]! substates: [ReportStateEnum!]! team: Team! text_query: String! updated_at: String! visible: Boolean! } # The connection type for TeamInboxView. type TeamInboxViewConnection { # A list of edges. edges: [TeamInboxViewEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type TeamInboxViewEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: TeamInboxView } input TeamInboxViewOrder { direction: OrderDirection! field: TeamInboxViewOrderField! } # Fields on which a collection of team inbox views can be ordered enum TeamInboxViewOrderField { position } # A HackerOne team member type TeamMember implements Node { # The primary key from the database _id: ID! auto_subscribe: Boolean concealed: Boolean created_at: String! i_can_leave_team: Boolean! id: ID! permissions: [String]! slack_user_id: String team: Team! user: User! } # The connection type for TeamMember. type TeamMemberConnection { # A list of edges. edges: [TeamMemberEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type TeamMemberEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: TeamMember } # A HackerOne team member group type TeamMemberGroup implements Node { _id: ID! created_at: String # ID of the object. id: ID! name: String! permissions: [String]! } # The connection type for TeamMemberGroup. type TeamMemberGroupConnection { # A list of edges. edges: [TeamMemberGroupEdge] # Information to aid in pagination. pageInfo: PageInfo! } # An edge in a connection. type TeamMemberGroupEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: TeamMemberGroup } input TeamMemberOrder { direction: OrderDirection! field: TeamMemberOrderField! } # Fields on which a collection of team members can be ordered enum TeamMemberOrderField { username } # Fields on which a collection of Teams can be ordered enum TeamOrderField { missing_pressure name } input TeamOrderInput { direction: OrderDirection! field: TeamOrderField! } # Different possible team states enum TeamState { da_mode inactive public_mode sandboxed soft_launched } # Team configuration of a weakness type TeamWeakness implements Node { id: ID! instruction: String state: TeamWeaknessStates team: Team! weakness: Weakness! } # The connection type for Weakness. type TeamWeaknessConnection { # A list of edges. edges: [TeamWeaknessEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type TeamWeaknessEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: Weakness team_weakness: TeamWeakness } # Possible states of how a weakness can be configured for a team enum TeamWeaknessStates { disabled enabled hidden } # A HackerOne trigger type Trigger implements Node { _id: ID! id: ID! title: String! } # Tshirt size enum TshirtSizeEnum { M_Large M_Medium M_Small M_XLarge M_XXLarge W_Large W_Medium W_Small W_XLarge W_XXLarge } # Represents a RFC compliant URI string. It is often used to fetch an object. scalar URI # Autogenerated input type of UpdateAutomaticInvites input UpdateAutomaticInvitesInput { # A unique identifier for the client performing the mutation. clientMutationId: String enabled: Boolean! handle: String! } # Autogenerated return type of UpdateAutomaticInvites type UpdateAutomaticInvitesPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash team: Team } # Autogenerated input type of UpdateBackupCodes input UpdateBackupCodesInput { backup_codes: [String]! # A unique identifier for the client performing the mutation. clientMutationId: String password: String! signature: String! totp_code: String! totp_secret: String! } # Autogenerated return type of UpdateBackupCodes type UpdateBackupCodesPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [String] me: User } # Autogenerated input type of UpdateInvitationPreferences input UpdateInvitationPreferencesInput { # A unique identifier for the client performing the mutation. clientMutationId: String invitation_preference: InvitationPreferenceTypeEnum! } # Autogenerated return type of UpdateInvitationPreferences type UpdateInvitationPreferencesPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # Autogenerated input type of UpdateJiraWebhook input UpdateJiraWebhookInput { # A unique identifier for the client performing the mutation. clientMutationId: String jira_webhook_id: ID! process_assignee_change: Boolean process_comment_add: Boolean process_priority_change: Boolean process_resolution_change: Boolean process_status_change: Boolean } # Autogenerated return type of UpdateJiraWebhook type UpdateJiraWebhookPayload { # A unique identifier for the client performing the mutation. clientMutationId: String jira_webhook: JiraWebhook! } # Autogenerated input type of UpdateMe input UpdateMeInput { # A unique identifier for the client performing the mutation. clientMutationId: String tshirt_size: String! } # Autogenerated return type of UpdateMe type UpdateMePayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # Autogenerated input type of UpdatePhabricatorIntegration input UpdatePhabricatorIntegrationInput { api_token: String base_url: String # A unique identifier for the client performing the mutation. clientMutationId: String description: String process_h1_comment_added: Boolean process_h1_status_change: Boolean process_phabricator_comment_added: Boolean process_phabricator_status_change: Boolean team_id: ID! title: String } # Autogenerated return type of UpdatePhabricatorIntegration type UpdatePhabricatorIntegrationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash team: Team } # Autogenerated input type of UpdateReportStateToNeedsMoreInfo input UpdateReportStateToNeedsMoreInfoInput { # A unique identifier for the client performing the mutation. clientMutationId: String message: String! report_id: ID! } # Autogenerated return type of UpdateReportStateToNeedsMoreInfo type UpdateReportStateToNeedsMoreInfoPayload { # A unique identifier for the client performing the mutation. clientMutationId: String report: Report } # Autogenerated input type of UpdateReportStructuredScope input UpdateReportStructuredScopeInput { # A unique identifier for the client performing the mutation. clientMutationId: String report_id: ID! structured_scope_id: ID } # Autogenerated return type of UpdateReportStructuredScope type UpdateReportStructuredScopePayload { # A unique identifier for the client performing the mutation. clientMutationId: String report: Report } # Autogenerated input type of UpdateSlackPipeline input UpdateSlackPipelineInput { channel: String! # A unique identifier for the client performing the mutation. clientMutationId: String descriptive_label: String notification_report_agreed_on_going_public: Boolean! notification_report_assignee_changed: Boolean! notification_report_became_public: Boolean! notification_report_bounty_paid: Boolean! notification_report_bounty_suggested: Boolean! notification_report_bug_closed_as_spam: Boolean! notification_report_bug_duplicate: Boolean! notification_report_bug_informative: Boolean! notification_report_bug_needs_more_info: Boolean! notification_report_bug_new: Boolean! notification_report_bug_not_applicable: Boolean! notification_report_closed_as_resolved: Boolean! notification_report_comments_closed: Boolean! notification_report_created: Boolean! notification_report_internal_comment_added: Boolean! notification_report_manually_disclosed: Boolean! notification_report_not_eligible_for_bounty: Boolean! notification_report_public_comment_added: Boolean! notification_report_reopened: Boolean! notification_report_swag_awarded: Boolean! notification_report_triaged: Boolean! slack_pipeline_id: ID! } # Autogenerated return type of UpdateSlackPipeline type UpdateSlackPipelinePayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash slack_pipeline: SlackPipeline! } # Autogenerated input type of UpdateSlackUser input UpdateSlackUserInput { # A unique identifier for the client performing the mutation. clientMutationId: String slack_user_id: String! team_member_id: ID! } # Autogenerated return type of UpdateSlackUser type UpdateSlackUserPayload { # A unique identifier for the client performing the mutation. clientMutationId: String team_member: TeamMember! } # Autogenerated input type of UpdateStructuredScope input UpdateStructuredScopeInput { asset_identifier: String availability_requirement: String # A unique identifier for the client performing the mutation. clientMutationId: String confidentiality_requirement: String eligible_for_bounty: Boolean eligible_for_submission: Boolean instruction: String integrity_requirement: String reference: String structured_scope_id: ID! } # Autogenerated return type of UpdateStructuredScope type UpdateStructuredScopePayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash query: Query structured_scope: StructuredScope } # Autogenerated input type of UpdateTeamFancySlackIntegration input UpdateTeamFancySlackIntegrationInput { # A unique identifier for the client performing the mutation. clientMutationId: String fancy_slack_integration: Boolean! team_id: ID! } # Autogenerated return type of UpdateTeamFancySlackIntegration type UpdateTeamFancySlackIntegrationPayload { # A unique identifier for the client performing the mutation. clientMutationId: String team: Team } # Autogenerated input type of UpdateTeamMemberVisibility input UpdateTeamMemberVisibilityInput { # A unique identifier for the client performing the mutation. clientMutationId: String concealed: Boolean! team_member_id: ID! } # Autogenerated return type of UpdateTeamMemberVisibility type UpdateTeamMemberVisibilityPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash team_member: TeamMember } # Autogenerated input type of UpdateTeamSubmissionState input UpdateTeamSubmissionStateInput { # A unique identifier for the client performing the mutation. clientMutationId: String handle: String! submission_state: SubmissionStateEnum! } # Autogenerated return type of UpdateTeamSubmissionState type UpdateTeamSubmissionStatePayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash team: Team } # Autogenerated input type of UpdateTeamSubscription input UpdateTeamSubscriptionInput { action: SubscriptionActionEnum auto_subscribe: Boolean! # A unique identifier for the client performing the mutation. clientMutationId: String team_member_id: ID! } # Autogenerated return type of UpdateTeamSubscription type UpdateTeamSubscriptionPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash team_member: TeamMember } # Autogenerated input type of UpdateTeamWeakness input UpdateTeamWeaknessInput { # A unique identifier for the client performing the mutation. clientMutationId: String instruction: String state: TeamWeaknessStates! team_weakness_id: ID! } # Autogenerated return type of UpdateTeamWeakness type UpdateTeamWeaknessPayload { # A unique identifier for the client performing the mutation. clientMutationId: String query: Query team_weakness: TeamWeakness } # Autogenerated input type of UpdateUserEmail input UpdateUserEmailInput { # A unique identifier for the client performing the mutation. clientMutationId: String email: String! password: String! } # Autogenerated return type of UpdateUserEmail type UpdateUserEmailPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: [Error] me: User was_successful: Boolean! } # Autogenerated input type of UpdateUserLufthansaAccount input UpdateUserLufthansaAccountInput { # A unique identifier for the client performing the mutation. clientMutationId: String first_name: String! last_name: String! number: String! } # Autogenerated return type of UpdateUserLufthansaAccount type UpdateUserLufthansaAccountPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # Autogenerated input type of UpdateUserPassword input UpdateUserPasswordInput { # A unique identifier for the client performing the mutation. clientMutationId: String current_password: String! password: String! password_confirmation: String! } # Autogenerated return type of UpdateUserPassword type UpdateUserPasswordPayload { # A unique identifier for the client performing the mutation. clientMutationId: String errors: Hash me: User } # A HackerOne user type User implements Node, ResourceInterface { _id: ID! address: Address authentication_service: AuthenticationServiceEnum! badges( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): BadgesUsersConnection bio: String bounties( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String currency: CurrencyCode = null # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): BountyConnection created_at: String! demo_hacker: Boolean! disabled: Boolean! duplicate_users( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): UserConnection email: String i_can_update_username: Boolean id: ID! impact: Float impact_percentile: Float invitation_preference: InvitationPreferenceTypeEnum location: String lufthansa_account: LufthansaAccount member_of_verified_team: Boolean membership(team_handle: String!): TeamMember memberships( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): TeamMemberConnection name: String next_update_username_date: String otp_backup_codes: [String] payout_preferences: [PayoutPreferenceUnion] profile_picture(size: ProfilePictureSizes!): String! profile_pictures: Hash! @deprecated(reason: "Returns all the possible profile pictures instead of just the one you want use .profile_picture instead.") program_health_acknowledgements( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): ProgramHealthAcknowledgementConnection rank: Int remaining_reports(team_handle: String!): Int reports( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String database_id: Int = null # Returns the first _n_ elements from the list. first: Int handle: String # Returns the last _n_ elements from the list. last: Int # Relay classic does not have support for starting paginationsomewhere in the # middle, see https://github.com/facebook/relay/issues/1312 Workaround is to # add a page argument till Relay supports this limit: Int = null order_by: ReportOrderInput = {direction: DESC, field: id} # Relay classic does not have support for starting paginationsomewhere in the # middle, see https://github.com/facebook/relay/issues/1312 Workaround is to # add a page argument till Relay supports this page: Int = null pre_submission_review_states: [ReportPreSubmissionReviewStateEnum] = null substate: ReportStateEnum without_scope: Boolean = null ): ReportConnection reputation: Int signal: Float signal_percentile: Float soft_launch_invitation(team_handle: String!): InvitationsSoftLaunch swag( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): SwagConnection tax_form: TaxForm teams( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String enrollable: Boolean # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int order_by: TeamOrderInput permissions: [PermissionEnum] = [] ): TeamConnection totp_enabled: Boolean totp_supported: Boolean triage_user: Boolean tshirt_size: TshirtSizeEnum unconfirmed_email: String url: URI! username: String! website: String whitelisted_teams( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): TeamConnection } # The connection type for User. type UserConnection { # A list of edges. edges: [UserEdge] # Information to aid in pagination. pageInfo: PageInfo! total_count: Int! } # An edge in a connection. type UserEdge { # A cursor for use in pagination. cursor: String! # The item at the end of the edge. node: User } # The type of vulnerability on a HackerOne report type Weakness implements Node { _id: ID! clusters( # Returns the elements in the list that come after the specified cursor. after: String # Returns the elements in the list that come before the specified cursor. before: String # Returns the first _n_ elements from the list. first: Int # Returns the last _n_ elements from the list. last: Int ): ClusterConnection created_at: String! description: String external_id: String id: ID! name: String! updated_at: String! } input WeaknessOrder { direction: OrderDirection! field: WeaknessOrderField! } # Fields on which a collection of weaknesses can be ordered enum WeaknessOrderField { name } graphql-ruby-1.11.10/benchmark/run.rb000066400000000000000000000113171414121453000173510ustar00rootroot00000000000000# frozen_string_literal: true TESTING_INTERPRETER = true require "graphql" require "jazz" require "benchmark/ips" require "ruby-prof" require "memory_profiler" module GraphQLBenchmark QUERY_STRING = GraphQL::Introspection::INTROSPECTION_QUERY DOCUMENT = GraphQL.parse(QUERY_STRING) SCHEMA = Jazz::Schema BENCHMARK_PATH = File.expand_path("../", __FILE__) CARD_SCHEMA = GraphQL::Schema.from_definition(File.read(File.join(BENCHMARK_PATH, "schema.graphql"))) ABSTRACT_FRAGMENTS = GraphQL.parse(File.read(File.join(BENCHMARK_PATH, "abstract_fragments.graphql"))) ABSTRACT_FRAGMENTS_2 = GraphQL.parse(File.read(File.join(BENCHMARK_PATH, "abstract_fragments_2.graphql"))) BIG_SCHEMA = GraphQL::Schema.from_definition(File.join(BENCHMARK_PATH, "big_schema.graphql")) BIG_QUERY = GraphQL.parse(File.read(File.join(BENCHMARK_PATH, "big_query.graphql"))) FIELDS_WILL_MERGE_SCHEMA = GraphQL::Schema.from_definition("type Query { hello: String }") FIELDS_WILL_MERGE_QUERY = GraphQL.parse("{ #{Array.new(5000, "hello").join(" ")} }") module_function def self.run(task) Benchmark.ips do |x| case task when "query" x.report("query") { SCHEMA.execute(document: DOCUMENT) } when "validate" x.report("validate - introspection ") { CARD_SCHEMA.validate(DOCUMENT) } x.report("validate - abstract fragments") { CARD_SCHEMA.validate(ABSTRACT_FRAGMENTS) } x.report("validate - abstract fragments 2") { CARD_SCHEMA.validate(ABSTRACT_FRAGMENTS_2) } x.report("validate - big query") { BIG_SCHEMA.validate(BIG_QUERY) } x.report("validate - fields will merge") { FIELDS_WILL_MERGE_SCHEMA.validate(FIELDS_WILL_MERGE_QUERY) } else raise("Unexpected task #{task}") end end end def self.profile # Warm up any caches: SCHEMA.execute(document: DOCUMENT) # CARD_SCHEMA.validate(ABSTRACT_FRAGMENTS) res = nil result = RubyProf.profile do # CARD_SCHEMA.validate(ABSTRACT_FRAGMENTS) res = SCHEMA.execute(document: DOCUMENT) end # printer = RubyProf::FlatPrinter.new(result) # printer = RubyProf::GraphHtmlPrinter.new(result) printer = RubyProf::FlatPrinterWithLineNumbers.new(result) printer.print(STDOUT, {}) end # Adapted from https://github.com/rmosolgo/graphql-ruby/issues/861 def self.profile_large_result schema = ProfileLargeResult::Schema document = ProfileLargeResult::ALL_FIELDS Benchmark.ips do |x| x.report("Querying for #{ProfileLargeResult::DATA.size} objects") { schema.execute(document: document) } end result = RubyProf.profile do schema.execute(document: document) end printer = RubyProf::FlatPrinter.new(result) # printer = RubyProf::GraphHtmlPrinter.new(result) # printer = RubyProf::FlatPrinterWithLineNumbers.new(result) printer.print(STDOUT, {}) report = MemoryProfiler.report do schema.execute(document: document) end report.pretty_print end module ProfileLargeResult DATA = 1000.times.map { { id: SecureRandom.uuid, int1: SecureRandom.random_number(100000), int2: SecureRandom.random_number(100000), string1: SecureRandom.base64, string2: SecureRandom.base64, boolean1: SecureRandom.random_number(1) == 0, boolean2: SecureRandom.random_number(1) == 0, int_array: 10.times.map { SecureRandom.random_number(100000) }, string_array: 10.times.map { SecureRandom.base64 }, boolean_array: 10.times.map { SecureRandom.random_number(1) == 0 }, } } class FooType < GraphQL::Schema::Object field :id, ID, null: false field :int1, Integer, null: false field :int2, Integer, null: false field :string1, String, null: false field :string2, String, null: false field :boolean1, Boolean, null: false field :boolean2, Boolean, null: false field :string_array, [String], null: false field :int_array, [Integer], null: false field :boolean_array, [Boolean], null: false end class QueryType < GraphQL::Schema::Object description "Query root of the system" field :foos, [FooType], null: false, description: "Return a list of Foo objects" def foos DATA end end class Schema < GraphQL::Schema query QueryType use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST end ALL_FIELDS = GraphQL.parse <<-GRAPHQL { foos { id int1 int2 string1 string2 boolean1 boolean2 stringArray intArray booleanArray } } GRAPHQL end end graphql-ruby-1.11.10/benchmark/schema.graphql000066400000000000000000000033701414121453000210400ustar00rootroot00000000000000# A big schema for testing type Query { node(id: ID!): Node } interface Node { id: ID! } interface Node2 { id: ID } interface Commentable { id: ID! comments: [Comment!]! } interface Named { name: String! } type Comment implements Node { author: Player body: String! id: ID! } type Card implements Node, Commentable, Node2, Named { name: String! converted_mana_cost: Int! mana_cost: String! colors: [Color!]! power: Int toughness: Int rules_text: String! id: ID! comments: [Comment!]! } type Printing implements Node, Commentable, Node2 { card: Card! expansion: Expansion! rarity: Rarity! artist: Artist! id: ID! comments: [Comment!]! } type Expansion implements Node, Commentable, Named { name: String! code: String! printings: [Printing!]! block: Block! id: ID! comments: [Comment!]! } type Block implements Node, Commentable, Named { name: String! expansions: [Expansion!]! id: ID! comments: [Comment!]! } # Eg shard, guild, clan type Watermark implements Node, Commentable, Named { name: String! cards: [Card!]! colors: [Color!]! id: ID! comments: [Comment!]! } type Artist implements Node, Commentable, Named { name: String! printings: [Printing!]! id: ID! comments: [Comment!]! } type Player implements Node, Commentable, Named { name: String! decks: [Deck!]! id: ID! comments: [Comment!]! } type Deck implements Node, Commentable, Named { name: String! colors: [Color!]! slots: [Slot!]! id: ID! comments: [Comment!]! } type Slot implements Node, Commentable { deck: Deck! card: Card! id: ID! comments: [Comment!]! } enum Color { WHITE BLUE BLACK RED GREEN COLORLESS } enum Rarity { COMMON UNCOMMON RARE MYTHIC_RARE TIMESHIFTED } graphql-ruby-1.11.10/cop/000077500000000000000000000000001414121453000150445ustar00rootroot00000000000000graphql-ruby-1.11.10/cop/no_focus_cop.rb000066400000000000000000000006611414121453000200500ustar00rootroot00000000000000# frozen_string_literal: true require 'rubocop' module Cop # Make sure no tests are focused, from https://github.com/rubocop-hq/rubocop/issues/3773#issuecomment-420662102 class NoFocusCop < RuboCop::Cop::Cop MSG = 'Remove `focus` from tests.' def_node_matcher :focused?, <<-MATCHER (send nil? :focus) MATCHER def on_send(node) return unless focused?(node) add_offense node end end end graphql-ruby-1.11.10/cop/none_without_block_cop.rb000066400000000000000000000016421414121453000221310ustar00rootroot00000000000000# frozen_string_literal: true require 'rubocop' module Cop # A custom Rubocop rule to catch uses of `.none?` without a block. # # @see https://github.com/rmosolgo/graphql-ruby/pull/2090 class NoneWithoutBlockCop < RuboCop::Cop::Cop MSG = <<-MD Instead of `.none?` without a block: - Use `.empty?` to check for an empty collection (faster) - Add a block to explicitly check for `false` (more clear) Run `-a` to replace this with `.empty?`. MD def on_block(node) # Since this method was called with a block, it can't be # a case of `.none?` without a block ignore_node(node.send_node) end def on_send(node) if !ignored_node?(node) && node.method_name == :none? && node.arguments.size == 0 add_offense(node) end end def autocorrect(node) lambda do |corrector| corrector.replace(node.location.selector, "empty?") end end end end graphql-ruby-1.11.10/gemfiles/000077500000000000000000000000001414121453000160565ustar00rootroot00000000000000graphql-ruby-1.11.10/gemfiles/mongoid_6.gemfile000066400000000000000000000003321414121453000212670ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "bootsnap" gem "ruby-prof", platform: :ruby gem "pry" gem "pry-stack_explorer", platform: :ruby gem "mongoid", "~> 6.4.1" gemspec path: "../" graphql-ruby-1.11.10/gemfiles/mongoid_7.gemfile000066400000000000000000000003321414121453000212700ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "bootsnap" gem "ruby-prof", platform: :ruby gem "pry" gem "pry-stack_explorer", platform: :ruby gem "mongoid", "~> 7.0.1" gemspec path: "../" graphql-ruby-1.11.10/gemfiles/rails_3.2.gemfile000066400000000000000000000006551414121453000211120ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "bootsnap" gem "ruby-prof", platform: :ruby gem "pry" gem "pry-stack_explorer", platform: :ruby gem "rails", "3.2.22.5", require: "rails/all" gem "activerecord", "~> 3.2.21" gem "actionpack", "~> 3.2.21" gem "test-unit" gem "sqlite3", "~> 1.3.6", platform: :ruby gem "activerecord-jdbcsqlite3-adapter", platform: :jruby gem "sequel" gemspec path: "../" graphql-ruby-1.11.10/gemfiles/rails_4.2.gemfile000066400000000000000000000006711414121453000211110ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "bootsnap" gem "ruby-prof", platform: :ruby gem "pry" gem "pry-stack_explorer", platform: :ruby gem "rails", "~> 4.2", require: "rails/all" gem "activerecord", "~> 4.2.4" gem "actionpack", "~> 4.2.4" gem "concurrent-ruby", "~> 1.0" gem "sqlite3", "~> 1.3.6", platform: :ruby gem "activerecord-jdbcsqlite3-adapter", platform: :jruby gem "sequel" gemspec path: "../" graphql-ruby-1.11.10/gemfiles/rails_5.2_postgresql.gemfile000066400000000000000000000004251414121453000233720ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "bootsnap" gem "ruby-prof", platform: :ruby gem "pry" gem "pry-stack_explorer", platform: :ruby gem "rails", "~> 5.2.0", require: "rails/all" gem "pg", platform: :ruby gem "sequel" gemspec path: "../" graphql-ruby-1.11.10/gemfiles/rails_6.0.gemfile000066400000000000000000000005351414121453000211100ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "bootsnap" gem "ruby-prof", platform: :ruby gem "pry" gem "pry-stack_explorer", platform: :ruby gem "rails", "~> 6.0.0", require: "rails/all" gem "sqlite3", "~> 1.4", platform: :ruby gem "activerecord-jdbcsqlite3-adapter", platform: :jruby gem "sequel" gemspec path: "../" graphql-ruby-1.11.10/gemfiles/rails_master.gemfile000066400000000000000000000005501414121453000220750ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "bootsnap" gem "ruby-prof", platform: :ruby gem "pry" gem "pry-stack_explorer", platform: :ruby gem "rails", github: "rails/rails", require: "rails/all" gem 'sqlite3', "~> 1.4", platform: :ruby gem "activerecord-jdbcsqlite3-adapter", platform: :jruby gem "sequel" gemspec path: "../" graphql-ruby-1.11.10/graphql-ruby.png000066400000000000000000000103401414121453000174040ustar00rootroot00000000000000PNG  IHDRgOۢsBIT|d pHYsaa?itEXtSoftwarewww.inkscape.org<]IDATxkTՕ֣i@^VuKv!Hdia,A2fEdYc|A,i߈(N&jpdfT59Uz=hn=.tϚszw9)f6I2"&{R6ZkL~")J=MC*O͋Y) v)VކT(>ރXPX_4\yMwD˳qYh 脯PZtW/Z{ˡrl ܑG$zi Ҿm#cCt3/؛MkRlɟP۸3RlQik<&^@juJ; p&FK@ 0㿺dyC!fP+kf|]Ʒh#J16XYzQ6Xkl!ӬO2 ŋ4F]qII[5-z(vQ!$dx+ .Tm YsiAhiR7?wZ0@$'ކI YPiv$*,)IbC_L_?brGKĊ;%g]iil~ 1 Cb. /V]]ўWH%TN$"i 2֏HfƦ`SJ { ]Pg5Я\ič7M9OEO5D|KM̋)^ RASQpIJL{9^fsw&Q.Pd(PZg>IfyxυLi>-^!'6^E@J  z|op:×W?<iٲ3R_a5l%fؿu_˞VM,bPe]ibbi.Iq׮.Пfur?._pPDeoR#(69S9\wS_ݶ#[&m_)2~]>5u?4\| ߀oQ(6p$fy0^ZCCoRNg+ǹ@/õpf_hN:=^?.iĽ Wv Hy`SngߟKvn$h?ygx;^Εz7+߽?zfR^$CR:"G@zSܦac{ꊄ <_I(T,Ćj,e*Z_pi&hi/vw۪igtF$dҠs">;&Nw겅~xXŠ jCX;/leXnLLvU  /WN^bX&,诳v"ƌy1KA_LoX^pŽPLT~n 4 ^EJuB8cZ^i:,yhGN;Mвbx}/}.GlBJ ?DڜHΰ^0EDpUE,ٽ5/z6ۉ\FUK[ԓr+.FeJ/;> zfljA X݂Ph— wh}rZj~"*w7 Hm?O/ZO5i9/ z}+{Z4:CTBXS 0*\^ԚZpM9h I`s V˜c)*R(CzGEfyF!& `Qb@a[/K.L5MIq >D^Y f2Bϴ#̤v#Vl8+0%l̄BDpu? I++W] ݅|,R1PJգ|=-ٟWg}I13! G.3btEQ'1D%j&@ki,eYhK菠O*48J ^֘EԒHm;Q~kùH}ٴai- ӺŬxUc6HobPP[[x ӣWzS찕aVDac$Dy@BUcŪQgۧ dF۟VN@X]ԦBUeL(_Cʑ@t)@&/9mj}JѵDžM5b:n'_ 4FB9pM>eMWu$>5i=8̳T C4@tý׀^+ٝ<W^Lb, 1z~Z(K 6 BeMO% ٛzVh R0jd^#6Eo:~uÜf|C61*\ ,ştEC 4D/X%(QӰ.R|$fvttHV(p[*ZY7yӌoPdjK3R)V qUB 1:߸g5wK*A%SuBuwmކ{ ${k8%ow @>1dġ79Jx!?'nቮ"FDo()l^>語hCmU'bn&493鬫z|!}ij*U\1_>[Y7yqCgOe*\WT #xHr3vnGΝdr 2i|UE\_*1e7x1.7EB/V҇JU{-`%W4^Ttyż+[ԛ+M pA5޳6Se-߭e0( *F G Ö9E͘3m %$}`%NC=w [~䨥Ce*rz/^e(H^8mG9hah-;N[1#bb,qu^_ [HRF&2xN3+|%僤®4=Wz"MTa#u}%-T߰nP|)rVq`g)A9kV`j~pĺ;>A2 zʛhG#tLȁ &^IYYlv"3ʺCǺ#|S7 :\8f!(u_sqͱFB1WĸtCOqIHs#}ǚ8@AGƯT$;3fIB:?7hᘓ=[1#8 ]mu|< 1pלX__'*IuU.TʳP@%WՁK+APnb@gbЗbú.)*ܻãW4_,ĚvutWAL\U'c E ȊЀ[cb@?9iz^;:6D>ʜq@gx@,AAGnj/LP̋z]Ҩݕ)& } !rH#Ϣ(gMd@~!v/L $}}(ʳMe໵VN %["|C 1(1(qs2~ BK& Jr K@ѿ6P0(Фc.}@"̎-9͟K59ݜ3YؒgB6pk}0eQ>F:q}+ r4IENDB`graphql-ruby-1.11.10/graphql-ruby.svg000066400000000000000000000024041414121453000174210ustar00rootroot00000000000000 graphql-ruby-1.11.10/graphql.gemspec000066400000000000000000000046171414121453000172760ustar00rootroot00000000000000# frozen_string_literal: true $LOAD_PATH.push File.expand_path("../lib", __FILE__) require "graphql/version" require "date" Gem::Specification.new do |s| s.name = "graphql" s.version = GraphQL::VERSION s.date = Date.today.to_s s.summary = "A GraphQL language and runtime for Ruby" s.description = "A plain-Ruby implementation of GraphQL." s.homepage = "https://github.com/rmosolgo/graphql-ruby" s.authors = ["Robert Mosolgo"] s.email = ["rdmosolgo@gmail.com"] s.license = "MIT" s.required_ruby_version = ">= 2.2.0" # bc `.to_sym` used on user input s.metadata = { "homepage_uri" => "https://graphql-ruby.org", "changelog_uri" => "https://github.com/rmosolgo/graphql-ruby/blob/master/CHANGELOG.md", "source_code_uri" => "https://github.com/rmosolgo/graphql-ruby", "bug_tracker_uri" => "https://github.com/rmosolgo/graphql-ruby/issues", "mailing_list_uri" => "https://tinyletter.com/graphql-ruby", } s.files = Dir["{lib}/**/*", "MIT-LICENSE", "readme.md", ".yardopts"] s.add_development_dependency "benchmark-ips" s.add_development_dependency "codeclimate-test-reporter", "~>0.4" s.add_development_dependency "concurrent-ruby", "~>1.0" s.add_development_dependency "guard", "~> 2.12" s.add_development_dependency "guard-minitest", "~> 2.4" s.add_development_dependency "guard-rake" s.add_development_dependency "guard-rubocop" s.add_development_dependency "listen", "~> 3.0.0" s.add_development_dependency "memory_profiler" # Remove this limit when minitest-reports is compatible # https://github.com/kern/minitest-reporters/pull/220 s.add_development_dependency "minitest", "~> 5.9.0" s.add_development_dependency "minitest-focus", "~> 1.1" s.add_development_dependency "minitest-reporters", "~>1.0" s.add_development_dependency "racc", "~> 1.4" s.add_development_dependency "rake", "~> 12" s.add_development_dependency "rubocop", "0.68" # for Ruby 2.2 enforcement # following are required for relay helpers s.add_development_dependency "appraisal" # required for upgrader s.add_development_dependency "parser" # website stuff s.add_development_dependency "jekyll" s.add_development_dependency "yard" s.add_development_dependency "jekyll-algolia" if RUBY_VERSION >= '2.4.0' s.add_development_dependency "jekyll-redirect-from" if RUBY_VERSION >= '2.4.0' s.add_development_dependency "m", "~> 1.5.0" end graphql-ruby-1.11.10/guides/000077500000000000000000000000001414121453000155435ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/CNAME000066400000000000000000000000211414121453000163020ustar00rootroot00000000000000graphql-ruby.org graphql-ruby-1.11.10/guides/_config.yml000066400000000000000000000007141414121453000176740ustar00rootroot00000000000000title: GraphQL Ruby baseurl: "" url: "https://graphql-ruby.org" exclude: - .gitignore keep_files: ["api-doc", ".git"] # Build settings markdown: kramdown highlighter: rouge kramdown: auto_ids: true hard_wrap: false input: GFM defaults: - scope: path: "" values: layout: "default" algolia: application_id: '8VO8708WUV' index_name: 'prod_graphql_ruby' plugins: - jekyll-algolia - jekyll-redirect-from graphql-ruby-1.11.10/guides/_layouts/000077500000000000000000000000001414121453000174025ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/_layouts/default.html000066400000000000000000000033401414121453000217140ustar00rootroot00000000000000 {% if page.section contains "GraphQL" %} {{ page.section }} - {{ page.title }} {% else %} GraphQL - {{ page.title }} {% endif %}
{{ content }}
graphql-ruby-1.11.10/guides/_layouts/doc_stub.html000066400000000000000000000002521414121453000220710ustar00rootroot00000000000000 {{ content }} graphql-ruby-1.11.10/guides/_layouts/guide.html000066400000000000000000000035501414121453000213700ustar00rootroot00000000000000--- layout: default --- {% if page.experimental %}

⚠ Experimental ⚠

This feature may get big changes in future releases. Check the changelog for update notes.

{% endif %} {% if page.pro %}

⚡️ Pro Feature ⚡️ This feature is bundled with GraphQL-Pro.

{% endif %}

{{ page.title }}

{{ content }}
graphql-ruby-1.11.10/guides/_plugins/000077500000000000000000000000001414121453000173635ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/_plugins/api_doc.rb000066400000000000000000000054331414121453000213130ustar00rootroot00000000000000# frozen_string_literal: true require_relative "../../lib/graphql/version" module GraphQLSite API_DOC_ROOT = "/api-doc/#{GraphQL::VERSION}/" module APIDoc def api_doc(input) if !input.start_with?("GraphQL") ruby_ident = "GraphQL::#{input}" else ruby_ident = input end doc_path = ruby_ident .gsub("::", "/") # namespaces .sub(/#(.+)$/, "#\\1-instance_method") # instance methods .sub(/\.(.+)$/, "#\\1-class_method") # class methods %|#{input}| end def link_to_img(img_path, img_title) full_img_path = "#{@context.registers[:site].baseurl}#{img_path}" <<-HTML #{img_title} HTML end end class APIDocRoot < Liquid::Tag def render(context) API_DOC_ROOT end end class OpenAnIssue < Liquid::Tag def initialize(tag_name, issue_info, tokens) title, body = issue_info.split(",") # remove whitespace and quotes if value is present @title = strip_arg(title) @body = strip_arg(body) end def render(context) %|open an issue| end private def strip_arg(text) text && text.strip[1..-2] end end # Build a URL relative to `site.baseurl`, # asserting that the page exists. class InternalLink < Liquid::Tag GUIDES_ROOT = "guides/" def initialize(tag_name, guide_info, tokens) text, path = guide_info.split(",") # remove whitespace and quotes if value is present @text = strip_arg(text) @path = strip_arg(path) if @path && @path.start_with?("/") @path = @path[1..-1] end if !exist?(@path) raise "Internal link failed, couldn't find file for: #{path}" end end def render(context) <<-HTML.chomp #{@text} HTML end private def strip_arg(text) text && text.strip[1..-2] end POSSIBLE_EXTENSIONS = [".html", ".md"] def exist?(path) filepath = GUIDES_ROOT + path.split("#").first filepath = filepath.sub(".html", "") POSSIBLE_EXTENSIONS.any? { |ext| File.exist?(filepath + ext) } end end end Liquid::Template.register_filter(GraphQLSite::APIDoc) Liquid::Template.register_tag("api_doc_root", GraphQLSite::APIDocRoot) Liquid::Template.register_tag("open_an_issue", GraphQLSite::OpenAnIssue) Liquid::Template.register_tag("internal_link", GraphQLSite::InternalLink) graphql-ruby-1.11.10/guides/_sass/000077500000000000000000000000001414121453000166535ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/_sass/reset.scss000066400000000000000000000021021414121453000206650ustar00rootroot00000000000000/* https://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 License: none (public domain) */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; } /* HTML5 display-role reset for older browsers */ article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } body { line-height: 1; } ol, ul { list-style: none; } blockquote, q { quotes: none; } blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } table { border-collapse: collapse; border-spacing: 0; } graphql-ruby-1.11.10/guides/_tasks/000077500000000000000000000000001414121453000170275ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/_tasks/site.rb000066400000000000000000000134031414121453000203210ustar00rootroot00000000000000# frozen_string_literal: true require "yard" namespace :apidocs do desc "Fetch a gem version from RubyGems, build the docs" task :gen_version, [:version] do |t, args| # GITHUB_REF comes from GitHub Actions version = args[:version] || ENV["GITHUB_REF"] || raise("A version is required") # GitHub Actions gives the full tag name if version.start_with?("refs/tags/") version = version[10..-1] end if version.start_with?("v") version = version[1..-1] end Dir.mktmpdir do puts "Fetching graphql-#{version}" system("gem fetch graphql --version=#{version}") system("gem unpack graphql-#{version}.gem") system("rm graphql-#{version}.gem") Dir.chdir("graphql-#{version}") do system("yardoc") # Copy it into gh-pages for publishing # and locally for previewing push_dest = File.expand_path("../gh-pages/api-doc/#{version}") local_dest = File.expand_path("../guides/_site/api-doc/#{version}") mkdir_p push_dest mkdir_p local_dest puts "Copying from #{Dir.pwd}/doc to #{push_dest}" copy_entry "doc", push_dest puts "Copying from #{Dir.pwd}/doc to #{local_dest}" copy_entry "doc", local_dest end end puts "Successfully generated docs for #{version}" end end namespace :site do desc "View the documentation site locally" task serve: [:build_doc] do require "jekyll" options = { "source" => File.expand_path("guides"), "destination" => File.expand_path("guides/_site"), "watch" => true, "serving" => true } # Generate the site in server mode. puts "Running Jekyll..." Jekyll::Commands::Build.process(options) Jekyll::Commands::Serve.process(options) end desc "Get the gh-pages branch locally, make sure it's up-to-date" task :fetch_latest do # Ensure the gh-pages dir exists so we can generate into it. puts "Checking for gh-pages dir..." unless File.exist?("./gh-pages") puts "Creating gh-pages dir..." sh "git clone git@github.com:rmosolgo/graphql-ruby gh-pages" end # Ensure latest gh-pages branch history. Dir.chdir("gh-pages") do sh "git checkout gh-pages" sh "git pull origin gh-pages" end end desc "Remove all generated HTML (making space to re-generate)" task :clean_html do # Proceed to purge all files in case we removed a file in this release. puts "Cleaning gh-pages directory..." purge_exclude = [ 'gh-pages/.', 'gh-pages/..', 'gh-pages/.git', 'gh-pages/.gitignore', 'gh-pages/api-doc', ] FileList["gh-pages/{*,.*}"].exclude(*purge_exclude).each do |path| sh "rm -rf #{path}" end end desc "Build guides/ into gh-pages/ with Jekyll" task :build_html do # Copy site to gh-pages dir. puts "Building site into gh-pages branch..." ENV['JEKYLL_ENV'] = 'production' require "jekyll" Jekyll::Commands::Build.process({ "source" => File.expand_path("guides"), "destination" => File.expand_path("gh-pages"), "sass" => { "style" => "compressed" } }) File.write('gh-pages/.nojekyll', "Prevent GitHub from running Jekyll") end desc "Commit new docs" task :commit_changes do puts "Committing and pushing to GitHub Pages..." sha = `git rev-parse HEAD`.strip Dir.chdir('gh-pages') do system "git status" system "git add ." system "git status" system "git commit --allow-empty -m 'Updating to #{sha}.'" end end desc "Push docs to gh-pages branch" task :push_commit do Dir.chdir('gh-pages') do sh "git push origin gh-pages" end end desc "Commit the local site to the gh-pages branch and publish to GitHub Pages" task publish: [:build_doc, :update_search_index, :fetch_latest, :clean_html, :build_html, :commit_changes, :push_commit] YARD::Rake::YardocTask.new(:prepare_yardoc) task build_doc: :prepare_yardoc do require_relative "../../lib/graphql/version" def to_rubydoc_url(path) "/api-doc/#{GraphQL::VERSION}/" + path .gsub("::", "/") # namespaces .sub(/#(.+)$/, "#\\1-instance_method") # instance methods .sub(/\.(.+)$/, "#\\1-class_method") # class methods end DOC_TEMPLATE = <<-PAGE --- layout: doc_stub search: true title: %{title} url: %{url} rubydoc_url: %{url} doc_stub: true --- %{documentation} PAGE puts "Preparing YARD docs @ v#{GraphQL::VERSION} for search index..." registry = YARD::Registry.load!(".yardoc") files_target = "guides/yardoc" FileUtils.rm_rf(files_target) FileUtils.mkdir_p(files_target) # Get docs for all classes and modules docs = registry.all(:class, :module) docs.each do |code_object| begin # Skip private classes and modules if code_object.visibility == :private next end rubydoc_url = to_rubydoc_url(code_object.path) page_content = DOC_TEMPLATE % { title: code_object.path, url: rubydoc_url, documentation: code_object.format.gsub(/-{2,}/, " ").gsub(/^\s+/, ""), } filename = code_object.path.gsub(/\W+/, "_") filepath = "guides/yardoc/#{filename}.md" File.write(filepath, page_content) rescue StandardError => err puts "Error on: #{code_object.path}" puts err puts err.backtrace end end puts "Wrote #{docs.size} YARD docs to #{files_target}." end desc "Update the Algolia search index used for graphql-ruby.org" task :update_search_index do if !ENV["ALGOLIA_API_KEY"] warn("Can't update search index without ALGOLIA_API_KEY; Search will be out-of-date.") else system("bundle exec jekyll algolia push --source=./guides") end end end graphql-ruby-1.11.10/guides/authorization/000077500000000000000000000000001414121453000204435ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/authorization/accessibility.md000066400000000000000000000053441414121453000236220ustar00rootroot00000000000000--- layout: guide search: true section: Authorization title: Accessibility desc: Reject queries from unauthorized users if they access certain parts of the schema. index: 2 --- __NOTE:__ This kind of authorization is deprecated and isn't supported by the {% internal_link "forthcoming GraphQL runtime", "/queries/interpreter" %} because it's too slow. With GraphQL-Ruby, you can inspect an incoming query, and return a custom error if that query accesses some unauthorized parts of the schema. This is different from {% internal_link "visibility", "/authorization/visibility" %}, where unauthorized parts of the schema are treated as non-existent. It's also different from {% internal_link "authorization", "/authorization/authorization" %}, which makes checks _while running_, instead of _before running_. ## Opt-In To use this kind of authorization, you must add a query analyzer: ```ruby class MySchema < GraphQL::Schema # Set up ahead-of-time `accessible?` authorization query_analyzer GraphQL::Authorization::Analyzer end ``` ## Preventing Access You can override some `.accessible?(context)` methods to prevent access to certain members of the schema: - Type and mutation classes have a `.accessible?(context)` class method - Arguments and fields have a `.accessible?(context)` instance method These methods are called with the query context, based on the hash you pass as `context:`. Whenever that method is implemented to return `false`, the currently-checked field will be collected as inaccessible. For example: ```ruby class BaseField < GraphQL::Schema::Field def initialize(preview:, **kwargs, &block) @preview = preview super(**kwargs, &block) end # If this field was marked as preview, hide it unless the current viewer can see previews. def accessible?(context) if @preview && !context[:viewer].can_preview? false else super end end end ``` Now, any fields created with `field(..., preview: true)` will be _visible_ to everyone, but only accessible to users where `.can_preview?` is `true`. ## Adding an Error By default, GraphQL-Ruby will return a simple error to the client if any `.accessible?` checks return false. You can customize this behavior by overriding {{ "Schema.inaccessible_fields" | api_docs }}, for example: ```ruby class MySchema < GraphQL::Schema # If you have a custom `permission_level` setting on your `GraphQL::Field` class, # you can access it here: def self.inaccessible_fields(error) required_permissions = error.fields.map(&:permission_level).uniq # Return a custom error GraphQL::AnalysisError.new("You need certain permissions: #{required_permissions.join(", ")}") end end ``` Then, your custom error will be added to the response instead of the default one. graphql-ruby-1.11.10/guides/authorization/authorization.md000066400000000000000000000056611414121453000236750ustar00rootroot00000000000000--- layout: guide search: true section: Authorization title: Authorization desc: During execution, check if the current user has permission to access retrieved objects. index: 3 --- While a query is running, you can check each object to see whether the current user is authorized to interact with that object. If the user is _not_ authorized, you can handle the case with an error. ## Adding Authorization Checks Schema members have `authorized?` methods which will be called during execution: - Type classes have `.authorized?(object, context)` class methods - Fields have `#authorized?(object, args, context)` instance methods - Arguments have `#authorized?(object, arg_value, context)` instance methods These methods are called with: - `object`: the object from your application which was returned from a field - `args`/`arg_value`: The arguments for a field, or the value of an argument - `context`: the query context, based on the hash passed as `context:` When you implement this method to return `false`, the query will be halted, for example: ```ruby class Types::Friendship < Types::BaseObject # You can only see the details on a `Friendship` # if you're one of the people involved in it. def self.authorized?(object, context) super && (object.to_friend == context[:viewer] || object.from_friend == context[:viewer]) end end ``` (Always call `super` to get the default checks, too.) Now, whenever an object of type `Friendship` is going to be returned to the client, it will first go through the `.authorized?` method. If that method returns false, the field will get `nil` instead of the original object, and you may handle that case with an error (see below). ## Handling Unauthorized Objects By default, GraphQL-Ruby silently replaces unauthorized objects with `nil`, as if they didn't exist. You can customize this behavior by implementing {{ "Schema.unauthorized_object" | api_doc }} in your schema class, for example: ```ruby class MySchema < GraphQL::Schema # Override this hook to handle cases when `authorized?` returns false for an object: def self.unauthorized_object(error) # Add a top-level error to the response instead of returning nil: raise GraphQL::ExecutionError, "An object of type #{error.type.graphql_name} was hidden due to permissions" end end ``` Now, the custom hook will be called instead of the default one. If `.unauthorized_object` returns a non-`nil` object (and doesn't `raise` an error), then that object will be used in place of the unauthorized object. A similar hook is available for unauthorized fields: ```ruby class MySchema < GraphQL::Schema # Override this hook to handle cases when `authorized?` returns false for a field: def self.unauthorized_field(error) # Add a top-level error to the response instead of returning nil: raise GraphQL::ExecutionError, "The field #{error.field.graphql_name} on an object of type #{error.type.graphql_name} was hidden due to permissions" end end ``` graphql-ruby-1.11.10/guides/authorization/can_can_integration.md000066400000000000000000000267251414121453000247660ustar00rootroot00000000000000--- layout: guide search: true section: Authorization title: CanCan Integration desc: Hook up GraphQL to CanCan abilities index: 4 pro: true --- [GraphQL::Pro](https://graphql.pro) includes an integration for powering GraphQL authorization with [CanCan](https://github.com/CanCanCommunity/cancancan). __Why bother?__ You _could_ put your authorization code in your GraphQL types themselves, but writing a separate authorization layer gives you a few advantages: - Since the authorization code isn't embedded in GraphQL, you can use the same logic in non-GraphQL (or legacy) parts of the app. - The authorization logic can be tested in isolation, so your end-to-end GraphQL tests don't have to cover as many possibilities. ## Getting Started __NOTE__: Requires the latest gems, so make sure your `Gemfile` has: ```ruby # For CanCanIntegration: gem "graphql-pro", ">=1.7.11" # For list scoping: gem "graphql", ">=1.8.7" ``` Then, `bundle install`. Whenever you run queries, include `:current_user` in the context: ```ruby context = { current_user: current_user, # ... } MySchema.execute(..., context: context) ``` And read on about the different features of the integration: - [Authorizing Objects](#authorizing-objects) - [Scoping Lists and Connections](#scopes) - [Authorizing Fields](#authorizing-fields) - [Authorizing Arguments](#authorizing-arguments) - [Authorizing Mutations](#authorizing-mutations) - [Custom Abilities Class](#custom-abilities-class) ## Authorizing Objects For each object type, you can assign a required action for Ruby objects of that type. To get started, include the `ObjectIntegration` in your base object class: ```ruby # app/graphql/types/base_object.rb class Types::BaseObject < GraphQL::Schema::Object # Add the CanCan integration: include GraphQL::Pro::CanCanIntegration::ObjectIntegration # By default, require `can :read, ...` can_can_action(:read) # Or, to require no permissions by default: # can_can_action(nil) end ``` Now, anyone fetching an object will need `can :read, ...` for that object. CanCan configurations are inherited, and can be overridden in subclasses. For example, to allow _all_ viewers to see the `Query` root type: ```ruby class Types::Query < Types::BaseObject # Allow anyone to see the query root can_can_action nil end ``` ### Bypassing CanCan `can_can_action(nil)` will override any inherited configuration and skip CanCan checks for an object, field, argument or mutation. ### Handling Unauthorized Objects When any CanCan check returns `false`, the unauthorized object is passed to {{ "Schema.unauthorized_object" | api_doc }}, as described in {% internal_link "Handling unauthorized objects", "/authorization/authorization#handling-unauthorized-objects" %}. ## Scopes #### ActiveRecord::Relation The CanCan integration adds [CanCan's `.accessible_by`](https://github.com/cancancommunity/cancancan/wiki/Fetching-Records) to GraphQL-Ruby's {% internal_link "list scoping", "/authorization/scoping" %} To scope lists of interface or union type, include the integration in your base union class and base interface module _and_ set a base `can_can_action`, if desired: ```ruby class BaseUnion < GraphQL::Schema::Union include GraphQL::Pro::CanCanIntegration::UnionIntegration # To provide a default action for scoping lists: can_can_action :read end module BaseInterface include GraphQL::Schema::Interface include GraphQL::Pro::CanCanIntegration::InterfaceIntegration # To provide a default action for scoping lists: can_can_action :read end ``` #### Array For Arrays, the CanCan integration will use `.select { ... }` to filter items using the `can_can_action` from the lists's type. #### Bypassing scopes To allow an unscoped relation to be returned from a field, disable scoping with `scope: false`, for example: ```ruby # Allow anyone to browse the job postings field :job_postings, [Types::JobPosting], null: false, scope: false ``` ## Authorizing Fields You can also require certain checks on a field-by-field basis. First, include the integration in your base field class: ```ruby # app/graphql/types/base_field.rb class Types::BaseField < GraphQL::Schema::Field # Add the CanCan integration: include GraphQL::Pro::CanCanIntegration::FieldIntegration # By default, don't require a role at field-level: can_can_action nil end ``` If you haven't already done so, you should also hook up your base field class to your base object and base interface: ```ruby # app/graphql/types/base_object.rb class Types::BaseObject < GraphQL::Schema::Object field_class Types::BaseField end # app/graphql/types/base_interface.rb module Types::BaseInterface # ... field_class Types::BaseField end # app/graphql/mutations/base_mutation.rb class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation field_class Types::BaseField end ``` Then, you can add `can_can_action:` options to your fields: ```ruby class Types::JobPosting < Types::BaseObject # Only allow `can :review_applications, JobPosting` users # to see who has applied field :applicants, [Types::User], null: true, can_can_action: :review_applicants end ``` It will require the named action (`:review_applicants`) for the object being viewed (a `JobPosting`). ### Authorizing by attribute CanCan 3.0 added attribute-level authorization ([pull request](https://github.com/CanCanCommunity/cancancan/pull/474)). You can leverage this in your field definitions with the `can_can_attribute:` configuration: ```ruby # This will call `.can?(:read, user, :email_address)` field :email_address, String, null: true, can_can_action: :read, can_can_attribute: :email_address ``` You could also provide a _default value_ for `can_can_attribute` in your base field class: ```ruby class Types::BaseField def initialize(*args, **kwargs, &block) # pass all configs to the super class: super # Then set a new `can_can_attribute` value, if applicable if can_can_attribute.nil? && can_can_action.present? # `method_sym` is the thing GraphQL-Ruby will use to resolve this field can_can_attribute(method_sym) end end end ``` (See {{ "GraphQL::Schema::Field" | api_doc }} for the different values available for defaults.) ## Authorizing Arguments Similar to field-level checks, you can require certain permissions to _use_ certain arguments. To do this, add the integration to your base argument class: ```ruby class Types::BaseArgument < GraphQL::Schema::Argument # Include the integration and default to no permissions required include GraphQL::Pro::CanCanIntegration::ArgumentIntegration can_can_action nil end ``` Then, make sure your base argument is hooked up to your base field and base input object: ```ruby class Types::BaseField < GraphQL::Schema::Field argument_class Types::BaseArgument # PS: see "Authorizing Fields" to make sure your base field is hooked up to objects, interfaces and mutations end class Types::BaseInputObject < GraphQL::Schema::InputObject argument_class Types::BaseArgument end class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation argument_class Types::BaseArgument end ``` Now, arguments accept a `can_can_action:` option, for example: ```ruby class Types::Company < Types::BaseObject field :employees, Types::Employee.connection_type, null: true do # Only admins can filter employees by email: argument :email, String, required: false, can_can_action: :admin end end ``` This will check for `can :admin, Company` (or a similar rule for the `company` being queried) for the current user. ## Authorizing Mutations There are a few ways to authorize GraphQL mutations with the CanCan integration: - Add a [mutation-level roles](#mutation-level-roles) - Run checks on [objects loaded by ID](#authorizing-loaded-objects) Also, you can configure [unauthorized object handling](#unauthorized-mutations) #### Setup Add `MutationIntegration` to your base mutation, for example: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation include GraphQL::Pro::CanCanIntegration::MutationIntegration # Also, to use argument-level authorization: argument_class Types::BaseArgument end ``` Also, you'll probably want a `BaseMutationPayload` where you can set a default role: ```ruby class Types::BaseMutationPayload < Types::BaseObject # If `BaseObject` requires some permissions, override that for mutation results. # Assume that anyone who can run a mutation can read their generated result types. can_can_action nil end ``` And hook it up to your base mutation: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation object_class Types::BaseMutationPayload field_class Types::BaseField end ``` #### Mutation-level roles Each mutation can have a class-level `can_can_action` which will be checked before loading objects or resolving, for example: ```ruby class Mutations::PromoteEmployee < Mutations::BaseMutation can_can_action :run_mutation end ``` In the example above, `can :run_mutation, Mutations::PromoteEmployee` will be checked before running the mutation. (The currently-running instance of `Mutations::PromoteEmployee` is passed to the ability checker.) #### Authorizing Loaded Objects Mutations can automatically load and authorize objects by ID using the `loads:` option. Beyond the normal [object reading permissions](#authorizing-objects), you can add an additional role for the specific mutation input using a `can_can_action:` option: ```ruby class Mutations::FireEmployee < Mutations::BaseMutation argument :employee_id, ID, required: true, loads: Types::Employee, can_can_action: :supervise, end ``` In the case above, the mutation will halt unless the `can :supervise, ...` check returns true. (The fetched instance of `Employee` is passed to the ability checker.) #### Unauthorized Mutations By default, an authorization failure in a mutation will raise a Ruby exception. You can customize this by implementing `#unauthorized_by_can_can(owner, value)` in your base mutation, for example: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation def unauthorized_by_can_can(owner, value) # No error, just return nil: nil end end ``` The method is called with: - `owner`: the `GraphQL::Schema::Argument` instance or mutation class whose role was not satisfied - `value`: the object which didn't pass for `context[:current_user]` Since it's a mutation method, you can also access `context` in that method. Whatever that method returns will be treated as an early return value for the mutation, so for example, you could return {% internal_link "errors as data", "/mutations/mutation_errors" %}: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation field :errors, [String], null: true def unauthorized_by_can_can(owner, value) # Return errors as data: { errors: ["Missing required permission: #{owner.can_can_action}, can't access #{value.inspect}"] } end end ``` ## Custom Abilities Class By default, the integration will look for a top-level `::Ability` class. If you're using a different class, provide an instance ahead-of-time as `context[:can_can_ability]` For example, you could _always_ add one in your schema's `#execute` method: ```ruby class MySchema < GraphQL::Schema # Override `execute` to provide a custom Abilities instance for the CanCan integration def self.execute(*args, context: {}, **kwargs) # Assign `context[:can_can_ability]` to an instance of our custom class context[:can_can_ability] = MyAuthorization::CustomAbilitiesClass.new(context[:current_user]) super end end ``` graphql-ruby-1.11.10/guides/authorization/overview.md000066400000000000000000000141311414121453000226330ustar00rootroot00000000000000--- layout: guide search: true section: Authorization title: Overview desc: Overview of GraphQL authorization in general and an intro to the built-in framework. index: 0 --- Here's a conceptual approach to GraphQL authorization, followed by an introduction to the built-in authorization framework. Each part of the framework is described in detail in its own guide. ## Authorization: GraphQL vs REST In a REST API, the common authorization pattern is fairly simple. Before performing the requested action, the server asserts that the current client has the required permissions for that action. For example: ```ruby class PostsController < ApiController def create # First, check the client's permission level: if current_user.can?(:create_posts) # If the user is permitted, then perform the action: post = Post.create(params) render json: post else # Otherwise, return an error: render nothing: true, status: 403 end end end ``` However, this request-by-request mindset doesn't map well to GraphQL because there's only one controller and the requests that come to it may be _very_ different. To illustrate the problem: ```ruby class GraphqlController < ApplicationController def execute # What permission is required for `query_str`? # It depends on the string! So, you can't generalize at this level. if current_user.can?(:"???") MySchema.execute(query_str, context: ctx, variables: variables) end end end ``` So, what new mindset will work with a GraphQL API? For __mutations__, remember that each mutation is like an API request in itself. For example, `Posts#create` above would map to the `createPost(...)` mutation in GraphQL. So, each mutation should be authorized in its own right. For __queries__, you can think of each individual _object_ like a `GET` request to a REST API. So, each object should be authorized for reading in its own right. By applying this mindset, each part of the GraphQL query will be properly authorized before it is executed. Also, since the different units of code are each authorized on their own, you can be sure that each incoming query will be properly authorized, even if it's a brand new query that the server has never seen before. ## What About Authentication? As a reminder: - _Authentication_ is the process of determining what user is making the current request, for example, accepting a username and password, or finding a `User` in the database from `session[:current_user_id]`. - _Authorization_ is the process of verifying that the current user has permission to do something (or see something), for example, checking `admin?` status or looking up permission groups from the database. In general, authentication is _not_ addressed in GraphQL at all. Instead, your controller should get the current user based on the HTTP request (eg, an HTTP header or a cookie) and provide that information to the GraphQL query. For example: ```ruby class GraphqlController < ApplicationController def execute # Somehow get the current `User` from this HTTP request. current_user = get_logged_in_user(request) # Provide the current user in `context` for use during the query context = { current_user: current_user } MySchema.execute(query_str, context: context, ...) end end ``` After your HTTP handler has loaded the current user, you can access it via `context[:current_user]` in your GraphQL code. ## Authorization in Your Business Logic Before introducing GraphQL-specific authorization, consider the advantages of application-level authorization. (See the [GraphQL.org post](https://graphql.org/learn/authorization/) on the same topic.) For example, here's authorization mixed into the GraphQL API layer: ```ruby field :posts, [Types::Post], null: false def posts # Perform an auth check in the GraphQL field code: if context[:current_user].admin? Post.all else Post.published end end ``` The downside of this is that, when `Types::Post` is queried in other contexts, the same authorization check may not be applied. Additionally, since the authorization code is coupled with the GraphQL API, the only way to test it is via GraphQL queries, which adds some complexity to tests. Alternatively, you could move the authorization to your business logic, the `Post` class: ```ruby class Post < ActiveRecord::Base # Return the list of posts which `user` may see def self.posts_for(user) if user.admin? self.all else self.published end end end ``` Then, use this application method in your GraphQL code: ```ruby field :posts, [Types::Post], null: false def posts # Fetch the posts this user can see: Post.posts_for(context[:current_user]) end ``` In this case, `Post.posts_for(user)` could be tested _independently_ from GraphQL. Then, you have less to worry about in your GraphQL tests. As a bonus, you can use `Post.posts_for(user)` in _other_ parts of the app, too, such as the web UI or REST API. ## GraphQL-Ruby's Authorization Framework Despite the advantages of authorization at the application layer, as described above, there might be some reasons to authorize in the API layer: - Have an extra assurance that your API layer is secure - Authorize the API request _before_ running it (see "visibility" below) - Integrate with code that doesn't have authorization built-in To accomplish these, you can use GraphQL-Ruby's authorization framework. The framework has three levels, each of which is described in its own guide: - {% internal_link "Visibility", "/authorization/visibility" %} hides parts of the GraphQL schema from users who don't have full permission. - {% internal_link "Accessibility", "/authorization/accessibility" %} prevents running queries which access parts of the GraphQL schema, unless users have the required permission. - {% internal_link "Authorization", "/authorization/authorization" %} checks application objects during execution to be sure the user has permission to access them. Also, [GraphQL::Pro](https://graphql.pro) has integrations for {% internal_link "CanCan", "/authorization/can_can_integration" %} and {% internal_link "Pundit", "/authorization/pundit_integration" %}. graphql-ruby-1.11.10/guides/authorization/pundit_integration.md000066400000000000000000000375251414121453000247070ustar00rootroot00000000000000--- layout: guide search: true section: Authorization title: Pundit Integration desc: Hook up GraphQL to Pundit policies index: 4 pro: true --- [GraphQL::Pro](https://graphql.pro) includes an integration for powering GraphQL authorization with [Pundit](https://github.com/varvet/pundit) policies. __Why bother?__ You _could_ put your authorization code in your GraphQL types themselves, but writing a separate authorization layer gives you a few advantages: - Since the authorization code isn't embedded in GraphQL, you can use the same logic in non-GraphQL (or legacy) parts of the app. - The authorization logic can be tested in isolation, so your end-to-end GraphQL tests don't have to cover as many possibilities. ## Getting Started __NOTE__: Requires the latest gems, so make sure your `Gemfile` has: ```ruby # For PunditIntegration: gem "graphql-pro", ">=1.7.9" # For list scoping: gem "graphql", ">=1.8.7" ``` Then, `bundle install`. Whenever you run queries, include `:current_user` in the context: ```ruby context = { current_user: current_user, # ... } MySchema.execute(..., context: context) ``` And read on about the different features of the integration: - [Authorizing Objects](#authorizing-objects) - [Scoping Lists and Connections](#scopes) - [Authorizing Fields](#authorizing-fields) - [Authorizing Arguments](#authorizing-arguments) - [Authorizing Mutations](#authorizing-mutations) - [Custom Policy Lookup](#custom-policy-lookup) - [Custom User Lookup](#custom-user-lookup) ## Authorizing Objects You can specify Pundit roles that must be satisfied in order for viewers to see objects of a certain type. To get started, include the `ObjectIntegration` in your base object class: ```ruby # app/graphql/types/base_object.rb class Types::BaseObject < GraphQL::Schema::Object # Add the Pundit integration: include GraphQL::Pro::PunditIntegration::ObjectIntegration # By default, require staff: pundit_role :staff # Or, to require no permissions by default: # pundit_role nil end ``` Now, anyone trying to read a GraphQL object will have to pass the `#staff?` check on that object's policy. Then, each child class can override that parent configuration. For example, allow _all_ viewers to read the `Query` root: ```ruby class Types::Query < Types::BaseObject # Allow anyone to see the query root pundit_role nil end ``` #### Policies and Methods For each object returned by GraphQL, the integration matches it to a policy and method. The policy is found using [`Pundit.policy!`](https://www.rubydoc.info/gems/pundit/Pundit%2Epolicy!), which looks up a policy using the object's class name. (This can be customized, see below.) Then, GraphQL will call a method on the policy to see whether the object is permitted or not. This method is assigned in the object class, for example: ```ruby class Types::Employee < Types::BaseObject # Only show employee objects to their bosses, # or when that employee is the current viewer pundit_role :employer_or_self # ... end ``` That configuration will call `#employer_or_self?` on the corresponding Pundit policy. #### Custom Policy Class By default, the integration uses `Pundit.policy!(current_user, object)` to find a policy. You can specify a policy class using `pundit_policy_class(...)`: ```ruby class Types::Employee < Types::BaseObject pundit_policy_class(Policies::CustomEmployeePolicy) # Or, you could use a string: # pundit_policy_class("Policies::CustomEmployeePolicy") end ``` For really custom policy lookup, see [Custom Policy Lookup](#custom-policy-lookup) below. #### Bypassing Policies The integration requires that every object with a `pundit_role` has a corresponding policy class. To allow objects to _skip_ authorization, you can pass `nil` as the role: ```ruby class Types::PublicProfile < Types::BaseObject # Anyone can see this pundit_role nil end ``` #### Handling Unauthorized Objects When any Policy method returns `false`, the unauthorized object is passed to {{ "Schema.unauthorized_object" | api_doc }}, as described in {% internal_link "Handling unauthorized objects", "/authorization/authorization#handling-unauthorized-objects" %}. ## Scopes The Pundit integration adds [Pundit scopes](https://github.com/varvet/pundit#scopes) to GraphQL-Ruby's {% internal_link "list scoping", "/authorization/scoping" %} feature. Any list or connection will be scoped. If a scope is missing, the query will crash rather than risk leaking unfiltered data. To scope lists of interface or union type, include the integration in your base union class and base interface module: ```ruby class BaseUnion < GraphQL::Schema::Union include GraphQL::Pro::PunditIntegration::UnionIntegration end module BaseInterface include GraphQL::Schema::Interface include GraphQL::Pro::PunditIntegration::InterfaceIntegration end ``` Pundit scopes [don't play well](https://github.com/rmosolgo/graphql-ruby/issues/2008) with `Array`s, so the integration _skips_ scopes on Arrays. You can also opt out on a field-by-field basis as described below. You can also customize how the scopes are looked up and applied, see below. #### Bypassing scopes To allow an unscoped relation to be returned from a field, disable scoping with `scope: false`, for example: ```ruby # Allow anyone to browse the job postings field :job_postings, [Types::JobPosting], null: false, scope: false ``` ## Authorizing Fields You can also require certain checks on a field-by-field basis. First, include the integration in your base field class: ```ruby # app/graphql/types/base_field.rb class Types::BaseField < GraphQL::Schema::Field # Add the Pundit integration: include GraphQL::Pro::PunditIntegration::FieldIntegration # By default, don't require a role at field-level: pundit_role nil end ``` If you haven't already done so, you should also hook up your base field class to your base object and base interface: ```ruby # app/graphql/types/base_object.rb class Types::BaseObject < GraphQL::Schema::Object field_class Types::BaseField end # app/graphql/types/base_interface.rb module Types::BaseInterface # ... field_class Types::BaseField end # app/graphql/mutations/base_mutation.rb class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation field_class Types::BaseField end ``` Then, you can add `pundit_role:` options to your fields: ```ruby class Types::JobPosting < Types::BaseObject # Allow signed-in users to browse listings pundit_role :signed_in # But, only allow `JobPostingPolicy#staff?` users to see # who has applied field :applicants, [Types::User], null: true, pundit_role: :staff end ``` It will call the named role (eg, `#staff?`) on the parent object's policy (eg `JobPostingPolicy`). #### Custom Policy Class You can override the policy class for a field using `pundit_policy_class:`, for example: ```ruby class Types::JobPosting < Types::BaseObject # Only allow `ApplicantsPolicy#staff?` users to see # who has applied field :applicants, [Types::User], null: true, pundit_role: :staff, pundit_policy_class: ApplicantsPolicy # Or with a string: # pundit_policy_class: "ApplicantsPolicy" end ``` This will initialize an `ApplicantsPolicy` with the parent object (a `Job`) and call `#staff?` on it. For really custom policy lookup, see [Custom Policy Lookup](#custom-policy-lookup) below. ## Authorizing Arguments Similar to field-level checks, you can require certain permissions to _use_ certain arguments. To do this, add the integration to your base argument class: ```ruby class Types::BaseArgument < GraphQL::Schema::Argument # Include the integration and default to no permissions required include GraphQL::Pro::PunditIntegration::ArgumentIntegration pundit_role nil end ``` Then, make sure your base argument is hooked up to your base field and base input object: ```ruby class Types::BaseField < GraphQL::Schema::Field argument_class Types::BaseArgument # PS: see "Authorizing Fields" to make sure your base field is hooked up to objects, interfaces and mutations end class Types::BaseInputObject < GraphQL::Schema::InputObject argument_class Types::BaseArgument end class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation argument_class Types::BaseArgument end ``` Now, arguments accept a `pundit_role:` option, for example: ```ruby class Types::Company < Types::BaseObject field :employees, Types::Employee.connection_type, null: true do # Only admins can filter employees by email: argument :email, String, required: false, pundit_role: :admin end end ``` The role will be called on the parent object's policy, for example `CompanyPolicy#admin?` in the case above. ## Authorizing Mutations There are a few ways to authorize GraphQL mutations with the Pundit integration: - Add a [mutation-level roles](#mutation-level-roles) - Run checks on [objects loaded by ID](#authorizing-loaded-objects) Also, you can configure [unauthorized object handling](#unauthorized-mutations) #### Setup Add `MutationIntegration` to your base mutation, for example: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation include GraphQL::Pro::PunditIntegration::MutationIntegration # Also, to use argument-level authorization: argument_class Types::BaseArgument end ``` Also, you'll probably want a `BaseMutationPayload` where you can set a default role: ```ruby class Types::BaseMutationPayload < Types::BaseObject # If `BaseObject` requires some permissions, override that for mutation results. # Assume that anyone who can run a mutation can read their generated result types. pundit_role nil end ``` And hook it up to your base mutation: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation object_class Types::BaseMutationPayload field_class Types::BaseField end ``` #### Mutation-level roles Each mutation can have a class-level `pundit_role` which will be checked before loading objects or resolving, for example: ```ruby class Mutations::PromoteEmployee < Mutations::BaseMutation pundit_role :admin end ``` In the example above, `PromoteEmployeePolicy#admin?` will be checked before running the mutation. #### Custom Policy Class By default, Pundit uses the mutation's class name to look up a policy. You can override this by defining `pundit_policy_class` on your mutation: ```ruby class Mutations::PromoteEmployee < Mutations::BaseMutation pundit_policy_class ::UserPolicy pundit_role :admin end ``` Now, the mutation will check `UserPolicy#admin?` before running. For really custom policy lookup, see [Custom Policy Lookup](#custom-policy-lookup) below. #### Authorizing Loaded Objects Mutations can automatically load and authorize objects by ID using the `loads:` option. Beyond the normal [object reading permissions](#authorizing-objects), you can add an additional role for the specific mutation input using a `pundit_role:` option: ```ruby class Mutations::FireEmployee < Mutations::BaseMutation argument :employee_id, ID, required: true, loads: Types::Employee, pundit_role: :supervisor, end ``` In the case above, the mutation will halt unless the `EmployeePolicy#supervisor?` method returns true. #### Unauthorized Mutations By default, an authorization failure in a mutation will raise a Ruby exception. You can customize this by implementing `#unauthorized_by_pundit(owner, value)` in your base mutation, for example: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation def unauthorized_by_pundit(owner, value) # No error, just return nil: nil end end ``` The method is called with: - `owner`: the `GraphQL::Schema::Argument` or mutation class whose role was not satisfied - `value`: the object which didn't pass for `context[:current_user]` Since it's a mutation method, you can also access `context` in that method. Whatever that method returns will be treated as an early return value for the mutation, so for example, you could return {% internal_link "errors as data", "/mutations/mutation_errors" %}: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation field :errors, [String], null: true def unauthorized_by_pundit(owner, value) # Return errors as data: { errors: ["Missing required permission: #{owner.pundit_role}, can't access #{value.inspect}"] } end end ``` ## Custom Policy Lookup By default, the integration uses `Pundit`'s top-level methods to interact with policies: - `Pundit.policy!(context[:current_user], object)` is called to find a policy instance - `Pundit.policy_scope!(context[:current_user], items)` is called to filter `items` ### Custom Policy Methods You can implement a custom lookup by defining the following methods in your schema: - `pundit_policy_class_for(object, context)` to return a policy class (or raise an error if one isn't found) - `pundit_role_for(object, context)` to return a role method (Symbol), or `nil` to bypass authorization - `scope_by_pundit_policy(context, items)` to apply a scope to `items` (or raise an error if one isn't found) Since different objects have different lifecycles, the hooks are installed slightly different ways: - Your base argument, field, and mutation classes should have _instance methods_ with those names - Your base type classes should have _class methods_ with that name Here's an example of how the custom hooks can be installed: ```ruby module CustomPolicyLookup # Lookup policies in the `SystemAdmin::` namespace for system_admin users def pundit_policy_class_for(object, context) current_user = context[:current_user] if current_user.system_admin? policy_class = SystemAdmin.const_get("#{object.class.name}Policy") policy_class.new(current_user, object) else super end end # Require admin permissions if the object is pending_approval def pundit_role_for(object, context) if object.pending_approval? :admin else super # fall back to the normally-configured role end end end # Add policy hooks as class methods class Types::BaseObject < GraphQL::Schema::Object extend CustomPolicyLookup end class Types::BaseUnion < GraphQL::Schema::Union extend CustomPolicyLookup end module Types::BaseInterface include GraphQL::Schema::Interface # Add this as a class method that will be "inherited" by other interfaces: definition_methods do include CustomPolicyLookup end end # Add policy hooks as instance methods class Types::BaseField < GraphQL::Schema::Field include CustomPolicyLookup end class Types::BaseArgument < GraphQL::Schema::Argument include CustomPolicyLookup end class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation include CustomPolicyLookup end ``` ### One Policy Per Class Another good approach is to have one policy per class. You can implement `policy_class_for(object, context)` to look up a policy _within_ the class, for example: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation def policy_class_for(_object, _context) # Look up a nested `Policy` constant: self.class.const_get(:Policy) end end ``` Then, each mutation can define its policy inline, for example: ```ruby class Mutations::PromoteEmployee < Mutations::BaseMutation # This will be found by `BaseMutation.policy_class`, defined above: class Policy # ... end pundit_role :admin end ``` Now, `Mutations::PromoteEmployee::Policy#admin?` will be checked before running the mutation. ## Custom User Lookup By default, the Pundit integration looks for the current user in `context[:current_user]`. You can override this by implementing `#pundit_user` on your custom query context class. For example: ```ruby # app/graphql/query_context.rb class QueryContext < GraphQL::Query::Context def pundit_user # Lookup `context[:viewer]` instead: self[:viewer] end end ``` Then be sure to hook up your custom class in the schema: ```ruby class MySchema < GraphQL::Schema context_class(QueryContext) end ``` Then, the Pundit integration will use your `def pundit_user` to get the current user at runtime. graphql-ruby-1.11.10/guides/authorization/scoping.md000066400000000000000000000030211414121453000224230ustar00rootroot00000000000000--- layout: guide search: true section: Authorization title: Scoping desc: Filter lists to match the current viewer and context index: 4 --- _Scoping_ is a complementary consideration to authorization. Rather than checking "can this user see this thing?", scoping takes a list of items filters it to the subset which is appropriate for the current viewer and context. The resulting subset is authorized as normal, and, assuming that it was properly scoped, each item should pass authorization checks. For similar features, see [Pundit scopes](https://github.com/varvet/pundit#scopes) and [Cancan's `.accessible_by`](https://github.com/cancancommunity/cancancan/wiki/Fetching-Records). ## `scope:` option Fields accept a `scope:` option to enable (or disable) scoping, for example: ```ruby field :products, [Types::Product], scope: true # Or field :all_products, [Types::Product], scope: false ``` For __list__ and __connection__ fields, `scope: true` is the default. For all other fields, `scope: false` is the default. You can override this by using the `scope:` option. ## `.scope_items(items, ctx)` method Type classes may implement `.scope_items(items, ctx)`. This method is called when a field has `scope: true`. For example, ```ruby field :products, [Types::Product] # has `scope: true` by default ``` Will call: ```ruby class Types::Product < Types::BaseObject def self.scope_items(items, context) # filter items here end end ``` The method should return a new list with only the appropriate items for the current `context`. graphql-ruby-1.11.10/guides/authorization/visibility.md000066400000000000000000000044231414121453000231570ustar00rootroot00000000000000--- layout: guide search: true section: Authorization title: Visibility desc: Programatically hide parts of the GraphQL schema from some users. index: 1 --- With GraphQL-Ruby, it's possible to _hide_ parts of your schema from some users. This isn't exactly part of the GraphQL spec, but it's roughly within the bounds of the spec. Here are some reasons you might want to hide parts of your schema: - You don't want non-admin users to know about administration functions of the schema. - You're developing a new feature and want to make a gradual release to only a few users first. ## Hiding Parts of the Schema You can customize the visibility of parts of your schema by reimplementing various `visible?` methods: - Type classes have a `.visible?(context)` class method - Fields and arguments have a `#visible?(context)` instance method - Enum values have `#visible?(context)` instance method - Mutation classes have a `.visible?(context)` class method These methods are called with the query context, based on the hash you pass as `context:`. If the method returns false, then that member of the schema will be treated as though it doesn't exist for the entirety of the query. That is: - In introspection, the member will _not_ be included in the result - In normal queries, if a query references that member, it will return a validation error, since that member doesn't exist ## For Example Let's say you're working on a new feature which should remain secret for a while. You can implement `.visible?` in a type: ```ruby class Types::SecretFeature < Types::BaseObject def self.visible?(context) # only show it to users with the secret_feature enabled super && context[:viewer].feature_enabled?(:secret_feature) end end ``` (Always call `super` to inherit the default behavior.) Now, the following bits of GraphQL will return validation errors: - Fields that return `SecretFeature`, eg `query { findSecretFeature { ... } }` - Fragments on `SecretFeature`, eg `Fragment SF on SecretFeature` And in introspection: - `__schema { types { ... } }` will not include `SecretFeature` - `__type(name: "SecretFeature")` will return `nil` - Any interfaces or unions which normally include `SecretFeature` will _not_ include it - Any fields that return `SecretFeature` will be excluded from introspection graphql-ruby-1.11.10/guides/css/000077500000000000000000000000001414121453000163335ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/css/main.scss000066400000000000000000000251021414121453000201540ustar00rootroot00000000000000--- --- @import "reset"; $brand-color: #a5152a; $brand-color-light: #ed8090; $brand-color-extralight: #f9e8ee; $experimental-background: #fff7cf; $experimental-color: #655400; $class-based-background: #c1f6ff; $class-based-color: #003a58; $pro-color: #406db5; $pro-background: #f1f4f9; $faint-color: #f0f0f0; $code-border: #d6d6d6; $code-background: #fafafa; $code-color: #777777; $code-border-radius: 2px; $muted-color: #777777; $subtle-color: #aaaaaa; $font: 'Rubik', sans-serif; $font-color: black; $code-font: 'Monaco', monospace; body { font-family: $font; background: #fafafa; } strong, b { font-weight: bold; } // Algolia highlights: .ais-Highlight { font-style: normal; font-weight: bold; } .header { box-shadow: 0px 0px 10px 0px #d6d6d6; z-index: 1; position: relative; background: white; .nav { $height: 30px; $margin: 10px; display: flex; flex-wrap: wrap; align-items: center; $fade-time: 0.2s; .img-link { transition: background $fade-time; img { transition: filter $fade-time; transition: -webkit-filter $fade-time; filter: brightness(1) invert(0); -webkit-filter: brightness(1) invert(0); } &:hover { img { -webkit-filter: brightness(0) invert(1); filter: brightness(0) invert(1); } } } img { height: $height; width: auto; margin: $margin; } a, span { transition: background-color $fade-time; padding: $margin; height: $height; display: flex; align-items: center; text-decoration: none; } } } .header-container { max-width: 1040px; margin: 0px auto; } .container { max-width: 1000px; margin: 0px auto; padding: 10px 20px; background: white; } pre { font-family: $code-font; padding: 0.5rem; border: 1px solid $code-border; border-radius: $code-border-radius; background-color: $code-background; margin: 10px 0px; overflow-x: auto; line-height: 1.4rem; } p, li { line-height: 1.3rem; } li { margin-left: 15px; margin-top: 5px } p, ul { margin-bottom: 20px; } ul { list-style-type: disc; list-style-position: outside; } code { font-family: $code-font; color: $code-color; font-weight: 400; } .code .line-numbers { display: none; } a { color: $brand-color; border-color: $brand-color; } a:hover, a:hover code { color: white; background-color: $brand-color; } #readme img { display: none; } .guide-container { a.img-link { background: none; &:hover { background: none; } img { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); transition: all 0.3s cubic-bezier(.25,.8,.25,1); max-height: 300px; max-width: 100%; &:hover { box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22); } } } } .cell { overflow-x: scroll; } .monitoring-img-group { display: flex; flex-direction: row; margin-bottom: 20px; flex-wrap: wrap; justify-content: space-around; align-items: center; } .guides-toc { ul { list-style: none; display: flex; flex-wrap: wrap; } li { padding-bottom: 10px; padding-right: 10px; } } .breadcrumb { color: $muted-color; } .guides { .guide-desc { color: $muted-color; margin-left: 5px; } ul { margin-left: 10px; list-style: none; } } @mixin doc-header($color, $background-color) { background-color: $background-color; color: $color; border-radius: $code-border-radius; padding: 10px 10px 10px 10px; margin-bottom: 10px; border: 1px solid $color; p { margin: 0px; padding: 0px; } a { color: $color; &:hover { background-color: $color; color: $background-color; } } } .experimental-header { @include doc-header($experimental-color, $experimental-background); } .class-based-header { @include doc-header($class-based-color, $class-based-background); } .pro-header { @include doc-header($pro-color, $pro-background); } .guide-footer { background: $brand-color-extralight; margin: 25px 0px 0px 0px; padding: 10px; border-radius: $code-border-radius; } .hero { display: flex; flex-direction: column; justify-content: space-around; .hero-title { display: flex; justify-content: center; align-items: center; img, h1 { margin: 20px 10px 30px 10px; } } .hero-part { padding: 10px; &.shaded { background: $faint-color; } h2 { color: $brand-color; text-shadow: #cccccc 1px 1px 1px; font-size: 1.4em; } } .hero-feature { display: flex; flex-basis: 50%; flex-grow: 0; flex-shrink: 0; justify-content: space-between; .teaser p { font-size: 1.2em; line-height: 2em; } .teaser:first-child { margin-right: 10px; } .teaser:last-child { margin-left: 10px; } } } h1, h2, h3, h4, h5 { margin: 25px 0px 15px 0px; a { text-decoration: none; } } .guide-header { margin-bottom: 15px; } h1 { font-size: 1.5rem; } h2 { font-size: 1.3rem; } h3 { font-size: 1.2rem; } h4 { font-size: 1.1rem; } h5 { font-size: 1.05rem; } em { font-style: italic; } table { width: 100%; margin: 0px 0px 15px 0px; thead { text-align: left; border-bottom: 1px solid $subtle-color; } td, th { padding: 5px 10px 5px 0px; } } li p, li div { display: inline-block; } .search-input { font-size: 1em; padding: 5px; margin: 10px; border: 1px solid $subtle-color; border-radius: 3px; // make it float right in a flex container margin-left: auto; } .search-results-container { $bg: #eaeaea; $bg-highlight: #f9f9f9; background-color: $bg; #search-results { $fade-time: 0.1s; display: flex; flex-direction: column; max-width: 1040px; margin: 0 auto; .search-result { text-decoration: none; color: $font-color; padding: 6px 10px 0px 6px; line-height: 18px; border-bottom: 2px solid transparent; transition: border-bottom-color $fade-time; .search-title { font-weight: bold; margin-right: 8px; transition: color $fade-time; } .search-preview { color: $subtle-color; } .search-category { border: 1px solid $brand-color; border-radius: 3px; margin: 0 8px 0 0; padding: 3px; font-size: 0.7em; color: $brand-color; position: relative; top: -3px; } &:focus, &:hover { outline: none; .search-title { color: $brand-color; } background-color: $bg-highlight; } &:hover { border-bottom-color: $brand-color; } } } } /* pygments CSS, github theme */ .highlight .hll { background-color: #ffffcc } .highlight .c { color: #999988; font-style: italic } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { color: #000000; font-weight: bold } /* Keyword */ .highlight .o { color: #000000; font-weight: bold } /* Operator */ .highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ .highlight .cp { color: #999999; font-weight: bold; font-style: italic } /* Comment.Preproc */ .highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ .highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #999999 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #aaaaaa } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #000000; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #000000; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #000000; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */ .highlight .kr { color: #000000; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #009999 } /* Literal.Number */ .highlight .s { color: #d01040 } /* Literal.String */ .highlight .na { color: #008080 } /* Name.Attribute */ .highlight .nb { color: #0086B3 } /* Name.Builtin */ .highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ .highlight .no { color: #008080 } /* Name.Constant */ .highlight .nd { color: #3c5d5d; font-weight: bold } /* Name.Decorator */ .highlight .ni { color: #800080 } /* Name.Entity */ .highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ .highlight .nl { color: #990000; font-weight: bold } /* Name.Label */ .highlight .nn { color: #555555 } /* Name.Namespace */ .highlight .nt { color: #000080 } /* Name.Tag */ .highlight .nv { color: #008080 } /* Name.Variable */ .highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mf { color: #009999 } /* Literal.Number.Float */ .highlight .mh { color: #009999 } /* Literal.Number.Hex */ .highlight .mi { color: #009999 } /* Literal.Number.Integer */ .highlight .mo { color: #009999 } /* Literal.Number.Oct */ .highlight .sb { color: #d01040 } /* Literal.String.Backtick */ .highlight .sc { color: #d01040 } /* Literal.String.Char */ .highlight .sd { color: #d01040 } /* Literal.String.Doc */ .highlight .s2 { color: #d01040 } /* Literal.String.Double */ .highlight .se { color: #d01040 } /* Literal.String.Escape */ .highlight .sh { color: #d01040 } /* Literal.String.Heredoc */ .highlight .si { color: #d01040 } /* Literal.String.Interpol */ .highlight .sx { color: #d01040 } /* Literal.String.Other */ .highlight .sr { color: #009926 } /* Literal.String.Regex */ .highlight .s1 { color: #d01040 } /* Literal.String.Single */ .highlight .ss { color: #990073 } /* Literal.String.Symbol */ .highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ .highlight .vc { color: #008080 } /* Name.Variable.Class */ .highlight .vg { color: #008080 } /* Name.Variable.Global */ .highlight .vi { color: #008080 } /* Name.Variable.Instance */ .highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ graphql-ruby-1.11.10/guides/defer/000077500000000000000000000000001414121453000166305ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/defer/overview.md000066400000000000000000000044131414121453000210220ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - Defer title: Overview desc: What is @defer, and why use it? index: 0 pro: true --- `@defer` is a {% internal_link "directive", "/type_definitions/directives" %} for streaming GraphQL responses from the server to the client. By streaming the response, the server can send the most critical (or most available) data _first_, following up with secondary data shortly afterward. `@defer` was first described by [Lee Byron at React Europe 2015](https://youtu.be/ViXL0YQnioU?t=768) and got experimental support in [Apollo in 2018](https://blog.apollographql.com/introducing-defer-in-apollo-server-f6797c4e9d6e). `@defer` requires the new {% internal_link "interpreter runtime", "/queries/interpreter" %} which ships with GraphQL-Ruby 1.9+. ## Example GraphQL queries can be large and complex, requiring lots of computation or dependencies on slow external services. In this example, the local server maintains an index of items ("decks"), but the item data ("cards") is actually hosted on a remote server. So, GraphQL queries must make remote calls in order to serve that data. Without `@defer`, the whole query is blocked until the last field is done resolving: {{ "https://user-images.githubusercontent.com/2231765/53442028-4a122b00-39d6-11e9-8e33-b91791bf3b98.gif" | link_to_img:"Rails without defer" }} But, we can add `@defer` to slow fields: ```diff deck { slots { quantity - card + card @defer { name price } } } ``` Then, the response will stream to the client bit by bit, so the page can load progressively: {{ "https://user-images.githubusercontent.com/2231765/53442027-4a122b00-39d6-11e9-8d7b-feb7a4f7962a.gif" | link_to_img:"Rails with defer" }} This way, clients get a snappy feel from the app even while data is still loading. View the full demo at https://github.com/rmosolgo/graphql_defer_example. ## Considerations - `@defer` adds some overhead to the response, so only apply it judiciously. - `@defer` is single-threaded. `@defer`ed fields are still evaluated in sequence, but in a chunk-by-chunk way. ## Next Steps {% internal_link "Set up your server", "/defer/setup" %} to support `@defer` or read about {% internal_link "client usage", "/defer/usage" %} of it. graphql-ruby-1.11.10/guides/defer/setup.md000066400000000000000000000100321414121453000203060ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - Defer title: Server Setup desc: Configuring the schema and server to use @defer index: 1 pro: true --- Before using `@defer` in queries, you have to: - Update `graphql` and `graphql-pro` gems - Add `@defer` to your GraphQL schema - Update your HTTP handlers (eg, Rails controllers) to send streaming responses You can also see a [full Rails & Apollo-Client demo](https://github.com/rmosolgo/graphql_defer_example). ## Updating the gems `GraphQL::Pro::Defer` is included in `graphql-pro 1.10+`, and it requires the new {% internal_link "Interpreter runtime", "/queries/interpreter" %} in `graphql 1.9+`, so update your gemfile: ```ruby # 1.9+ for Interpreter gem "graphql", "~>1.9.0" # 1.10+ for `@defer` gem "graphql-pro", "~>1.10.0" ``` And then install them: ``` $ bundle update graphql graphql-pro ``` ## Adding `@defer` to your schema Then, add `GraphQL::Pro::Defer` to your schema as a plugin: ```ruby class MySchema < GraphQL::Schema # The new interpreter runtime (1.9+) is required: use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST # Then add the directive: use GraphQL::Pro::Defer end ``` This will: - Attach a {% internal_link "custom directive", "/type_definitions/directives" %} called `@defer` - Add instrumentation to queries to track deferred work and execute it later ## Sending streaming responses Many web frameworks have support for streaming responses, for example: - Rails has [ActionController::Live](https://api.rubyonrails.org/classes/ActionController/Live.html) - Sinatra has [Sinatra::Streaming](http://sinatrarb.com/contrib/streaming.html) - Hanami::Controller can [stream responses](https://github.com/hanami/controller#streamed-responses) See below for how to integrate GraphQL's deferred patches with a streaming response API. To investigate support with a web framework, please {% open_an_issue "Server support for @defer with ..." %} or email `support@graphql.pro`. ### Checking for deferrals When a query has any `@defer`ed fields, you can check for `context[:defer]`: ```ruby if context[:defer] # some fields were `@defer`ed else # normal GraphQL, no `@defer` end ``` ### Working with deferrals To handle deferrals, you can enumerate over `context[:defer]`, for example: ```ruby context[:defer].each do |deferral| # do something with the `deferral`, eg # stream_to_client(deferral.to_h) end ``` The initial result is _also_ present in the deferrals, so you can treat it just like a patch. Each deferred patch has a few methods for building a response: - `.to_h` returns a hash with `path:`, `data:`, and/or `errors:`. (There is no `path:` for the root result.) - `.to_http_multipart` returns a string which works with Apollo client's `@defer` support. - `.path` returns the path to this patch in the response - `.data` returns successfully-resolved results of the patch - `.errors` returns an array of errors, if there were any Calling `.data` or `.errors` on a deferral will resume GraphQL execution until the patch is complete. ### Example: Rails with Apollo Client In this example, a Rails controller will stream HTTP Multipart patches to the client, in Apollo Client's supported format. ```ruby class GraphqlController < ApplicationController # Support `response.stream` below: include ActionController::Live def execute # ... result = MySchema.execute(query, variables: variables, context: context, operation_name: operation_name) # Check if this is a deferred query: if (deferred = result.context[:defer]) # Use built-in `stream_http_multipart` with Apollo-Client & ActionController::Live deferred.stream_http_multipart(response) else # Return a plain, non-deferred result render json: result end ensure # Always make sure to close the stream response.stream.close end end ``` You can also investigate a [full Rails & Apollo-Client demo](https://github.com/rmosolgo/graphql_defer_example) ## Next Steps Read about {% internal_link "client usage", "/defer/usage" %} of `@defer`. graphql-ruby-1.11.10/guides/defer/usage.md000066400000000000000000000022531414121453000202600ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - Defer title: Usage desc: Using @defer on the client side index: 2 pro: true --- `@defer` is a [GraphQL directive](https://graphql.org/learn/queries/#directives) which instructs the server to execute the field in a special way: ```graphql query GetPlayerInfo($handle: String!){ player(handle: $handle) { name # Send this field later, to avoid slowing down the initial response: topScore(from: 2000, to: 2020) @defer } } ``` The directives `@skip` and `@include` are built into any GraphQL server and client, but `@defer` requires special attention. Apollo-Client has [experimental support](https://www.apollographql.com/docs/react/features/defer-support.html) but it may [have some issues](https://github.com/apollographql/apollo-client/issues/4484), so you can try [this updated fork](https://github.com/rmosolgo/apollo-client) while they're worked out. Want to use `@defer` with another client? Please {% open_an_issue "Client support for @defer with ..." %} or email `support@graphql.pro` and we'll dig in! ## Next Steps {% internal_link "Set up your server", "/defer/setup" %} to support `@defer`. graphql-ruby-1.11.10/guides/development.md000066400000000000000000000242641414121453000204170ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: Development section: Other desc: Hacking on GraphQL Ruby --- So, you want to hack on GraphQL Ruby! Here are some tips for getting started. - [Setup](#setup) your development environment - [Run the tests](#running-the-tests) to verify your setup - [Debug](#debugging-with-pry) with pry - [Run the benchmarks](#running-the-benchmarks) to test performance in your environment - [Coding guidelines](#coding-guidelines) for working on your contribution - Special tools for building the [lexer and parser](#lexer-and-parser) - Building and publishing the [GraphQL Ruby website](#website) - [Versioning](#versioning) describes how changes are managed and released - [Releasing](#releasing) Gem versions ### Setup Get your own copy of graphql-ruby by forking [`rmosolgo/graphql-ruby` on GitHub](https://github.com/rmosolgo/graphql-ruby) and cloning your fork. Then, install the dependencies: - Install SQLite3 and MongoDB (eg, `brew install sqlite && brew tap mongodb/brew && brew install mongodb-community`) - `bundle install` - Optional: [Ragel](https://www.colm.net/open-source/ragel/) is required to build the lexer ### Running the Tests #### Unit tests You can run the tests with ``` bundle exec rake # tests & Rubocop bundle exec rake test # tests only ``` You can run a __specific file__ with `TEST=`: ``` bundle exec rake test TEST=spec/graphql/query_spec.rb # run tests in `query_spec.rb` only ``` Alternatively, you can run a __specific file__ with the [m](https://github.com/qrush/m) gem: ``` m spec/graphql/query_spec.rb # run tests in `query_spec.rb` only ``` You can focus on a __specific example__ with `focus`: ```ruby focus it "does something cool" do # ... end ``` Then, only `focus`ed tests will run: ``` bundle exec rake test # only the focused test will be run ``` (This is provided by `minitest-focus`.) You can __watch files__ with `guard`: ``` bundle exec guard ``` When a file in `lib/` is modified, `guard` will run the corresponding file in `spec`. Guard also respects `# test_via:` comments, so it will run that test when the file changes (if there is no corresponding file by name). #### Integration tests You need to pick a specific gemfile from gemfiles/ to run integration tests. For example: ``` BUNDLE_GEMFILE=gemfiles/rails_5.1.gemfile bundle install BUNDLE_GEMFILE=gemfiles/rails_5.1.gemfile bundle exec rake test TEST=spec/integration/rails/graphql/relay/array_connection_spec.rb ``` #### Other tests There are system tests for checking ActionCable behavior, use: ``` bundle exec rake test:system ``` And JavaScript tests: ``` bundle exec rake test:js ``` ### Gemfiles, Gemfiles, Gemfiles `graphql-ruby` has several gemfiles to ensure support for various Rails versions. You can run all gemfiles with ``` appraisal rake ``` You can specify a gemfile with `BUNDLE_GEMFILE`, eg: ``` BUNDLE_GEMFILE=gemfiles/rails_5.gemfile bundle exec rake ``` ### Debugging with Pry [`pry`](https://pryrepl.org/) is included with GraphQL-Ruby's development setup to help with debugging. To pause execution in Ruby code, add: ```ruby binding.pry ``` Then, the program will pause and your terminal will become a Ruby REPL. Feel free to use `pry` in your development process! ### Running the Benchmarks This project includes some Rake tasks to record benchmarks: ```sh $ bundle exec rake -T | grep bench: rake bench:profile # Generate a profile of the introspection query rake bench:query # Benchmark the introspection query rake bench:validate # Benchmark validation of several queries ``` You can save results by sending the output into a file: ```sh $ bundle exec rake bench:validate > before.txt $ cat before.txt # ... # --> benchmark output here ``` If you want to check performance, create a baseline by running these tasks before your changes. Then, make your changes and run the tasks again and compare your results. Keep these points in mind when using benchmarks: - The results are hardware-specific: computers with different hardware will have different results. So don't compare your results to results from other computers. - The results are environment-specific: CPU and memory availability are affected by other processes on your computer. So try to create similar environments for your before-and-after testing. ### Coding Guidelines GraphQL-Ruby uses a thorough test suite to make sure things work reliably day-after-day. Please include tests that describe your changes, for example: - If you contribute a bug fix, include a test for the code that _was_ broken (and is now fixed) - If you contribute a feature, include tests for all intended uses of that feature - If you modify existing behavior, update the tests to cover all intended behaviors for that code Don't fret about coding style or organization. There's a minimal Rubocop config in `.rubocop.yml` which runs during CI. You can run it manually with `bundle exec rake rubocop`. ### Lexer and Parser The lexer and parser use a multistep build process: - Write the definition (`lexer.rl` or `parser.y`) - Run the generator (Ragel or Racc) to create `.rb` files (`lexer.rb` or `parser.rb`) - `require` those `.rb` files in GraphQL-Ruby To update the lexer or parser, you should update their corresponding _definitions_ (`lexer.rl` or `parser.y`). Then, you can run `bundle exec rake build_parser` to re-generate the `.rb` files. You will need Ragel to build the lexer (see above). If you start __guard__ (`bundle exec guard`), the `.rb` files will be rebuilt whenever the definition files are modified. #### Install Ragel and Colm on a Mac GraphQL Ruby requires Ragel 7.0.0.9 which is not available on Homebrew. To install it, you might have to download it from source. This is not meant to be a step by step guide and will likely not work as the documentation ages. Download colm from [http://www.colm.net/files/colm/colm-0.13.0.4.tar.gz](http://www.colm.net/files/colm/colm-0.13.0.4.tar.gz) Download ragel from [http://www.colm.net/files/ragel/ragel-7.0.0.9.tar.gz](http://www.colm.net/files/ragel/ragel-7.0.0.9.tar.gz) ```sh # In colm directory cat README # for install instructions # The author who added this documentation succeeded with these steps ./configure make make install # After installing colm, in ragel directory ./configure make make install ``` ### Website To update the website, update the `.md` files in `guides/`. To preview your changes, you can serve the website locally: ``` bundle exec rake site:serve ``` Then visit `http://localhost:4000`. To publish the website with GitHub pages, run the Rake task: ``` bundle exec rake site:publish ``` #### Search Index GraphQL-Ruby's search index is powered by Algolia. To update the index, you need the API key in an environment variable: ``` $ export ALGOLIA_API_KEY=... ``` Without this key, the search index will fall out-of-sync with the website. Contact @rmosolgo to gain access to this key. #### API Docs The GraphQL-Ruby website has its own rendered version of the gem's API docs. They're pushed to GitHub pages with a special process. First, generate local copies of the docs you want to publish: ``` $ bundle exec rake apidocs:gen_version[1.8.0] # for example, generate docs that you want to publish ``` Then, check them out locally: ``` $ bundle exec rake site:serve # then visit http://localhost:4000/api-doc/1.8.0/ ``` Then, publish them as part of the whole site: ``` $ bundle exec rake site:publish ``` Finally, check your work by visiting the docs on the website. ### Versioning GraphQL-Ruby does _not_ attempt to deliver "semantic versioning" for the reasons described in `jashkenas`' s post, ["Why Semantic Versioning Isn't"](https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e). Instead, the following scheme is used as a guideline: - Version numbers consist of three parts, `MAJOR.MINOR.PATCH` - __`PATCH`__ version indicates bug fixes or small features for specific use cases. Ideally, you can upgrade patch versions with only a quick skim of the changelog. - __`MINOR`__ version indicates significant additions, internal refactors, or small breaking changes. When upgrading a minor version, check the changelog for any new features or breaking changes which apply to your system. The changelog will always include an upgrade path for any breaking changes. Minor versions may also include deprecation warnings to warn about upcoming breaking changes. - __`MAJOR`__ version indicates significant breaking changes. Do not expect code to run without some modification, especially if the code yielded deprecation warnings. This policy is inspired by the [Ruby 2.1.0+ version policy](https://www.ruby-lang.org/en/news/2013/12/21/ruby-version-policy-changes-with-2-1-0/). Pull requests and issues may be tagged with a [GitHub milestone](https://github.com/rmosolgo/graphql-ruby/milestones) to denote when they'll be released. The [changelog](https://github.com/rmosolgo/graphql-ruby/blob/master/CHANGELOG.md) should always contain accurate and thorough information so that users can upgrade. If you have trouble upgrading based on the changelog, please open an issue on GitHub. ### Releasing GraphQL-Ruby doesn't have a strict release schedule. If you think it should, consider opening an issue to share your thoughts. To cut a release: - Update `CHANGELOG.md` for the new version: - Add a new heading for the new version, and paste the four categories of changes into the new section - Open the GitHub milestone corresponding to the new version - Check each pull request and put it in the category (or categories) that it belongs in - If a change affects the default behavior of GraphQL-Ruby in a disruptive way, add it to `### Breaking Changes` and include migration notes if possible - Include the PR number beside the change description for future reference - Update `lib/graphql/version.rb` with the new version number - Commit changes to master - Release to RubyGems - Without 2FA 😢: `bundle exec rake release` - With 2FA 😎: `bundle exec rake build` then `gem push pkg/graphql-.gem`, `git tag v && git push v` - Update the website: - Generate new API docs with `bundle exec rake apidocs:gen_version[]` - Push them to the website with `bundle exec rake site:publish` - Celebrate 🎊 ! graphql-ruby-1.11.10/guides/errors/000077500000000000000000000000001414121453000170575ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/errors/error_handling.md000066400000000000000000000045721414121453000224060ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Errors title: Error Handling desc: Rescuing application errors from field resolvers index: 3 --- You can configure your schema to rescue application errors during field resolution. Errors during batch loading will also be rescued. __Note:__ This feature is for class-based schemas using the {% internal_link "interpreter runtime", "/queries/interpreter" %} only. Thanks to [`@exAspArk`] for the [`graphql-errors`](https://github.com/exAspArk/graphql-errors) gem which inspired this behavior and [`@thiago-sydow`](https://github.com/thiago-sydow) who [suggested](https://github.com/rmosolgo/graphql-ruby/issues/2139#issuecomment-524913594) and implementation like this. ## Setup Add error handling to your schema with `use GraphQL::Execution::Errors`. (This will be the default in a future graphql-ruby version.) ```ruby class MySchema < GraphQL::Schema # Use the new runtime & analyzers: use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST # Also use the new error handling: use GraphQL::Execution::Errors end ``` ## Add error handlers Handlers are added with `rescue_from` configurations in the schema: ```ruby class MySchema < GraphQL::Schema # ... rescue_from(ActiveRecord::RecordNotFound) do |err, obj, args, ctx, field| # Raise a graphql-friendly error with a custom message raise GraphQL::ExecutionError, "#{field.type.unwrap.graphql_name} not found" end rescue_from(SearchIndex::UnavailableError) do |err, obj, args, ctx, field| # Log the error Bugsnag.notify(err) # replace it with nil nil end end ``` The handler is called with several arguments: - __`err`__ is the error that was raised during field execution, then rescued - __`obj`__ is the object which was having a field resolved against it - __`args`__ is the Hash of arguments passed to the resolver - __`ctx`__ is the query context - __`field`__ is the {{ "GraphQL::Schema::Field" | api_doc }} instance for the field where the error was rescued Inside the handler, you can: - Raise a GraphQL-friendly {{ "GraphQL::ExecutionError" | api_doc }} to return to the user - Re-raise the given `err` to crash the query and halt execution. (The error will propagate to your application, eg, the controller.) - Report some metrics from the error, if applicable - Return a new value to be used for the error case (if not raising another error) graphql-ruby-1.11.10/guides/errors/execution_errors.md000066400000000000000000000055261414121453000230100ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Errors title: Top-level "errors" desc: The top-level "errors" array and how to use it. index: 1 --- The GraphQL specification [allows for a top-level `"errors"` key](https://graphql.github.io/graphql-spec/June2018/#sec-Errors) in the response which may contain information about what went wrong during execution. For example: ```ruby { "errors" => [ ... ] } ``` The response may include _both_ `"data"` and `"errors"` in the case of a partial success: ```ruby { "data" => { ... } # parts of the query that ran successfully "errors" => [ ... ] # errors that prevented some parts of the query from running } ``` ## When to Use Top-Level Errors In general, top-level errors should only be used for exceptional circumstances when a developer should be made aware that the system had some kind of problem. For example, the GraphQL specification says that when a non-null field returns `nil`, an error should be added to the `"errors"` key. This kind of error is not recoverable by the client. Instead, something on the server should be fixed to handle this case. When you want to notify a client some kind of recoverable issue, consider making error messages part of the schema, for example, as in {% internal_link "mutation errors", "/mutations/mutation_errors" %}. ## Adding Errors to the Array In GraphQL-Ruby, you can add entries to this array by raising `GraphQL::ExecutionError` (or a subclass of it), for example: ```ruby raise GraphQL::ExecutionError, "Can't continue with this query" ``` When this error is raised, its `message` will be added to the `"errors"` key and GraphQL-Ruby will automatically add the `line`, `column` and `path` to it. So, the above error might be: ```ruby { "errors" => [ { "message" => "Can't continue with this query", "locations" => [ { "line" => 2, "column" => 10, } ], "path" => ["user", "login"], } ] } ``` ## Customizing Error JSON The default error JSON includes `"message"`, `"locations"` and `"path"`. The [forthcoming version](https://spec.graphql.org/draft/#example-fce18) of the GraphQL spec recommends putting custom data in the `"extensions"` key of the error JSON. You can customize this in two ways: - Pass `extensions:` when raising an error, for example: ```ruby raise GraphQL::ExecutionError.new("Something went wrong", extensions: { "code" => "BROKEN" }) ``` In this case, `"extensions" => { "code" => "BROKEN" }` will be added to the error JSON. - Override `#to_h` in a subclass of `GraphQL::ExecutionError`, for example: ```ruby class ServiceUnavailableError < GraphQL::ExecutionError def to_h super.merge({ "extensions" => {"code" => "SERVICE_UNAVAILABLE"} }) end end ``` Now, `"extensions" => { "code" => "SERVICE_UNAVAILABLE" }` will be added to the error JSON. graphql-ruby-1.11.10/guides/errors/overview.md000066400000000000000000000054761414121453000212630ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Errors title: Errors in GraphQL desc: A conceptual introduction to errors in GraphQL index: 0 redirect_from: - /schema/type_errors/ - /queries/error_handling/ --- There are a _lot_ of different kinds of errors in GraphQL! In this guide, we'll discuss some of the main categories and learn when they apply. ## Validation Errors Because GraphQL is strongly typed, it performs validation of all queries before executing them. If an incoming query is invalid, it isn't executed. Instead, a response is sent back with `"errors"`: ```ruby { "errors" => [ ... ] } ``` Each error has a message, line, column and path. The validation rules are part of the GraphQL specification and built into GraphQL-Ruby, so there's not really a way to customize this behavior, except to pass `validate: false` when executing a query, which skips validation altogether. ## Analysis Errors GraphQL-Ruby supports pre-execution analysis, which may return `"errors"` instead of running a query. You can find details in the {% internal_link "Analysis guide", "queries/ast_analysis" %}. ## GraphQL Invariants While GraphQL-Ruby is executing a query, some constraints must be satisfied. For example: - Non-null fields may not return `nil`. - Interface and union types must resolve objects to types that belong to that interface/union. These constraints are part of the GraphQL specification, and when they are violated, it must be addressed somehow. Read more in {% internal_link "Type Errors", "/errors/type_errors" %}. ## Top-level `"errors"` The GraphQL specification provides for a top-level `"errors"` key which may include information about errors during query execution. `"errors"` and `"data"` may _both_ be present in the case of a partial success. In your own schema, you can add to the `"errors"` key by raising `GraphQL::ExecutionError` (or subclasses of it) in your code. Read more in the {% internal_link "Execution Errors guide", "/errors/execution_errors" %}. ## Handled Errors A schema can be configured to handle certain errors during field execution with handlers that you give it, using `rescue_from`. Read more in the {% internal_link "Error Handling guide", "/errors/error_handling" %}. ## Unhandled Errors (Crashes) When a `raise`d error is not `rescue`d, the GraphQL query crashes entirely and the surrounding code (like a Rails controller) must handle the exception. For example, Rails will probably return a generic `500` page. ## Errors as Data When you want end users (human beings) to read error messages, you can express errors _in the schema_, using normal GraphQL fields and types. In this approach, errors are strongly-typed data, queryable in the schema, like any other application data. For more about this approach, see {% internal_link "Mutation Errors", "/mutations/mutation_errors" %} graphql-ruby-1.11.10/guides/errors/type_errors.md000066400000000000000000000023521414121453000217600ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Errors title: Type Errors desc: Handling type errors index: 3 --- The GraphQL specification _requires_ certain assumptions to hold true when executing a query. However, it's possible that some code would violate that assumption, resulting in a type error. Here are two type errors that you can customize in GraphQL-Ruby: - A field with `null: false` returned `nil` - A field returned a value as a union or interface, but that value couldn't be resolved to a member of that union or interface. You can specify behavior in these cases by defining a {{ "Schema.type_error" | api_doc }} hook: ```ruby class MySchema < GraphQL::Schema def self.type_error(err, query_ctx) # Handle a failed runtime type coercion end end ``` It is called with an instance of {{ "GraphQL::UnresolvedTypeError" | api_doc }} or {{ "GraphQL::InvalidNullError" | api_doc }} and the query context (a {{ "GraphQL::Query::Context" | api_doc }}). If you don't specify a hook, you get the default behavior: - Unexpected `nil`s add an error the response's `"errors"` key - Unresolved Union / Interface types raise {{ "GraphQL::UnresolvedTypeError" | api_doc }} An object that fails type resolution is treated as `nil`. graphql-ruby-1.11.10/guides/faq.md000066400000000000000000000015411414121453000166350ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: FAQ other: true desc: How to do common tasks --- Returning Route URLs ==================== With GraphQL there is less of a need to include resource URLs to other REST resources, however sometimes you want to use Rails routing to include a URL as one of your fields. A common use case would be to build HTML format URLs to render a link in your React UI. In that case you can add the Rails route helpers to the execution context as shown below. Example ------- ```ruby class Types::UserType < Types::BaseObject field :profile_url, String, null: false def profile_url context[:routes].user_url(object) end end # Add the url helpers to `context`: MySchema.execute( params[:query], variables: params[:variables], context: { routes: Rails.application.routes.url_helpers, # ... }, ) ``` graphql-ruby-1.11.10/guides/fields/000077500000000000000000000000001414121453000170115ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/fields/arguments.md000066400000000000000000000104351414121453000213430ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Fields title: Arguments desc: Fields may take arguments as inputs index: 10 --- Fields can take **arguments** as input. These can be used to determine the return value (eg, filtering search results) or to modify the application state (eg, updating the database in `MutationType`). Arguments are defined with the `argument` helper. These arguments are passed as [keyword arguments](https://robots.thoughtbot.com/ruby-2-keyword-arguments) to the resolver method: ```ruby field :search_posts, [PostType], null: false do argument :category, String, required: true end def search_posts(category:) Post.where(category: category).limit(10) end ``` To make an argument optional, set `required: false`, and set default values for the corresponding keyword arguments: ```ruby field :search_posts, [PostType], null: false do argument :category, String, required: false end def search_posts(category: nil) if category Post.where(category: category).limit(10) else Post.all.limit(10) end end ``` Be aware that if all arguments are optional and the query does not provide any arguments, then the resolver method will be called with no arguments. To prevent an `ArgumentError` in this case, you must either specify default values for all keyword arguments (as done in the prior example) or use the double splat operator argument in the method definition. For example: ```ruby def search_posts(**args) if args[:category] Post.where(category: args[:category]).limit(10) else Post.all.limit(10) end end ``` Another approach is to use `default_value: value` to provide a default value for the argument if it is not supplied in the query. ```ruby field :search_posts, [PostType], null: false do argument :category, String, required: false, default_value: "Programming" end def search_posts(category:) Post.where(category: category).limit(10) end ``` **Experimental:** __Deprecated__ arguments can be marked by adding a `deprecation_reason:` keyword argument: ```ruby field :search_posts, [PostType], null: false do argument :name, String, required: false, deprecation_reason: "Use `query` instead." argument :query, String, required: false end ``` Note argument deprecation is a stage 2 GraphQL [proposal](https://github.com/graphql/graphql-spec/pull/525) so not all clients will leverage this information. Use `as: :alternate_name` to use a different key from within your resolvers while exposing another key to clients. ```ruby field :post, PostType, null: false do argument :post_id, ID, required: true, as: :id end def post(id:) Post.find(id) end ``` Provide a `prepare` function to modify or validate the value of an argument before the field's resolver method is executed: ```ruby field :posts, [PostType], null: false do argument :start_date, String, required: true, prepare: ->(startDate, ctx) { # return the prepared argument. # raise a GraphQL::ExecutionError to halt the execution of the field and # add the exception's message to the `errors` key. } end def posts(start_date:) # use prepared start_date end ``` Arguments that are snake_cased will be camelized in the GraphQL schema. Using the example of: ```ruby field :posts, [PostType], null: false do argument :start_year, Int, required: true end ``` The corresponding GraphQL query will look like: ```graphql { posts(startYear: 2018) { id } } ``` To disable auto-camelization, pass `camelize: false` to the `argument` method. ```ruby field :posts, [PostType], null: false do argument :start_year, Int, required: true, camelize: false end ``` Furthermore, if your argument is already camelCased, then it will remain camelized in the GraphQL schema. However, the argument will be converted to snake_case when it is passed to the resolver method: ```ruby field :posts, [PostType], null: false do argument :startYear, Int, required: true end def posts(start_year:) # ... end ``` Only certain types are valid for arguments: - {{ "GraphQL::ScalarType" | api_doc }}, including built-in scalars (string, int, float, boolean, ID) - {{ "GraphQL::EnumType" | api_doc }} - {{ "GraphQL::InputObjectType" | api_doc }}, which allows key-value pairs as input - {{ "GraphQL::ListType" | api_doc }}s of a valid input type - {{ "GraphQL::NonNullType" | api_doc }}s of a valid input type graphql-ruby-1.11.10/guides/fields/introduction.md000066400000000000000000000207451414121453000220640ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Fields title: Introduction desc: Implement fields and resolvers with the Ruby DSL index: 0 --- Object fields expose data about that object or connect the object to other objects. You can add fields to your object types with the `field(...)` class method, for example: ```ruby field :name, String, "The unique name of this list", null: false ``` {% internal_link "Objects", "/type_definitions/objects" %} and {% internal_link "Interfaces", "/type_definitions/interfaces" %} have fields. The different elements of field definition are addressed below: - [Return types](#field-return-type) say what kind of data this field returns - [Documentation](#field-documentation) includes description and deprecation notes - [Resolution behavior](#field-resolution) hooks up Ruby code to the GraphQL field - [Arguments](#field-arguments) allow fields to take input when they're queried - [Extra field metadata](#extra-field-metadata) for low-level access to the GraphQL-Ruby runtime - [Add default values for field parameters](#field-parameter-default-values) ### Field Return Type The second argument to `field(...)` is the return type. This can be: - A built-in GraphQL type (`Integer`, `Float`, `String`, `ID`, or `Boolean`) - A GraphQL type from your application - An _array_ of any of the above, which denotes a {% internal_link "list type", "/type_definitions/lists" %}. {% internal_link "Nullability", "/type_definitions/non_nulls" %} is expressed with the required `null:` keyword: - `null: true` means that the field _may_ return `nil` - `null: false` means the field is non-nullable; it may not return `nil`. If the implementation returns `nil`, GraphQL-Ruby will return an error to the client. Additionally, list types maybe nullable by adding `[..., null: true]` to the definition. Here are some examples: ```ruby field :name, String, null: true # `String`, may return a `String` or `nil` field :id, ID, null: false # `ID!`, always returns an `ID`, never `nil` field :teammates, [Types::User], null: false # `[User!]!`, always returns a list containing `User`s field :scores, [Integer, null: true], null: true # `[Int]`, may return a list or `nil`, the list may contain a mix of `Integer`s and `nil`s ``` ### Field Documentation Fields may be documented with a __description__ and may be __deprecated__. __Descriptions__ can be added with the `field(...)` method as a positional argument, a keyword argument, or inside the block: ```ruby # 3rd positional argument field :name, String, "The name of this thing", null: false # `description:` keyword field :name, String, null: false, description: "The name of this thing" # inside the block field :name, String, null: false do description "The name of this thing" end ``` __Deprecated__ fields can be marked by adding a `deprecation_reason:` keyword argument: ```ruby field :email, String, null: true, deprecation_reason: "Users may have multiple emails, use `User.emails` instead." ``` Fields with a `deprecation_reason:` will appear as "deprecated" in GraphiQL. ### Field Resolution In general, fields return Ruby values corresponding to their GraphQL return types. For example, a field with the return type `String` should return a Ruby string, and a field with the return type `[User!]!` should return a Ruby array with zero or more `User` objects in it. By default, fields return values by: - Trying to call a method on the underlying object; _OR_ - If the underlying object is a `Hash`, lookup a key in that hash. The method name or hash key corresponds to the field name, so in this example: ```ruby field :top_score, Integer, null: false ``` The default behavior is to look for a `#top_score` method, or lookup a `Hash` key, `:top_score` (symbol) or `"top_score"` (string). You can override the method name with the `method:` keyword, or override the hash key with the `hash_key:` keyword, for example: ```ruby # Use the `#best_score` method to resolve this field field :top_score, Integer, null: false, method: :best_score # Lookup `hash["allPlayers"]` to resolve this field field :players, [User], null: false, hash_key: "allPlayers" ``` To pass-through the underlying object without calling a method on it, you can use `method: :itself`: ```ruby field :player, User, null: false, method: :itself ``` This is equivalent to: ```ruby field :player, User, null: false def player object end ``` If you don't want to delegate to the underlying object, you can define a method for each field: ```ruby # Use the custom method below to resolve this field field :total_games_played, Integer, null: false def total_games_played object.games.count end ``` Inside the method, you can access some helper methods: - `object` is the underlying application object (formerly `obj` to resolve functions) - `context` is the query context (passed as `context:` when executing queries, formerly `ctx` to resolve functions) Additionally, when you define arguments (see below), they're passed to the method definition, for example: ```ruby # Call the custom method with incoming arguments field :current_winning_streak, Integer, null: false do argument :include_ties, Boolean, required: false, default_value: false end def current_winning_streak(include_ties:) # Business logic goes here end ``` As the examples above show, by default the custom method name must match the field name. If you want to use a different custom method, the `resolver_method` option is available: ```ruby # Use the custom method with a non-default name below to resolve this field field :total_games_played, Integer, null: false, resolver_method: :games_played def games_played object.games.count end ``` `resolver_method` has two main use cases: 1. resolver re-use between multiple fields 2. dealing with method conflicts (specifically if you have fields named `context` or `object`) Note that `resolver_method` _cannot_ be used in combination with `method` or `hash_key`. ### Field Arguments _Arguments_ allow fields to take input to their resolution. For example: - A `search()` field may take a `term:` argument, which is the query to use for searching, eg `search(term: "GraphQL")` - A `user()` field may take an `id:` argument, which specifies which user to find, eg `user(id: 1)` - An `attachments()` field may take a `type:` argument, which filters the result by file type, eg `attachments(type: PHOTO)` Read more in the {% internal_link "Arguments guide", "/fields/arguments" %} ### Extra Field Metadata Inside a field method, you can access some low-level objects from the GraphQL-Ruby runtime. Be warned, these APIs are subject to change, so check the changelog when updating. A few `extras` are available: - `ast_node` - `graphql_name` (the field's name) - `owner` (the type that this field belongs to) - `lookahead` (see {% internal_link "Lookahead", "/queries/lookahead" %}) - `execution_errors`, whose `#add(err_or_msg)` method should be used for adding errors - `argument_details` (Intepreter only), an instance of {{ "GraphQL::Execution::Interpreter::Arguments" | api_doc }} with argument metadata - Custom extras, see below To inject them into your field method, first, add the `extras:` option to the field definition: ```ruby field :my_field, String, null: false, extras: [:ast_node] ``` Then add `ast_node:` keyword to the method signature: ```ruby def my_field(ast_node:) # ... end ``` At runtime, the requested runtime object will be passed to the field. __Custom extras__ are also possible. Any method on your field class can be passed to `extras: [...]`, and the value will be injected into the method. For example, `extras: [:owner]` will inject the object type who owns the field. Any new methods on your custom field class may be used, too. ### Field Parameter Default Values The field method requires you to pass `null:` keyword argument to determine whether the field is nullable or not. For another field you may want to override `camelize`, which is `true` by default. You can override this behavior by adding a custom field with overwritten `camelize` option, which is `true` by default. ```ruby class CustomField < GraphQL::Schema::Field # Add `null: false` and `camelize: false` which provide default values # in case the caller doesn't pass anything for those arguments. # **kwargs is a catch-all that will get everything else def initialize(*args, null: false, camelize: false, **kwargs, &block) # Then, call super _without_ any args, where Ruby will take # _all_ the args originally passed to this method and pass it to the super method. super end end ``` graphql-ruby-1.11.10/guides/fields/limits.md000066400000000000000000000014621414121453000206370ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Fields title: Limits desc: Always limit lists of items index: 4 --- ## List Fields Always limit the number of items which can be returned from a list field. For example, use a `limit:` argument and make sure it's not too big. The `prepare:` function provides a convenient place to cap the number of items: ```ruby field :items, [Types::ItemType] do # Cap the number of items at 30 argument :limit, Integer, default_value: 20, prepare: ->(limit, ctx) {[limit, 30].min} end def items(limit:) object.items.limit(limit) end ``` This way, you won't hit your database for 1000 items! ## Relay Connections Relay connections accept a {% internal_link "`max_page_size` option","/relay/connections.html#maximum-page-size" %} which limits the number of nodes. graphql-ruby-1.11.10/guides/fields/resolvers.md000066400000000000000000000150331414121453000213610ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Fields title: Resolvers desc: Reusable, extendable resolution logic for complex fields index: 9 redirect_from: - /fields/functions --- A {{ "GraphQL::Schema::Resolver" | api_doc }} is a container for field signature and resolution logic. It can be attached to a field with the `resolver:` keyword: ```ruby # Use the resolver class to execute this field field :pending_orders, resolver: PendingOrders ``` Under the hood, {{ "GraphQL::Schema::Mutation" | api_doc }} is a specialized subclass of `Resolver`. ## First, ask yourself ... Do you really need a `Resolver`? Putting logic in a Resolver has some downsides: - Since it's coupled to GraphQL, it's harder to test than a plain ol' Ruby object in your app - Since the base class comes from GraphQL-Ruby, it's subject to upstream changes which may require updates in your code Here are a few alternatives to consider: - Put display logic (sorting, filtering, etc.) into a plain ol' Ruby class in your app, and test that class - Hook up that object with a method, for example: ```ruby field :recommended_items, [Types::Item], null: false def recommended_items ItemRecommendation.new(user: context[:viewer]).items end ``` - If you have lots of arguments to share, use a class method to generate fields, for example: ```ruby # Generate a field which returns a filtered, sorted list of items def self.items_field(name, override_options) # Prepare options default_field_options = { type: [Types::Item], null: false } field_options = default_field_options.merge(override_options) # Create the field field(name, field_options) do argument :order_by, Types::ItemOrder, required: false argument :category, Types::ItemCategory, required: false # Allow an override block to add more arguments yield self if block_given? end end # Then use the generator to create a field: items_field(:recommended_items) do |field| field.argument :similar_to_product_id, ID, required: false end # Implement the field def recommended_items # ... end ``` As a matter of code organization, that class method could be put in a module and shared between different classes that need it. - If you need the _same_ logic shared between several objects, consider using a Ruby module and its `self.included` hook, for example: ```ruby module HasRecommendedItems def self.included(child_class) # attach the field here child_class.field(:recommended_items, [Types::Item], null: false) end # then implement the field def recommended_items # ... end end # Add the field to some objects: class Types::User < BaseObject include HasRecommendedItems # adds the field end ``` - If the module approach looks good to you, also consider {% internal_link "Interfaces", "/type_definitions/interfaces" %}. They also share behavior between objects (since they're just modules that get included, after all), and they expose that commonality to clients via introspection. ## When do you really need a resolver? So, if there are other, better options, why does `Resolver` exist? Here are a few specific advantages: - __Isolation__. A `Resolver` is instantiated for each call to the field, so its instance variables are private to that object. If you need to use instance variables for some reason, this helps. You have a guarantee that those values won't hang around when the work is done. - __Complex Schema Generation__. `RelayClassicMutation` (which is a `Resolver` subclass) generates input types and return types for each mutation. Using a `Resolver` class makes it easier to implement, share and extend this code generation logic. ## Using `resolver` To add resolvers to your project, make a base class: ```ruby # app/graphql/resolvers/base.rb module Resolvers class Base < GraphQL::Schema::Resolver # if you have a custom argument class, you can attach it: argument_class Arguments::Base end end ``` Then, extend it as needed: ```ruby module Resolvers class RecommendedItems < Resolvers::Base type [Types::Item], null: false argument :order_by, Types::ItemOrder, required: false argument :category, Types::ItemCategory, required: false def resolve(order_by: nil, category: nil) # call your application logic here: recommendations = ItemRecommendation.new( viewer: context[:viewer], recommended_for: object, order_by: order_by, category: category, ) # return the list of items recommendations.items end end end ``` And attach it to your field: ```ruby class Types::User < Types::BaseObject field :recommended_items, resolver: Resolvers::RecommendedItems, description: "Items this user might like" end ``` Since the `Resolver` lifecycle is managed by the GraphQL runtime, the best way to test it is to execute GraphQL queries and check the results. ### Nesting resolvers of the same type You may run into cyclical loading issues when using a resolver within the definition of the type the resolver returns e.g. ```ruby # app/graphql/types/query_type.rb module Types class QueryType < Types::BaseObject field :tasks, resolver: Resolvers::TasksResolver end end # app/graphql/types/task_type.rb module Types class TaskType < Types::BaseObject field :title, String, null: false field :tasks, resolver: Resolvers::TasksResolver end end # app/graphql/resolvers/tasks_resolver.rb module Resolvers class TasksResolver < GraphQL::Schema::Resolver type [Types::TaskType], null: false def resolve [] end end end ``` The above can produce the following error: `Failed to build return type for Task.tasks from nil: Unexpected type input: (NilClass)`. A simple solution is to express the type as a string in the resolver: ```ruby module Resolvers class TasksResolver < GraphQL::Schema::Resolver type "[Types::TaskType]", null: false def resolve [] end end end ``` In doing so, you can defer the loading of the type class until the nested resolver has already been loaded. # Extensions In cases when you want your resolvers to add some extensions to the field they resolve, you can use `extension` method, which accepts extension class and options. Multiple extensions can be configured for a single resolver. ```ruby class GreetingExtension < GraphQL::Schema::FieldExtension def resolve(object:, arguments:, **rest) name = yield(object, arguments) "#{options[:greeting]}, #{name}!" end end class ResolverWithExtension < BaseResolver type String, null: false extension GreetingExtension, greeting: "Hi" def resolve "Robert" end end ``` graphql-ruby-1.11.10/guides/getting_started.md000066400000000000000000000075041414121453000212620ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: Getting Started section: Other desc: Start here! --- ## Installation You can install `graphql` from RubyGems by adding to your application's `Gemfile`: ```ruby # Gemfile gem "graphql" ``` Then, running `bundle install`: ```sh $ bundle install ``` ## Getting Started On Rails, you can get started with a few [GraphQL generators](https://rmosolgo.github.io/graphql-ruby/schema/generators#graphqlinstall): ```sh # Add graphql-ruby boilerplate and mount graphiql in development rails g graphql:install # Make your first object type rails g graphql:object Post title:String rating:Int comments:[Comment] ``` Or, you can build a GraphQL server by hand: - Define some types - Connect them to a schema - Execute queries with your schema ### Declare types Types describe objects in your application and form the basis for [GraphQL's type system](https://graphql.org/learn/schema/#type-system). ```ruby # app/graphql/types/post_type.rb module Types class PostType < Types::BaseObject description "A blog post" field :id, ID, null: false field :title, String, null: false # fields should be queried in camel-case (this will be `truncatedPreview`) field :truncated_preview, String, null: false # Fields can return lists of other objects: field :comments, [Types::CommentType], null: true, # And fields can have their own descriptions: description: "This post's comments, or null if this post has comments disabled." end end # app/graphql/types/comment_type.rb module Types class CommentType < Types::BaseObject field :id, ID, null: false field :post, PostType, null: false end end ``` ### Build a Schema Before building a schema, you have to define an [entry point to your system, the "query root"](https://graphql.org/learn/schema/#the-query-and-mutation-types): ```ruby class QueryType < GraphQL::Schema::Object description "The query root of this schema" # First describe the field signature: field :post, PostType, null: true do description "Find a post by ID" argument :id, ID, required: true end # Then provide an implementation: def post(id:) Post.find(id) end end ``` Then, build a schema with `QueryType` as the query entry point: ```ruby class Schema < GraphQL::Schema query QueryType end ``` This schema is ready to serve GraphQL queries! {% internal_link "Browse the guides","/guides" %} to learn about other GraphQL Ruby features. ### Execute queries You can execute queries from a query string: ```ruby query_string = " { post(id: 1) { id title truncatedPreview } }" result_hash = Schema.execute(query_string) # { # "data" => { # "post" => { # "id" => 1, # "title" => "GraphQL is nice" # "truncatedPreview" => "GraphQL is..." # } # } # } ``` See {% internal_link "Executing Queries","/queries/executing_queries" %} for more information about running queries on your schema. ## Use with Relay If you're building a backend for [Relay](https://facebook.github.io/relay/), you'll need: - A JSON dump of the schema, which you can get by sending [`GraphQL::Introspection::INTROSPECTION_QUERY`](https://github.com/rmosolgo/graphql-ruby/blob/master/lib/graphql/introspection/introspection_query.rb) - Relay-specific helpers for GraphQL, see the `GraphQL::Relay` guides. ## Use with Apollo Client [Apollo Client](https://www.apollographql.com/) is a full featured, simple to use GraphQL client with convenient integrations for popular view layers. You don't need to do anything special to connect Apollo Client to a `graphql-ruby` server. ## Use with GraphQL.js Client [GraphQL.js Client](https://github.com/f/graphql.js) is a tiny client that is platform- and framework-agnostic. It works well with `graphql-ruby` servers, since GraphQL requests are simple query strings transported over HTTP. graphql-ruby-1.11.10/guides/graphql-ruby-icon.png000066400000000000000000000100121414121453000216060ustar00rootroot00000000000000PNG  IHDRggA\bKGD pHYsaa?itIMEJ7IDATx]}xSU&)lKIR(<Jp|DGQeR*M˂]G>gQaGyQOqgG|`gwMR*|5G{Is~&iIOy;: 0` 0` HwW %yݜGbXLa`8Ol 5|L*o%`03cVޮ $9 [LK0,滚|ky)[=aT<J+u_%侖"mw,cIM"tۓyYpl?_5~ԊpQCGiK%c( f'{ifhk%G$B \TҊxb`Jh㡂Lky]R"v$f&@(Ia2]s0n0i*XטJg-ef߰73f ^bҶPG'y}~:4Mr0|1"d7<&^߻'mНؙfd8&an dӀ^헅y@@.KJƌjr6^M~eh)jBUyx+X{T*(@4t$ !EtIkyBV(_&K,I9 ׄRq`o\LPL#A\T7'MUĸA,6E-{ Pr1f]|3C^!ձ7U i+peL}Gp.aVW3nV(i0c^t8j81 ,܁r|Y,M?Q7y{286nVe!z LN'O | poHl]qY^2U/TM"oir<䄹]R=0IL7=P<.!rBeDPMJڥ)qR?d"6jSwc=]2nEϚj;;;uUVpuh=-hjlΗ2JNݱRvB0 @czͅ@ͱ(Bi3ر6= T)&ۭIdj7K7|ޜ/$d6\]ȩ; [WGAbΈ؜YVHD_~Pm- Ak$t9La[\_譤pX#T\9v7igRtcPo:zIw'*'Ԫ'kYI(/`zׁ c!Um$VxT,81 |ikNΩ8$3!e9z;rm n% `sͱ F%9M:{ψi@nsga'8.5ʂcGb a|MI&tYow~G0PC`~s YL1QDnWI-bI0VeJ& fE ~gð1W8ךL).q QBj \͇΀*nu?̔N&EjO(PdI< #~K?´@lq>/nزGO 5=z=ewu~AcyQ%a=r5Uzs A3b"vE`][ϊA*1;ߦkzɄUKu:h"EK)*3q4n8nX;=CF[ v Mr)"Le{omx0x*X;O !G~Ym꼁$zf!CB  _-r6,98 40xDP*] "h6dTaX3|xž$O,+ꦆCGwZ::U?݇S&niJ3LT;KW%7B{:ǃ+{<d٩ uBo3k~W`]I$E!T y_C:ׁ8DՄN^kIJt;ljaЃA$oo.3TF{$彷Z~EJHXw f'YԈa0 1}"'H$Eн4@ys}2=Mߨԗˉ[;s\dA8A`Zts_ HYr̞tހNEBIlUÄU!dSwޥ iVZUA?cJcc;NMǽVhR"[t[|#qujTD@XkU>2 zwk.zfo n}'>S{:xt֭&q"GJp C 9"/C ~,IWꑘh51DpĤrzysړZ<#GGEVe DE|וQ 2W@,ndLAyޯVxEeڔ1aA*,D/@-`W?!N=P_:Q$Hl3?dߧӔBN>=d)ꬎljBK _Ngi}oVRB6C+t M mJ@vT{ꬎ t.Q$0jd"r՝L`m:gߪaꃾ YiT~1A``XάNFcO:C>+tΐÄĸ[Z;fn8mxl8V..f ݿXAֱ[B \CwSΐ3]M0Y=jٚkj?^V粐g!R&md9|8|?xW$m&%uu@L*H hi:^P 3͖ME~ן\R]z:7L!L%bShJ&Дt%&ܺ< Wȷƈӄd Al&bt9J=ް9(fUoT#C~''NА} Rț7"51UO!8 ĔYē"Ep{jb@x![wՀ}՜UGy3:^եc?i:L LA]/E6YGNʥ0ss\$?zޒ?Wͣ;-]{U dʹ(1nv;ÃZfeu0-WڕG}-M@ɖ Ѻ"UErr] ދ jub8-Q;Ɍ靿#v|)+s`жYgPPx4' _1+{4)r]t! `|] eU} 19GN߫Ŀ19IOV|1Y2ki\$&gige4 D稅Ir钖dfphw/#7Ig`z[c(r6 `YZ̽\0U Ǚ>M96"9"Hc*b+1)[ {a 0` 0`o$r{IENDB`graphql-ruby-1.11.10/guides/graphql-ruby.png000066400000000000000000000122271414121453000206720ustar00rootroot00000000000000PNG  IHDRgOۢsRGB pHYsaa?iiTXtXML:com.adobe.xmp www.inkscape.org 1 UXeIDATx\ TřgFF LC#,FMD#(p#f$g(1(̃`| fW.`{fI,țaqkwzwgw뜙zU_U_<SIv"X`\c\Gz"Y0 Z5mcSR];|ESeB=BhϵF{F65.kuBa ;)ɊIa=nr`'6pp_ ZUww`sd8u\0_6ul);Sdbs0|N/DktI01-~'50&ÄcDrgat,}N`e-˖տVsC#Bgq$#6@%afT zrXyO/yGq+ul8묪 =)|Hm ? Zk4beGvbBjm NSc|1 V^1QU2tc= 'AWwL<(X$L M5G;`m~=g@rS4" ,x3yg+[ݏ'6Ԅs&Mk|-]V2ўN퓧ORM=?>f{%N,9S5i6#Ic~_rd v< qiHJHeQZ(95Ոo2YG_$lod IbSȼ5^~2]9{X `MP?-TAmtW-D__ͣMפLʬ4E2$ i)NIS X8 /GI*N +dM9= 5Ɲ\\aRⱋ;}"8Fbs{7 5eދ/›;5ଭ#-=Wi }  BW\lϞSn M(lنHf>Oׯ} :gO¹V~0aW4E^Ӹ*_//[Ϙ1ͧ!s Ht;4zYe *vq!~ {Pڕ1zIbO rx~HܗВTڤx"@x!ކ}nI aN6v4އ9fb<5O>){dJN{m÷*iL0X|.oߵzX$EsKKׅiT{x9?^ܴ[W"h31O` k&N0Ha Aj G1DM$t#@Vћp 6("60NĬ `!M/t9QCfg.kjJ#Eξ?; 31 M_IM$qv-) 锱 \і~xFN=qE_@!y Fa\VEX\yܥ!x}~=|^`:/Ipo2DD< ,BT6=H`g:D\;mdʧ3p ODvV6oK¸zjb?9қn,[8"@#gA$0{._Xl%GnQ}aFwSDj~ .?UCu,̋1 d6# :A "Vc/bs"%M lP䬧 )L[an^y9gT>7PAK@! olЇ'Nn2}"07jɔrOe/ b[6L:Z֖*]:29fr5l| Z0|V-+mP 7#cikoF>5=e}1[j$Fje߲Np:b!"9xu?N"k?j*+)r QϏ?\Ic$Ν7؄_0pQ%~)N-^ ֞j+Oy)USpm^=돆wi0S~ξ"G B~vթcdH@c;0p;fbPW bV$7n !W8cbLK3F\f+~Z6p"&#!ݚ[{U6 &X')m4W~>Y`c|ZMpgS{MӬծCXAP?2TNU\a9dS^U6kGLX.A 野nLWb $ZȦ±A x0Ʋ[x}~eWX]c+rx1b? |L>{b ^BbԴt*{$9)AeęLgKِ$W@zJ܇_'XtQpU G/=pHgLb%T9bCS ͧW(t_M h G۔Vz .qK㘝1w(RqᏉ1OlUeʬ/ sa.C w+aJ Ws :o7z%}$I7K%Qe.=*s[=JoH*i^RXF`g<@ܥE}Q{N&ʬ˖L=[yٔ)"-5cSg}pE i*~:?Ȃ( e Q}N”96sXAjtQ1j$~dn`J$aLdcgz1NO$Cbnm B83|-'́Fg5}S%Y:D f{ DM[쵟N'.pS9 VN vpcXKOCD\-mx>T¾B88]A "W,͘Օ.-&N^yӯ̶e\6U=UűE`Pvƈo/;X4zb+S.٭Ñ{4onjs2;Er|the=^\{|SZŏK_8Lis/K s $Zh/Po +?lD)}XgdYYvƃ_.oo*e1:!yu0Yӂ\?Cc)kk-d|̓9CC h(GQx? I 1&cyҥ-%k;! 1hm6ߟvgƤ_9'}+'p3&C)Ku,RD9N>Y|^!Mg`/6mxU;6ØbSq>pFޭVr'SIENDB`graphql-ruby-1.11.10/guides/guides.html000066400000000000000000000035061414121453000177150ustar00rootroot00000000000000--- title: Guides Index sections: - name: Schema - name: Queries - name: Type Definitions - name: Authorization - name: Fields - name: Mutations - name: Errors - name: Pagination - name: Relay - name: Subscriptions - name: GraphQL Pro - name: GraphQL Pro - OperationStore - name: GraphQL Pro - Defer - name: JavaScript Client - name: Language Tools - name: Testing - name: Other ---

Guides

{% assign sorted_pages = site.pages | sort: "index" %} {% assign pages_with_index = ''|split:'' %} {% assign pages_without_index = ''|split:'' %} {% for guide in sorted_pages %} {% if guide.layout == "guide" %} {% if guide.index %} {% assign pages_with_index = pages_with_index | push: guide %} {% else %} {% assign pages_without_index = pages_without_index | push: guide %} {% endif %} {% endif %} {% endfor %} {% assign sorted_guides = pages_with_index | concat: pages_without_index %} {% for section in page.sections %}

{{ section.name }}

    {% for guide in sorted_guides %} {% if guide.section == section.name %}
  • {{ guide.title }} {{ guide.desc }}

  • {% endif %} {% endfor %}
{% endfor %}
graphql-ruby-1.11.10/guides/index.html000066400000000000000000000044031414121453000175410ustar00rootroot00000000000000--- title: Welcome ---
GraphQL Ruby Logo

GraphQL Ruby

Install the Gem

{% highlight bash %} # Download the gem: gem install graphql # Setup with Rails: rails generate graphql:install {% endhighlight %}

Get going fast with the graphql gem, battle-tested and trusted by GitHub, Shopify and Kickstarter.

Define Your Schema

Describe your application with the GraphQL schema to create a self-documenting, strongly-typed API.

{% highlight ruby %} # app/graphql/types/profile_type.rb class Types::ProfileType < Types::BaseObject field :id, ID, null: false field :name, String, null: false field :avatar, Types::PhotoType, null: true end {% endhighlight %}

Run Queries

{% highlight ruby %} # app/controllers/graphql_controller.rb result = MySchema.execute( params[:query], variables: params[:variables], context: { current_user: current_user }, ) render json: result {% endhighlight %}

Serve queries to build a great UI or webservice.

Add GraphQL to your Ruby app. Get Started!

graphql-ruby-1.11.10/guides/javascript_client/000077500000000000000000000000001414121453000212475ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/javascript_client/ably_key.png000066400000000000000000003616541414121453000235730ustar00rootroot00000000000000PNG  IHDR,IZ iCCPICC ProfileHWXS[R -)7A@H! !,*v*b[ kE `ETVTޤ}|s3{duGHj',Dž0SRӘ'(Pes$"(e ܂PbM.O8+A|\#@hz"X[ @e2c6 q@b>j2BQqtrB d?xL^t[g|#1lV"r@"e?K^txSبY8YpLa*'1kA|Q,iXҾ# kl.;(bҜD%vf'(qxz2>Z(̍RY [xaLAH8İУEY hs )b5%9JGEY6bi92!q L7O2a炵d%)|$%jqyD%7 VW@ҷXǶrC^+XI6;"V9׀ 6A GAA e z{/H`1ad>A"Gy꿌hO{)-{gGl8 cJ &È!D:61]$y0;p:O7 f\{ndGWUsUy3#V?F n[Z%VikNH%"Dd1RE*H +r9\B:cy|B1j%:eh$NAh>Z.AWh5CϢWЛh'10caX ebblVaX'֋}ĉ8g6DxÛcJ vOB8!' wM  ~ll fAbO"Hv$oR M* 6NIȪdc39F{ɧɃ****1*\Y*Tv4\UVhR(ޔJ6e!rrVUUTCu@uj!ՋU?R@dzzFYhiJZ ]A-\6_RN+uu uEeGԯjhXjj5iTj׸ѯItҌ\W -VWksZ]tnFs;Dm+plRm}:Z:ttfTd` KF8#qqi(QQGum{Ѻ~<݃7u?1r=m'ߢ^whќ%g6ajohdj(2dxΰ׈agmQ1X`L?3YlfHM Z&.2=hЌb24[odgnl>|y=  EFVɖK--_XZ[YZ=YZ[W[߰!ڰlrl6۴ۢYWP;7;f11ccܶ?v`8D9,rwx5|l5c[~utuuxI)iSg[gs K|mwǕ:uk7w7wst*,mV,k#c n=bx㻼M۽;}>>|:}M|پվO~g 8>3pn ,(4$-X+81"Qi?6/5tv0BXdؚ͑Ȋ'QQ 脈 &<Fǀu1cbcH;r89q-i{V%ON&6%'MNIz63elܔ+Ԇ4RZRڮI6L:x)VSfN4UjԓԧI''M̎aW3328\?znϛ<;sm 7'7,W(ޚ>'&gwPnr !ε}%K/._qRzwߏ]u15kg]p#ƕ7;n%޺s{;;/}}Jj<,{d68yq'Owq^><ܽYs5/_ ic/E/{ڗZz͊zow?@z|d}lϤ_l4~`(ohHG 6437³C;5K.(G?aL.n qQ6 2Ҕ"tqVĢ [CH| n |ŝO&Dxf#Cm=lW pHYs%%IR$iTXtXML:com.adobe.xmp 1008 812 aʴiDOT(V[@IDATx }ScL1 EJJ! MW隆rJtM{F_xtK,DdM" H@$  H@ ݎST;fvH?xzߜ:zǾ@~lM[lE'.u~$  H@$ C`T .otwW^f+ȸ|< :o̝3DL1zӔSN^ծwaoxӫj^JW]uUfiҊ+8ƹ$  H`|Uf[h$5G~~%Y^y׿~{$  H@_}!xP_?EY$6_~_e/-+𼯵Zy(Ȁ 9#7~=W_}u{޴RK[I@$ @` ?y\;e7)M;8d>iyֱ͓KBUk]?Bz'-oI ,@mBƳ3Q 9p|U?CE30CuYC ^L3͔7 X_G^<,$0yw&Ō՟gyҢ.:h0؏y (#ċe~˲]wݕ{\,Lzꩋ=S>ƭޚ?Ԑ㴪PF+< Q(³4 ӟ4wZ1Kڼu*r 9y{$V8# o:ϚuP#M%%Y旼RDL}'ʓr`DgWDoP,+m8R8/kM)ZQ'9s9gˎvyr\%Bp/]' &wCeX":·C3k5y5Q_2Gϟ_'u#h_:|gay'NYh n4\$Ի~]{yqx#>O9up\V3<&"V\B300:Zξ0`)vM@b H J6D0p>j#fDuH#:N4p e05 䉎Lt錃3{s)?5Mo;Fe S\ʉ3'0C ' H@&0Q83VqxV\sX=೙ǃ+'`xATÃcil4Ӑgv!P<#oh0T@GtfߎU<κi>FTAC;`"()C=t2!9cv1x.1Gru#'"r,2Ɯ)C'?EE!/B˜&D(t # S53&ʲ4qtDD'fQgxlcYr,7"'EGEDM7C}u?񏳈:'Zo=GPeDfR0r O;2DP,$]G: #(By`v54p:¸oPftĆ.OΗ5S$  L4*xꌰr])>C fOl4aeVԹ?œկ%i!h73DcmcI}wO8Ӄ>qbU/wc/GgyGGx>!lK>` e>^ wܶ/Mk;Cxl׾\+ߊ{x;IL,}YBO H@$0/uLqIG]'ҋeofcNOy8 OntI97fgr7۵RL꬙c>tD( Z VC#E~ NF_,7 {f{Mr"a!fF8{2`\?CAn-,MǬ!c7Dc{i>`CxxyqUA?:⸞鐋ۗ/"#0|g8^Hpc-" ؏:ۍtu.L3e  fuk爲& H@$?}-Y,<;w*1=V^I;/E|p7Y⭎[ީg/~9IqAQ5(^Fl,|uz>oOvȳ T OG9A_?ߙqC*[zrax #۱',ǑW;0HP8nU55;ؾ1mnuuP+K"uWib?FedCgt0>N:C !:* H@&,ntD@ơ:C--d xxڱv3i5 gLM."!4Ÿqm7BIf4( !Ĭo*I0rƫhX3Zfr0L^u*$xE1^l3|uR7ؿr6&"Su0zœ^" 2dnx'CTqv?#4B^(BCE:w/: NêªjD@g2k$uKc>43Êr+-:lXƹّx;a>VC!F*[_6 eGG z1:8b>(' >~J@$ Lǻ S.X O=H,!Y~(!اYY7|2L'ƹ!3SꪼeH6CHkΗG@F,N*Q |U"^#)2DL~Iy9+}l[N|x~bձU6t@c\tP}gϪ&$  H@S&l&wC? fxX-OZ4LO46:ƒYFG>,V!z<q4 x,'g#aԬoǺ!9X{GjpF{d"y#^X"cHB_m~Ekc_^O༨̇;9QKȍ ת[}e_ʈqLHCx3!8eDBx˙Yt1rK馛}&xYf܁r$\󃍛vtftFDGf +(ٰPxro:G݋8$  H@B? #QwwuH.EJv;1Яx;!rH& !BX2Eo QPunD8s*0p&D j uf$ jf 3idžO!bAgAGb5:(RB3&]՜wJ\f}XOc==.o.$  H@N`=p<^>8giϡ)%/x'.["m$K/4]p\ #y<Ӗ@I $  H`Pp#+x]w5Ow^F=q':؁ ؈Nx7ax\ 0*A IM$  HKO,=s8.Nkj'H뉓2BU D<0vqڭI@$  H@㏀~I[9b5*wk%M0GxVs$  H@$09Lzb 1fg&c9ռ>{D̤$  H@$  E@&7$  H@$  -$  H@$ (F$  H@$ %[]$  H@$|[H$  H@$0cߣK@$  H@"o I@$  H@Ɩ~l{t H@$  H@@[mar# H@$  H@P-. H@$  H@h-Ln$ H@$  H@[ %  H@$  H@mPɍ$  H@$  H@cK@?=$  H@$  H- 0$  H@$  H`l (ǖG$  H@$  E@&7$  H@$  -$  H@$ (F$  H@$ %[]$  H@$|[H$  H@$0cߣK@$  H@"o I@$  H@Ɩ~l{t H@$  H@@[mar# H@$  H@Qꫯ{.ˉ$  H@$  H L1i)LO?}hو x>p8$  H@$034Өx_|,GGbTK˃I@$  H@&04Ӥ馛nTgJz H@$  H@+8gyQ9 ~zTNH@$  H@ƒ,2*W f" H@$  H@J@߯%yI@$  H@@_PUqz2$  H@$ЯZ$  H@$  |_'# H@$  H@J@߯%yI@$  H@@_PUqz2$  H@$ЯZ$  H@$  |_'# H@$  H@J@߯%yI@$  H@@_PUqz2$  H@$ЯZ$  H@$  |_'# H@$  H@J@߯%yI@$  H@@_PUqz2$  H@$ЯZ$  H@$  |_'# H@$  H@J@߯%yI@$  H@@_PUqz2$  H@$ЯZ$  H@$  |_'# H@$  H@J@߯%yI@$  H@@_PUqz2$  H@$ЯPO>d^Q8ZwviTSM5OrcjPg_ק)f$  H@W\t7G}4M9MozSz;ޑ/}#I6}<#E]0ot?{ɝs=wZl'?ɴzif㎉~tP6xwtgc (/}CJ[mUZr%d[^ve^iUVik^衇J[ou뮛>ύI/tQGc/8]{k_Wzֻ8:ì ~wN_rg93`up$  H@ @cx]vI?MugLgn햦nAN3YsE'|饗G?2g=q#Ն+r+j!Ҧnn֦ԧ>gat!`{vi2bc_״2яvUWM7tSuYӽ;`xEg۳>Lg^;N$  H`h~vxuu;Q{G"!yy/| .OwO3Oߟ?#k,/;vϟQ K.Pm]:C׏sa5 oxCZ;YlDt3oqcL1u6i]-~cuP~iEMs9ggy/O=\#N8!wRg*H^FY) H@%B'<Yoy[&K~ DA_6x%:80qV[m(-9::N8HxOX5 +w}o}+-re:87R𣑏+B ЙDX|0hw1ܙg9JMc-1Op$  H@㑀RA#4o喜!ft26!=ؾe}tꩧZs5''juO&:+Z2~6K/H.Bx|ۼ+oDD;!\gMou< &3x.& H@P(Bfo|٣*;W54acx_i,x8owoM|;c쩧J?^8G<3y=ƳK7?'x"[ҩFW^ye#7pCc[2BO80Vm$<v[fC&k`xdlʜehH٧;R64wIHYNF0nҫRҘ>H;ӳeYE#T!Tk7d<<۟r)x^{C,m$[lјQl/~͏992x-}MpEp#r23"?x;Ve7uY[~0::Fq?=`H'4itpoS$  g JȱpD0%YLXhqtU6/4""Y箻F|t0S{x<vX$'Bb29Fi#%[6/ LDnC-;t` <87I믿~xYeS.?`u]OtP_5>(3iNhB f1"nꆖFu x:!`D yahA9䟼/'uoS{՝Un$  H`PwP$QU*VFc=ydа,CC!=Wyl >ƸYxJ3prK^NDzSƋJDcՐ뼰 _?`q"+Cȉ@Һ-Svԍ3:a\ x:.igE"hTQ:0 wЏڱcA6\ϰ&oLuz c{A78)먻L$ ^$0U&pfI2fPU=ץǯCdUt FP8R#2+mfxef+y1MhuxgӨǘ.}N=tB]H 8aӄ# L)C-[ved~5P"ʺҪN+[ ^^_ DaRKLVFX >fG9! s7TӀUo۶{8,ONt/ N :.$  H (;(5V24TDu)R.g,-㪛F{[Y+?11s4,E!1_5ZLO0z471\!:K[" 4(m| mϤfXU/-;xr";ƌчcDE\E +\ VcrAgs8`Uk4rى/QF+_\C?܄[]tV\{Cx^q~G¹1'LX780>) H@@/PwPjP&"xc|w; n%Jǯf)^9.3ѡآ`+}VSH1τ#3=^U2}5lUVYgfgHc|@3Vh xAzxO|Um Y+ꩧ}c9.sh_: qg>xb0/i4T+J:ݮj~#Ct#>:UAc#'u{fY\#Cs#{op|9s(TBQw8pRבv$  H@H@ao6VC# 3go2cqgm<csKCtlUS 2 Gja(=J1ZzٶlX@gf C 0&(Cxb|BB19n3v>Dr^YCJ-*H"mVvfnG3(ZMVM]֕fu ~7x!:^R$5'۶(ߙIp0)C\y啵 #% yn5ۉ'xmUyuYVDTjŭ@zJkƋ!'۳"uDönC]$  |J25j AK}|78{Y#+o1yn1m{׆~щ! k'l;'̖O/A _ʱX蠃!o[v9//!QD T֕xy6˝xry晬T!^'\ %Xy}T)Î:]D}!މ*aKiezu߹7QQ::^ c=;OgZ+gGsO ފW5LXIy⚡P4T) L} 7$  H@ .O<2ULcp<@J0ҥ!`<-󠕀|sZ:Oxͪ]Q֎+]wuu}sY#<0# fml'y1'coqcg0at`;c2LE]z%FEvK3܉o(6~$  H@ㅀ%Xd'DOL3͔Ö]xp!Z3>ҤP>$G#x _SrqvEY)bG:O&o;#rFxcDk=xxK;!sOji#!Iw\N70γAP!)1\'G3iq\::U o;#6nQDcAi֕v q7&/xۏ]@ <s=y= 3ڥ:X1!u!FZ䁺ɤr;Qu\;Exo]z-k&aO(2d8]G`b=zBCG;6\7FFOѮ!In`?;5/`zʜ:yP 5$  H@I@?ѱ8ʞYd 6t$  H@O vRF㍀~$  H@m &>N'a6&oJ@$  )|`x+# H@$0M|ęAcf'6>lWLmQ? H@$  L RҞ$  H@$  4|O$  H@$ B@?QJ$  H@$ &3$  H@$0Q('JI{$  H@$=]|f^$  H@& D)iS$  H@zK@$  H@D!(%yJ@$  H@@OPty H@$  H@(=O H@$  H@i .>3/ H@$  H@~) H@$  H@=M@aI}@IDATg%  H@$  H`PO<%  H@$  H ({̼$  H@$  L RҞ$  H@$  4|O$  H@$ B@?QJ$  H@$ &3$  H@$0Q('JI{$  H@$z^?3W_MSL1E0$  H@$ @h]>gyb}b~u$ҋ/#q Ӕ$  H@$  6wꩧNO?da<>Q)F" H@$  H@M!?L38 x2K/ƹx H@$  H@@O9ѲuG$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  |_'! H@$  H@N@%I@$  H@@_PE1z$  H@$^ž$  H@$  QrK;Z{^{ 暫]#]uU?f_.$9W^y@?ӯƲUVY%9眍~$  H@$  FE{O/3Θ6x6ۤiCY6} _h~7-R[?9VX!wqӶnXv'[/$  H@$ X i%E}*;$  H@$0Dꪉ/=4묳G oxÀ5$  H@$ @`L_rn5\3/k,{.=裍>6~w ?|NrOs1ǀ $  H@$0q'/6d쑏O/xO|'  H@$  H`T K:}o{l_^_$  H@gIZ]v٥P' o}kcIcڿmo{キB -fa_y}fJL7 ;ll7*8uu WK|lgy",M6@#s5_&,ȫ {,=#{s^X[y|ꩧ5\|+)g=-2yj" H@$  H@}C` x3]VZ)uQ 'tR:S7s;t1Ǥ'x"jN;픿O;Cο5r,g=63/xW]xi}#yvmn&nFK/MvXz'sp~k]]5o&w}O?=:9+95G:([&/ΘSӟt_Eeԝ$  H@$  q+񖯲*ϴFw߽Nvio]ۯV_}/;sk*fɿ袋TSMwS#=teeceoDp V?)#|tAil3K@$  H@yVxٛsϴ.=_WҎ;X_x}Xc?f+ '>vmjfm-H{,\pzB&Pw6l6gO}_`Oihҏfy<pg/՟ ?tMWͪ%  H@$  q)[oupwxƫo!}(-oY g~_5{/4,guV7X|g4ڳ[?q1obX?X#^eX_v Dt>`l7cC9w&Ƴ5tUWC3<1@䃰z駉 $  H@$PG` <^39bRŃoۍ >U~x?яt~y1^nx&?>{kPl9^裏N/Nw;믿~z衇!) H@$  H`N6[zOxL\Ga8?akM)c>x膀\z啉fc,[n?g7駟> .HL{&Չ>O5BZӡLt|Ncuyҗ|<"'gvM$  H@D'0&]P3+<.ULl;&O/~'N=ԼPvuUdvC7|&+S~ !9~cUlfuy_z# `C"rrH@$  H@hƭGl2Z[&U_ ۩0|3y~%gY0n-"~cc6gy&jk馛6~7뮻6Ҩ#yx^z@s2$lr=y",2eՂ o}[]zWuU7|sb@\s솀'kD0Wʻ3{;߅1a:?~r!'?I^_'໙y9|>4L?;C ) H@$  H ; x㎗+V0+x/5fK,DG$ѹc x_:h`Rwjx 7ɯ+Zae]\w H@$  H@}K/<뮻l`0gf{lM6_|żptP}7놀g{7'袋WUo^!]w l |7,/ kUuLhWNp[DIh$  H@@_:]{U<>6lx?90&km_nxyep8r&a&X^kau!cLZAɡbv;"b{?%  H@$  *RKՖ]U3g?Y袋3O:s' U؟O&+; x g?r!}?qgO䳙U) nY^mQؼ[;V$  H@gyށrf8=ߌ#of x(B4n}1^Yǻ_~ح;֓Άn"M5T|}@{F:<.O7XEG a$  H@$ЏM<]zkg6 /t` /p"|1N;؎Pzf'}^Wc:wpܟ}#(w˯£S!o!|LVԈSO=uV&$  H@$  ;| O:ϻKkex<ݟg<WژP.o^əgEO?]yMnjZ x4tDs1(Lk 7to$  H@$   P>QL3M/76xK;M8c-ܒl&Y=N?yykk[nrU{.ϮwOw#-y{.첼b-~Ԧ ǿ/OϳG|'cXZ.$  H@$ &0* vXJO?}o3 ՙ05gn믟gFwuWzG2Αyo!7$  H@$=^#}:V_}<ƞo5;HŴ%  H@$  Lt ^Z? /ao" H@$  H@G@?z͑.xy_5O>m_⮱/$  H@$0#x|_N뭷^W> 3$&c\sΙxīx]c=Yf%) H@$  H@L@?p_~ymžW.vM$  H@Ǝ~؏ɑy\;tw6<̓z릩v$  H@$ !(z}::]u剘) H@$  H@~( H@$  H@O@eH@$  H@ )J@$  H@@P~z$  H@$0('@!{$  H@$_$  H@$  L PȞ$  H@$  >|g  H@$  H@~( H@$  H@O@eH@$  H@ )J@$  H@@P~z$  H@$0('@!{$  H@$_$  H@$  L PȞ$  H@$  >|g  H@$  H@~( H@$  H@O@eH@$  H@ )J@$  H@@P~z$  H@$0('@!{$  H@$_$  H@$  LBN30Cb)&rOQ$  H@$0tc"?;#M{ozgҔSN7E]4-iKSO=dgtI'|0 |+O?78-C㙀t:*ްz56_8{uM+Bc_Z/lXO#8"/?G~4s}s#|Igw|Z+cQ8~t7#if/RK 7ܐ}`8㆞{HG {7s9'gu,`m^`hǖ xַ`6쳧6,})|;<W _dvcI^ȇn^ʊe?|zQ6^<~p[c5Ҷn8,zhO}*暍u|iua?~6/?+ک]Za?77:rZ0t O;Cسz̉nΨvm<=Wy'/<޷׿S#G,Q_Pm;^sLs:9"bYfI믿~W}8uNgbhk^ .A)Qv[:y}ߟ<Fc.K?OOonʫ7SC+x9:}،;:Ҍ`؃/ꏖfd_n]O{,/JϺ :*cYAig:ޣw;dMG>B0^:k6>ؤ/MsδRK=g6l4æU-(RsOd( ˠL3P@1^yb0c10ЬՆ oM|lK^rx&K\pe3P9-Ju#ot.""?1|_.yH;>ZLNrŵsD+j&3fijwkGp;r_/,uVáؗm3xV~8Evx6w}w?'_by9f\5o_)c2"|_ͫxf3P+qNFs(<˺$vtrױixH$q^eUҎ;,ɖ!y|Gc.@c.zaT}o铟do{NN{yMuY:V Ue=˃I:BcuOC""8x:x5YyF}'aD0qc11^~iQ x^OzgcpO#%[lx\xu5y^x=|+\!nIΆ³~6l=ئ2x3 ':#6G#Ær}uCH\StP$U?Z`Q317YD0Rѩx8>@b?uc K?kg&ڸeM'VmI?m*6vqiRvm$? >Pg=7p3*g,#oXly}J<[ y1F=/gvNG xz|W^3kuC)/je-D:xei^¦r! %/)^Ή:چCPi/=ӉO<q.h<\ݹܺ`;?u:NkzTZM@' · /0ϛ:K1\a- [#9WDs\s\7eS)~>{g^Y0u&Ͻ7lӮstq/': ϨG; ڢi3wta3gmX7GZO(!Eq7u .U>IvOviFʰ]XN1y.Eά@)<ڜ\_u6g8)sS)hG⨋6uuJSy=lZ;i5^V*Qڤ+n*?ZJyAV|Y V/})7*bx`?!y晹מ^#GC1` )Qe(}nL&ģ l2ip YTn x1>6lYo e>n卲S8l;O BbX7:)BnqõiB=Ú{HU{GMUS.s78vh>JÇy 'z뭳)t̼ؤ6u)0:a3tdz,sl /u7zJyAV<"DFY=>"MF0{β0Ôz@Ǫ r24v$|e>e'ou͞I5彶NPZ;W7d\w;gjcq!~a<ϹQÈfB7o8b(Uglu{USyNN=:C'iV:׻ޕ;j36 )5}|fTDkҡ ~jf}btbEGxSסGz"j3,' },/.]#* '&<aPIΩd)K < `PM0C6_gAA\iq+,L+½6͘r [xyX `Y /lxW Tg%¾W O 86zjS+pD it5\?8FG[\ hDP|>ﵯnd,"2X?~T[zy@FFEV]mo&dz:2?GpfٖN{Q e<&Gtqd[tOu+*y7=3d~lmmy iۺ 'l}BV ׸FzMQƐ6!({,?lj c' b?zb[' Q00M{Pe('h8uC6n4-i粘fkh|=CX*5<m|GDq5EEd.>Z=sLmޯ;бJ_!*QM%dA&k백8Æ}  V >e$F߷# Kk!"4(FIbVe-|d8ucB*Qۜ:j{O*'S3k˅0Hrћa: Efq>phҩ m锿ٱD:#9xFi!e^r[=i?ɶwOuw8e=M~iNDC 5r4mX .Q\mL[;=Q|/ڰXЋFY"[<". gp#V'+w<{I? 1"0f>Sq]ms xxǚUFyհ4AB AsQGէ!L6o ֔XP7^( VF#z WFЙF]aJNIub uOg>'88Jc.?kKpPI9}iXy;bƭ K۽ϫm(k|p\tBi:A9:h0ο9*MF +=\T[ޔ ۧ_Cw2!.mmqΟmkWd)$_8E?&<Ɛ. Nh0Sh'峏G~yx1 Aǽw&<_ssy㤽T}.v>g:=/V[}"pv|e_]27ŵx < >N)&D;~qM g8'E}9> x.|P0~BAP05/ޑo,CDaja2?mH6>y#]4[9U;yܹ#׽5s݄1O9?y!oxv /$p"U F:guZhb1RZ: V4Q'h6_uBfU~amo調]y<=0`sxlQ9v摞qh8yJ9XٱRc)v"#Z v x=dmm6EDz䅺F|J1 +[w<L綶IsT.KWnD[/ްmNKS!Q u geQ:_׽+,@8=ya^DDRPF9*εq\/b%jN\,kqB"MFUFŢ}Xq L-!_pŽ +9R'$HϾuK[&m֖8nS'#N{E9̼T޹̳UNzP';?ƳqMgpA1(0_6yk|b|V)b%,_ui9fIzhx'5 IDATW8ʩ>{=?I{)TM|sylЯ _+Ơ N'E,%@'F{Gs-)gQ dl+y//%qtpYGH 2`K†xio:L42X!KAoMƹ4c}:9/Ms ClбAx`2Bk 2isr:Ǎy] `qzyA>q g.DgzF=gU63/v}C/~w[;iBqiԩ&C#x6b xz=lܶU'~9ZݨM:<;,mvt.l'2I^nq\GنJJ{@)"tXRe1ʋ_x22GQ|gU,5Fs,7((bQ8~3z|܃:M> x#ynjnl !tٱ1}BF{&qM GXMn|0"It&ɟN,:³ZbҘdܾ/,~tqnC{܄͝@)P]|#{!PvD}p!Hyh kYrl0;`hu9szMH`˵em,SZ8G~R_X/v^tGmsrT7jp\N>GzD;08lԺ^B65: LQopLRfL%{Q8FGvt0ƈrex+H!7#w9+l-4>[wHma2iv q9~C #Eym7`t_}9bS~y8kj $1?hF l252.|;As]\2e~Oii}=4➥]o8>s{9ژ|y .Ym;u) Жir9Gݡ`-{m{@o) H@$  H@{S_΍nte='$  H@$  L~^zig?_z뭫ۮ}ݧ2uvu|k~6|է>zZm?^`6;ն YgUs1a|3wq$_$  H@&YY]vunwxE6ڿN?ë:} 'P|`<՟j->-7ʗO|!R׾c=FIc$  H@$  X > ƫ& ..: 7ܰb{M6l͇(į,oZwu{o'$  H`uLw_&7p8k$  6}s./ԛvmu{]/ϯW/V+?^~_u[r1ԧ_o~}黟I&wէ?zc؊ cL _=qP$  H`(GG,G<bXzq[ ?nK)WsN=%r~s\“k!0m#HG?k*1s;ݩ?5OOsI{ֵU3{ 567AHgM7]cQ8—&RKY5?)鶕 eG}!/\k%6yaʜs< Ssͱ?̙fیD:."?4;M,=:>kDs_җ-آzЕ5I@$#x\"v$KW3jtOR%s9 [Zw [ֱQw*" jtNYxGED@fϢr̷řg<" ڎ8ZX!p| ?:wcS#>B0۰g#V!dxz7FQ'5 g"9~+_~zЃz>׷N;TOK'3 1>Suk 0D 1̸Nzn!:Xq uF9[_3Ϭ*9 ʟ2zPm\;IhZ`XOzғV^3eu7N+859"O,~VۘO9PO6g~wm6;*7jZKu˽}vaή)vc_Оm^Q40 F;9KM<j6yL ,>sχϾgqF<$  H@] ʦc{XI ?Ӟ5`uGֿΆQC,~Bq,'3PFd<7w=`C0&U*Q A|S.A&Mkub 8XQ8g1G!3#?y*b# BX8\#wZ ޸&uEqywY?Tu툷,"gKz5c#V; ξSF؟}Yԕ_5?10b!4/Q`c^0c1ctXUb 'Έb߳XI?"eNbD:[IPOp`!/mVN ¬,(w=,}iFT9Xq/.Q%;,w]+~ϵAs/S_g0Rﳐc=*NYs9ʹ9/u& vmWGIA{ OxB\; a t.M$  H@C@?BYdO(Ig:a1etvDtgE8A(٣*̫Dt7eOGTvͣ/yKUs:mu#)d]pՙ bc[̝ 5B~2ypr0 #pPFytc™k]E!x9gX䉴<:Qvwo}[{jF3 A?G,@8?!yHwdKDrDe;u A`-0x,`B]!aC#1yE)!fEobE~z}G}%# dE9Q*e38r["]"*8LЖ 4"⩯DD&#yp~qΔO8SGh3 'rJnk82G|SG;C{DűyD~ؗ-A\sL܋\'M$ %lt8!D0LR#eqɨ6 Pi׃dYM}mqsDH,ӍQ*Ÿn 13񈤛uGnkf b|}>B[qF)&ξ,#ʏ4q~ bǏ[q8ġR&DI` cMʚ{yhkVߏt|׻UG܍ QRmLDbuDp]A A$E@$  H`(G(,~%GDAt0#|t򘷎}l䋑C:!aFLean9'DNuّ%M.1{y?tۨ.sSY ˣ̣Fc]9ˣ#Р -D@a|S º=ILO"lil/-#Kqa䃈 -·x,~}ϣ}9cchh8i`@&ˣ18D? ^32r=è,ï -uXSE. &Y8~y|wPq/2Ҏ@dg;ލgZij;\oX5iڢp #YفciD2`[߷Ll$  0IzXJ.PF١R1…el\χXcy:E2F'm>FXP,"|fwS #P!ێQ{F'.&sB1F-V)eTw:c[Lhc]1Jyj!r>m`3I8b617z"e7rj q,_t™sDS4,BaCdǽf8042Oy*mM&7" OǃÀX'ʸt5E= :Oۚ&G"zفC4PȍxLgXLǠsTN>~B:c}#"x?%  H@XZ xOBj!m'm#T!Lfϗ!xFG6ez$8/&8&_#tF੟0l|I]3l@eiG b,~ Hv?֕f>߈ ` YƼsg )`' 9pxIeD/czC㳭`iO H@f~2go7aN8!u x!lo~8 !n_#_/N Vሊ4&>7"0"ġ@$#2b߈r^:5/0{,0kW(1 A;Φ~ģJ- 'NH,惗ɯmɽS!lK XÉB>8k7K]3eoNhkʈ>cϱ3G'B^ۚõetM$ !$<N~v-0^PCT1tcO2m9a,0җ4#{6wz^)g'^D0GqD-|HՖcui =.bBDa4Bn0dNjQyFY7$Ftn!P8'a0PvʗE"xX/>V_>0JcOd΁r44u0^Z=,Ӗ7CTqޘ˝aȄ>|y.l 9,q*hW_5<618?;<8?NFn(F]@"^?3a(̼|RD`iÈ5{IԥHc=|DZ?ʜO$^UB|21f7#al',1@M4!X~ 0"aդ|3b1^ a*8b:n#tSLQ'ASLwJ!i?"Z-T;Bc.B7yqrPa`QD AC'L,DƜnN+>sY|Q8 ?ٸNFJ'0LBq%);lj娆8G' +d߱9)x *ޠQ1~]F@~H<.,摏ZIN\#F9yőB~{^ܒ<0]OJ#φS vib& H@PP&VdӕfI Ͼy䑫EC9bn3҆"<!' ǍIO[G5 {Br\GB,^|-̲,r682]4j\#7Fb9:6 DIO6‘Q~ +?GXfٳN;-FDاmN~D*(A!# 9`}bnh%Sdt7"! \ˁ3);5FQ^,R CRWqӔ }5`G8zeS$DPWBEDM_0?Q).}By!&I7?]'0Gvv&Ϥq/L#y-nQ#6+OwBkS3ִ^H}#ra˔V'0?{ABV@ k"SM) H@lP@y ٌyEԌjtHI^ UF%D5:"h7{>q݌ez2!5kkSM'{9d#ϱO"eAfp8``M9} r?)|l=B܇f0J!3)ߨsUaD%ΔRv߶-&'f>EpoV܀1my%]eJUa2gm& H@|PG93,#tfB]$f L#i6;񓚠ZDTO>h՟%  H@1 +3:XeFTB]GO#%MaLa`t cGB5 ,D,}D]$  H@/S=F$V_{ ޘ?[ܙkGZMֶYӾ& H@$ &S̿6efR1w0me L:Ȩ'i&VgQH\qg3Hba$  H@!05B<.(< 8=V@ھǚ7 H@$  t礈xVeUeOg2F,>|$  H@$  H`*৑aӔ$  H@$  D X^$  H@$  $  H@$ H@Kk$  H@$ #"3$  H@$ (Wb{$  H@$0wsWdfX$  H@V"J,uY$  H@掀~ K@$  H@J$_5K@$  H@P]a H@$  H@X+Խf H@$  H@; +23, H@$  H@+~%, H@$  H@sG@?wEf%  H@$  H`%PR%  H@$  H`(̰$  H@$  D X^$  H@$  $  H@$ H@Kk$  H@$ #"3$  H@$ (Wb{$  H@$0wsWdfX$  H@V"J,uY$  H@掀~ K@$  H@J$_5K@$  H@P]a H@$  H@X+Խf H@$  H@; +23, H@$  H@+~%, H@$  H@sG`U.9EՕ;jՅSFz{Z{-z.$  H@$  ,5 xߏީ_kZk <[=$  H@$  L???ĴߘPOn$  H@$ y$05͖|= pt^$  H@$  H` LM-+dIUWU\rI/y=$  H@$   1gu1T_ת /˫\:V[mU~Wm&cc=\o<裫 6ؠy9|gug7^ATO6lë3$  H@F'YgU|E]Ԛº[/w}Fp衇VC8j7%D_]ve]zjvXL{i"V;bDӳW(/D`ժUu{%1m4TL^ʔ|_~W[lEnM,&+ FxfwK/D4<#niʾk\cN|1~ 64"WsN6,K/?{]f7kC9s$.lZ>+tWVߨmݶGܝMnrO%pj7K`I\pՙgY;'Zwi<#ɝ|u)wi?H<@IDAT vSy92R"O(JP6Ӝy߷ɐB(C $!syw}{s3|{5]Z{F( noqKuH4J͒cߙң7\8s~,f{6뮴椞go}[[wߝ㬲*i櫌íiy O}*?ͿO>K'/I_}z򓟜]w|TsseK/4|iKzxgɯ*Uv|o?\y啉qƘ_ywF`H8_'=)QCR-W%X#fj ow!0o}g3#015:x{t;LOgk-0=y}3i?yZaFEOL6l~vT`y?!>(򖷤]wuw=PG}tO{һ馛K^ oxCzߜ8o~3zӗ>M %x'9眣ףWUxG:|HSZ|swy 3&âӟ+^17*uBItg&C{Te0F` 25nO}s@\@`k Olnt '$Yd wYNe)-wiAYIs\bڄ[1cF:sXE*_Xİ^uU]vl)CR Z ox_fe-ܒfΜ9ށr[og4l[-sϝA,>, #:YOXʂp}g\s͕h'mCx"bVZQ)K,D\ ?>Т.wҾx /SN /xAV{I[5)[w sꊕOICK VZ)W#C4:) r~x -^ ?<%=qhvJ6/' TiK¤n1MWoqư1v# aΨ4}"]p]i*kyeR3a+(*C~ ۶e$9ɂ!ϣx6l@"% qL{,^LAlZ6QEg"[l1c9&כ¨>pc1” ]r'}CN@]hƈO}+_e[c ֹ;+LzF뮻.{EW|=3 B$> 0mk;ڽ & e:*'{TzNQyUe"ݽ;cpEϙM6$S 1P0Jj[FLW_=KRG̯Q 5M(-6\Xg1:w=bna\D#֜2?}> YS5\3g03`,vaTU\% x0GB+\|zB;!dGbDu/{*>^WDz9sKe"KjH|дoFM_V4bY+L^xO`pg=k>ֶO*+F >!1rmF #ʏz1DMTضxUWat]``3/`[e2OL("gӿK\ 1.)HML _w:?GbZ>/[fGfs1dL?ϊw,\#n%Ux'A π_ӻψu]E-3y_N"L0LJn_V _Bfm}5]SB N x4`ҀtJj*3yaae$-WLܻDq8g7)o,x1#d 7!cz&Ψ'^ KM\;찜L s<>,"PmF( P п .@N#^{#0#8+yHa^ ]D`h{8V2A~`†#i7UL4}9²uq ㎐>` 6bL](*)}qg N%>s }K u>ؤE0"Nq4g|]vQ`c LGDC~ۤ\v\Ɣ*QFҠo6y}(%w!g^(ȗNs(eq[ȟ{~^7:7(*-sQoZ /JmHl#/Ì])~H5yMv>hӷȠmQigp7i3ڂ9+K? E0" }m̉TWIQV0`gKiF0o!| eL<ҚI.A*.?ΖGا?W2OuGآ1k K&kmVEш!bL"cɆ/Ї43Ug((# ^yOD>_"?MX7}PP^J!tQ&g|ߝJe~xXg?YN{Ϲկ~>pgEh)a`6% ' UTgzݶ? ^w}h0xs#DPŀEJa?]Nq˸[+ B,"K.&BIߦlL)Cuਨ)&=H[AŏEkeaHu!b_* 5m.:3pۏ:4r|Ń9Pnfr=**xnH j~NH+'P,2*/+J_s6c4Pk L*y1o(2nмCJy"+aUfSQH`TyXvOX"]#DA|S^^-l c-w:$&̙7P0dAؖR|4Y˭g#̂-u"}oq\e=͡}2G\+;HUkQҿc,Kx٥=vy.}+BmemmKm橱#T%wq^Lb2-iqNJ6}Hq' QI'xfѦm"y%6uz!0z\| Fi *YxvSҎ]c2ƾoUʊUU`UX=\y3 RW^q1FQ[;/#T)X ]N":̽. ƑRCe-7eqga|x0Q]o(Ȑ.0ʔ &f2 ԡ,,e~WqXq$Lx^H{`AIb,&Vi>bA-4qܤx+7 $>H~bF9k5s$KA2 BBb$).eT߆GEMh4wrX/)4ی( 82 KigVB8Faxa.!DB=aNjF@CXPcuu@fE%?;kGE/Ǐ~QCRMVc*c68O6Q*nɊdF M5 [!lGD(fPUͺ*c>l[xF,`GuT~sq\ xq*7׀J+H/Hr K]!sb{pOiJNY?e`EM)hISyqŢ`y!|CJW`%k, ~ߝofir<,Uq`g+}`m(jkZ$pA -;k1=4Շ=`V\K_&&eW4BNoZ6 Ae>w(V7S,Q_uS'r@易 zpkVzQ^P pbJi'``,^裮x@SDʤ(y+6TW*`/ #]ˈ tMhNO{8$-w}wI(qb;y2 Y 10ʔ7R/B~ԕ.`x_Kv8D(g kOMǓ0c %5˕|?"]jڷˍ0BKIw蟌 Z'{9 +?eg ^kD O?E8DBa G¢W]CikuQ0=bl Sp5HK?"> ʣ*+ހqX%DSʏ7#jg"7|RP ȶRQ# v9QL_&,{OR{]oJ/7~DNXR~\51^ս[D eU=늧 Nr`.5F+;y׼U TlM-Zߙ'RF,(䘧!a,#Qƒ;@Au^PtYcb;FbغqE"kV&  w.`LUQX_0(mL }k*w/D YY s!xPh~Xo,}" kk՗zĥNu`^EbW]ۣ*.hJSj3OW~D< q#He,GCgR>S\ʬ0<_Gi~Kw8OJ<&gLT x1ͽS[.Ms-¨L2\" \+ LEI D\@C}kڿ2(86g@w8!+ҏA_GMR)RŢO|"?&=YYܰf4˴~kUΪ~+R,.e QxVn!DزraxW61B+U ENGuN߲"ju/ŋ%Q`~DW*171XMY4Ѵ 7h'sJiLîyՋ_2I8 l(c 42RoIҗHA0oʭr/f`a~qQsKR_**cnQFE%de)=.7eb|AUKeZ ( PL1R'~(G 8!/;v/% u{rU~EEk˒Ye>P[]WXʀ Cp'"O֥Odܰ6SV&o@QǸCĸgEU.־*Qv".xZ ;?Fux`~P6kdfyz)m'0:s OpU>?x⟰GbXO}&\ #犎 JBbң7<~7R FĚ4dX>!$|L#PG?{ ,pVE}+_ɯ8˭*,Ϙ@X!+y5ҭR<`E5 կ~5[(z{ߛ]xw؈5C'χ<~c%A8i^~ur^湘>/-B<G+rU]a)p1lbO!u i1&х臋DK`U&_1} U{cxOy{TO$N|D*YL8[FW_n܂2^e#Or_UcTBB(sGT"V`DAg0|F<؎XPA˜ )efU2R&0Q"\|q,Ia8j\mj^7 I!~Jz{}kg)c]g1檽/XpfKLGC0?[/egGO$4XmYM 4>p>c}ДaOOop0y\N,4Lt#mF$&U]EcAu)g?mޏ FMU=JWq'E EF;?B aOkUP8ЯahQRqW`'m4W2'1,Qu54zY0H.ĄUSVӜE7%Dnxܱmy4]e"\[TTF˭Uy戺qTڦr㡋'2Mti]^ 0uVOQr#b=Ci  xG\Sz[M7« t>-=s<5} 쵏]K]\t< o? SID'~,nA ߋ.}240X2Рd1 eBd1CPfpBu< 0A"T%sjOȑ'-]wʎ;B:` [Q{7d\fcUE'o^ƒC, C!<d:Ì`Չ=Feyi eR-0(sA'HaQ%LmPT@Kh,DXDN['tmݳ8!|ƈE@,d`N@ɜHO[л L08Kbcċ1D[AqM)xV P(LtxXN%5-2' }aFxĆyl)tdq*GVk?R %K&-YQ~Q"km,kb2+Yx>ݦcTgT̽kP0}@xÈȼ`?}>&B1ϘG)+}/bAEɇQ_,}̵R2oS3'+FbH˻(*7%w3zuT1'z *W13jδB8Ac}6I|@~Mצ.7)Vec>A#hox) ӴO*R2Z#"(*_XC$ぅWī85]$>*e<<5~$fDQ0Gi^,U!1Oǃ jgrHQu>иJg) 9/*GI( %xɫXwl}*,9&Y /7s&[M Ui Q ~헅zÆy$G8Ԯ1A"aT Jt4xro&$b4Ptc…YQ9hQ ,VX^'KȢ?(Զlđ,*,hl⮥6u HxVy*(~~Y#0O-Nݵ nĕ =@{36ԷK {}o)q)aa([$YWBۼfyK!RtY+e|P'"#N6e_9[;c%3)B,s#2((3~CJKmYj '1#JHB <ŶH}{a)ƭD"}q2Tn*ߺRDbde>uA82<k=fMqN_sk>hӷ_ʭSK1G 5 C2N>zWS+y*_PHk`a-c^MkD1&X~xq#TuFH ex .٥#+ _*PH;A?)]xBeY>C:B23#߈8zϵ%G`x Br|JA M& }̄AˎBòC%wF-0 ӅF$&51Fbc?$֐*7i¢Pl0XfX8^~x|W}"tq{ayIz΢&TS^j?bьB'biĚ-b`q#ݍ#! ) $ .` Q&+qnt游e$ HyM3g4Ũc/"n-[[lB kI/}wsOxkK.$MZ~3#`0F#` 9- /Ӟ,!.KvZ^۰4"XTER~^fm6pC7M{Aq_^:@Dvm?aT9%U'#qHy$tMFm'YZ^WC)?9{7kfr-G}Jg}vBȌL\s_%`׼&6h=pNZHXbi_*oGm<ze x&jswg+ 7ܐ~`|G~G,>(cڶ'mv'ge.0F#`0CsNpV,X](!!D#*XZ"\"3-m;N?X@Qk?^CQ%n \`b|_m{Ŷ+)B7ڶ< Uy0F#`0 :8 z-eFm=XmJ_}_hYY{xxw1^XipS*W9"~}mS  )UF#`0F ٣ai} B)\sM:SdqpgFEc9"{ݜWp]"So>=Fq=GMHG~;!PXo}E;Qt\nr;~/Z{I93>xj'CL(-wyYIߴ?\meQ2Qf'|bBY@[ʾv pVɷ33f4? Re#`0F@x ARP#Bd4 =79THYJ8yG!K+>e3]P^ߧH`DY8k]{R.|r{ni 7llE@{j _*82]9%jNї z$||:+M:]AQF V-p `/BQ@xv%%Pi/iﯽڹ۴'Q&|°0F#`0  ;w]'Ҍ:;x*/^<"<(+V]Ly8+!dFXSiiVo Dt]S`XKm{":6Ԧ X)+ڂѦ<k0F#`D` K=L(TS{ja2#`0F{:@IDAT#08,[:&5Tt㇪0F#`0X'=^~N~7#`0F#`@J ?az*L0F#`0FBd!|0F#`0F@| #`0F#`d!`~wF#`0F#`Z `Xj0F#`0F`?Y;_#`0F#`0-,5F#`0F#0YX,䝯0F#`0FXo#`0F#`,,O#`0F#`@ ,A0F#`0FL' yk0F#`0F[F#`0F#`&  𓅼5F#`0F# -rP#`0F#`0B#`0F#`h`90F#`0FBd!|0F#`0F@| #`0F#`d!`~wF#`0F#`Z `Xj0F#`0F`?Y;_#`0F#`0-,5F#`0F#0YX,䝯0F#`0FXo#`0F#`,,O#`0F#`@ ,A0F#`0FL' yk0F#`0F[F#`0F#`&  𓅼5F#`0F# -rP#`0F#`0B#`0F#`h`90F#`0FB`̙3ӽޛy iN;oZtE&#`0F#`0Äx;#=c\s͕Zj) 33F#`0FA#00N=Р_ϟ_|w~h0F#`0F`:"00np^Z?}5F#`0F#0뭷N*8.̍0F#`0F';ym9FmwMLoy[r[/}%ηtggx̂#`0F#`}g̘cn =гtAh/|c' /C2g0F#`XvA?X8Zkա3\n:뤕VZizV¥6F#`[p׿_>sX]]#  p% x2'XiN௺ꪄ2,vuiypi%'?9/k;zꫯNkfz뭧tY]8#P?tYgX 򕯬 V#rlIp +@`,`z԰});$!ä_җNmGqD}_:#9 ZyP|F/{챉ۘ!Ԃ^\r%fWN-\x >c:~uQ{Ioy Ӝx̝%rHCh ӧ=ii*BD!еLT&#N:)]{O}vӶ倗Óe/{Y6ȴF`ߡ!2jiy晭d7tSv+|͗M74-"*_J^zi|W]u<sCeǪMpW뮻.3LмΛHyt5?+3fg<uYsx衇&(*Ҿ袋2^(ZjNi8~a84sGXfn~^8㌜Ǎ78L7Fs]c?exV6O}{> +\ߌ+?Y ;.3Czի&;1׼Vs#(_}ksiC`s ҬkrH??uA?%\2ÉG`AN|wxhw/K\wuV[m/9 |xXzK,-cя~-h?(lR(zNg!{e7 /xABX) sNz^] wwΏ77 z=~wC|f+!p4~0]wqGB=#\|.õ _N?O+{s&|Q,袳Ãn-O7*eVhJ#[H8}acuE!D»7 mA`Aԓszdخ⊳z+"+yBP[n%. .ҌWmNY JMڭmcM'cԛw/X::3ƨ3B}pe8fbYBo!ډQ}UDJ\/4ӴoMn]' r,)]>O#-A S^3e߱s|AT͝J;^s[pm&II{5B|Abc.kHU;110 ,CD=IO)OyJV #<~O} * F=N'^R&ҫ**oB׶‘:RW0b _6{nz0O `CW`"g$\/Yɳ>#hg=bE펅ta2?ðsq'g Bx._Wr9:g?;/>҃_>|Tvw+}_Z]v%^,!p 1,dԕfTs"0 _qa(DRSşh_Sx[.(W xE^y4"{&ƶm}qÍENn~^a>Z0(k}0(V8(-B |҇a,1W]vl^00]XhÒ8BEb Wa\0:*9ﻔ[}22ϩ`jE#cMhs=wLoNFx[}بp1sXJ8 ?W6X?Ŵ͸'6}mQFP`qqahaKb<{ Rx9SNsl̗n:wU}+UJim{Pz3.,M -֡ՋEKb[}v/}e#b,5s}a>Ry b5H(HQްK9'W)+Z {cR+.m &Ak?=+ovbb_FѪێYΙg5DM³6 \4#0+gţѯ(7Pj#c~Ff`W(}C?Y8;œ .&130E뭷^Ƙ,0@}aVuWfƈE ːt<5qօeF UP‰FbE@B@aA0GbًSs !>@|6-mi 0$0`'h XO9Fn.C(c=9pm6@!\$Ǝa(ao7uҿ"b1#p\yCu0X`f#~eS9`PڈG8A!GJKGyRnI1c29uFX#~D׿2(G^P׏T?}~BQeG#̣"y7fi"lvT#Wss1!({?J4oC0!\9G=ؙ(?[lqPoD@}#Ae(q: D/ҫ{(<|J8?㈶#x!42C`9tՈR.b7&ʋ@x^җP402ߕDpW8?@1L(Wʿ2Op@ڂ>a)uwcNgߴ $P2w jSx:Mݨ倿G@OevmZؤ+_,Nz0O `CWHyvȥ)7~n"cmჾoetY<ַה'zH%%P"*W  TNG`F I.B"b…  R|~=,0b\WQZDbaI#!GLW Pwmƅa= PXQ"DsȂ #B%fy6ц`auB8Ga"-păy#^Hۢ̀J]|`<)##ZiSqDKDPI !e-P"Rǿ6} }?m0/A2`O{FN^B8ʀM8bIBh1_31ڎ6}k;``@; _Ѻ\NG"8DX ^/Wh<͓/$˜|hQpUdR/ͮϔډvdnb;1晫!B=Y3IXD'R?>ˠw\Q?;BT{ʇ0!^v[/q!^RΈ#c~].r exy=1NP8Ѿ"t'A׺҄]bx2˯tԦDCO?<@5WF0T}n A8Sߡ#X{<-$2EeDG ++$1-u>gjQ౰}ӫٮ0-haYd0JP}lU`;ʲÁN vQC@`AF] mXS5lCy#tj$Ϣ4bK~H,b 66(h~azadh70D Ef!~ZLy>%ۍxť7VZa#Bxo(K%e auW}ymt3鵥r~ G<^+t#8\9 ķ}i^˂C"вf(GwqumVvJ=KpWBg,0JźW5w9Z!0^_]ۃ5Gn,j%[GfTW3]b;%c57 ǚ(5[czI+IMQbWGc-p$E7q֨+OL/Zy~UWx14lD.277iT+]n# 8B5ҫ"2}׸jѥM*,!+}!$*NF?vBQ0a}H&4? VO𡯐_Pe -(hMI5)7i~ &%I9{ aޫpdΠѧQX*@-i#B ^5˸oڷc,wܲq5 !/$B!Ϫip?s17V7\uKʠNC%a™ΔJk{yuxΚ!`|@a R>O}Tw aKĥ5eAJw7uĺC'X`~ e}l"]Q+D4z3h:fتS}rKQ\eX`&fM37ha9Ed[.zFg!׿=dN1锉`e>,<1Y˸U.Q+\p-|y}T7uV&Ѩ-/,zV0 i1EɻQXްAULcdJ qb;A #I (º B1C/$`p/BZSÒPGr)=Mzx=0:wrGaLe9Tz\Q~-Uګ*$*ʂg dJWgq7[]1ʛ2? o^ ^x0IPJD^xynDxҎ ^EW]ۣ*_Eu]; R (&[qT_(YJRqѻxad^*&7lWױ4DeP\pQa|]/%K? O(*sClSxUxt;J}Fd@]njy$'8M'<):F,whUT@Dˍ&=Er{0,n # &ZIbIX6^y$aZ5jG ^L.'hqURXX`Ы[J;*j) pD,y]y:kˆ@m eiOK+JbAǂzsHN'm%.0X@rO< 3ܴ (]}0?ď Qe^ ,cks"_rU,a-lv!k'Ue*CWYR@Kr92ɷ_ڎ*w2R!zⸯPx9@is&@@'x>Ek潪e{ı38H Gܪ~+ͮ:Wj'Y=v N`-f`N!,Tӈa9?)9_u+R[/WsypAX:F[e>*cTalQ/XPB`g`蓾_xMghJ1#c0&}PcF1īHyj,whUd8 d{߫Wy?>k[X<!+xT}ӟίWO|b4hY, ;? 1J,b]qkJX ]ėu*m:Guӻ~W\a{o`ÌF+r[L^{bV !Xʛ6BPOx Yu,ȂĪ|<CC2.1|!1b` PDVVuZ{ A:Ux `|c;WɷK~[g¯Ⱥ:#錂*EOX},_]ڎ*w%y$~2NA J©UY9g dduniQי^ivm"m!c7![0z7 XBQZ xI"gaueS8 zѝ-(iMac)' 4}U2 喲2V^Uy˸}wen MiT0+/]5DZ1#cat #:Ajd,whY(`vJU.O0ةCXo6'.L84\No|c#>fVL|M3L ㊾ ӄdI:V#mx'@ueHF9$,JF)mY@xĴ5n~5” 7*Xx0b`#VvTUgĭ(R(ko38?3JUh5wvjsOẺ[{IG]IR׿*Z=V.V33JCc5lX0KR<*1!-ZEK9] 1_ uʿ6*$ⶠr u+M}8funPF3XP <]LUp*O]8eb?n#P<_x zD V;d6m\z:Ϟ{  *n 9q7{3sy,` `Ȣ\.>ֆ@Gs <(\靮X+W;'`pHw

3+L,`RB53L'HaJP6h%, Y9Gm \jScTRna2KOڄq҅v9cyKXҤ1i.e\i3-նcj+Jyx!0b`d@]JPẇ:}suX#Pp2b}8 M%LUPhH}1C߄"_HkڋgMѠ|}&#``n"x bw49AO1WN*xX99yVe=0]xßI"+ߖX~C‚ FuG-ڍPnd徧pA[E5wR?s6">1W Ӧ=9L~D)*RX;`^úÚ˼ 9Q\L#,GA $^Ej^(TL/k[($KfOPd*S%q'>k? _<چb29ygE׹IJ_ڎ,Ц3(0XeJ>1ff1^!>b8,0d0PL0O }6`Kp w`׍SUz6ڔQJ<ƒ- uq><ږ NPAA(l];X_uV$R|ˊkkis]Y<(.c:&5ړy 9c͸oӷThG k@t69|@ȷ*{I|/鑎UϪҌ&=buAՙF^X [9^kMuuUΘG6A4pe(s>+*o藬_[ OAぇ0S S2Tae3Lg29QG4@@2E(T8 j;-*B!@.[6P E Ebvi:f,sNG+j3MUܴh{a*T6Mx &M\zQXᶯ׬lGZS2737F'_sk.[LF``g{ {M0& &rdyŢYgRx- ;#0E:j.?ot1pTA z Lug软F#0X&;j>AgG=M3@ J7ƴXij s7$nQdg8.[8d!W-kDG9#`L&s`nmgQPxxƜٴS7Au|}N|6lՙ:uIΊ#`0FtC``'=]Z\"--O(#`0F#`{3=|.ecZlc*0F#`0F;>҄[/맯F#`0F#`=ooIg7;s#`0F#`0㉀hmo{[m[,iuMm]{k0F#`0F&XoR{M3f(V\>ZkUS#`0F#`0 v:#7<-ӻB.0F`!po=:iVF%wQ{n{*^UkIY/)O[0>xi嗟tg⊴j%SJ'xb<")]uUOJ]wԚ7zꫯ z)TU#<2=i-ʬ^pG{,miUW|Z;3_j5V׻F`: 0mx QL)${ct72- cBkM'tRZ|w17ȗ]vYV 5\i}Tλ+ 3Op UTkPm&o+ܧzhbBÒބl;c?vmK76a<7 ң:jRFu]SK2+'"e]*oQ^{8i+LrJVnT׻YL ޅk/!~*l:r9Q![oMw}w~΁w 뮻.m馕>p'܂ .V_} ""9a`&oWk-B -V\q\$?䡇yo _~ybΤ >"3%u\la`G@ݎ>,iVUI}G[wU7t U@@@AWiAjh> ;A)$RCoAzb {;YYdSa>'g A׺ֵmF$ʢFO~^&8Cv[s~/xA/r\dxֽ2DB`+c; w?x5Cwg?{ӟw#jWZa{XwQG桇}c믽E/Kb9}޽ ?΁y-}k_x.z׻vCnpt$X=Y>q{\-w|g( N}9\xC>D}֙Lg:S{k~1 u7ͽh'oX>~^W -o9h\绷-}o%xSbB71u`~_^W6kU-ַu<~~#OGwJ3#{Kk9pì{U%;Zɝ?^W/|aw\x !xfSf>O{ \8 UAg=Y;Y"sx;0I/B/t d|y$dOSTODŽ'?|p{qhzYo\=ka+" -xFCx-`hBmJƎVyyg?^^ #O!CEO<Һ8b x$a\ϘzV73DYן{EyUOye2ƾu"_8S_oQ"yN[ppȥ: 3f mY~{}yv3 5 ڭ|cx1Ǵga\˒9iX*gCw<6s폏ոeQ1f ũcf1>+̖}fT6/B[.sQXC,3vp-ڨ߆x/x0#o|q8`&a-nqd>OPw8 Ϝܐ E *veH / CDqzիUw3q.o\_h,'铱1#KMim!?C=e{!ر *;woGrq~ <&u{߻R Ƕbv+\gҘ}mo{`8yxH6Zx-XDa#2p߷LIx{Q2K& h,RjPڷd+(ox{h$<(ZQ A72,06(- ;C[‹h bEQq,]ju2Syޡ/((v b ! !-AiBr-/q(?k|q'NH?dDaȆbt-](apQ&95ۀ@IDATom U\ @Z:fT4V bLYzH X#ga5_xe̹n/6 # T(n py~  5+$eP=\ kX>aP/"DՏ1޴EadkR%!` MgPi~Q);h ȫMUe7O<)c~_[Qxm1-;vߎ)mқ_l3("$2u):Xk?RƒfQiB&S0Zll2a=Vr y!etfض.gu/NG`(w;I^P] <, ZH,R-^} dP(~y#D9䙰PnNkXxNy{1D̑|*&+WIGh3X,P֌}s \4x#5aA{)bq} _nW,ȢRcuxP޾f%+ke B'Eڲg|)KqGs4Bb$pzV_("Rb[H8h1^2A%~A/Yl[z9'SaiSqCW yvaS3k? dƜ1Ȩǖ ֆP~n8LIE-Ł!$~@Շ!+Q<'si/֬XB7!/1)OAN<#%1b)1c{x])|=?~_2g>_:C6~oݦb  ?>#5 dƙ2GՌQku^ݟu/B`7 |('9͙z|~l(񛥼xyL݆f A#.(,|=i}2B..ZeaV-O~YY-   uP>3.n蓒%x[P^.xǸ5Z륈(iLBhF$5}xj!U+8D<+2~S0,P i-Ѝ5 Z>;;?;ta"L Ł" AYk< +$Ms+i둌|}R8@cCq\oBze(Re_,ڟCyCWʻxzKѶ&Ax?l7P5d(W`8;#3^F[٘k Ч.b 1"cF>&g4P䱈D{ {8u2΢v o?CD1J[N77x8^MnCQQ.wM7_|ƚ7?;-"NbhN2c|dz1e]IFx 9{Z#fc47N*hWqa:a`ysÑԿB`! |N>}qy{>g6ۥctQ(Czf#腰\YYÛJk 0 X(ͬ؄0vf pY{HB[ނ n\ yҴ(^ykC{?~ø'P [BOPfrvlJľ1x[ QY'lkHgPYW8 C)yeIB,g ,!4BA#8z%Ŗ؊AQ&s8̊ `$\oJ^3`L©'Y?|`{K -¨Q҈~{~SAm+qЯ?yh"_^YXeyѰoefa\$BDP?*47|Q|:f2kET͸BSv_FP?fUUN=Xy yi$7aʡwA"+fAʵ% E)̂HB xgxz$d!yL0͊|OOeѼ( ^iG?0S-(UcŸވ:m5&yMl xo~gpo;ϱOUޕxJǐZމXdEq|hS6PH\|l -{cv4`=Da(W€cwqx^q-1Fփ_ψȞN]P<eQh=o *_?.O#SX c~)2oa'ְ)MB#m=1Eᤐ_޲w0/ˆ?dؘu︧!{v4wI6,=~WhZs7)Z>)j {4S>)|~C'/ ZCH^`"ڕ &<;9B b=8}{^B7B/;O\zC:0d1tJLy&8Ikogw#yLrB =vnkc͕v<]F^4/)cs yMFs"!GH5P~Z.V#8+xbd1^+ e]RiVX< }ʝG['"M>!3zNh #^DqS2Ȱ"lwB`!Ox QS'W_Pm.uPeo7UJVcrHF(< /p˖'UxEva:Y,LYtf"qݭ(hZOgP{48!@c?O'!%uD]BC _e3dCrHus !S4D1tI_8i1~vZg)'r?A13@9d,a1=Yr:kQVW3ze1jC[{BQoϜ,N9H 9(Eiƙ(yκiM3H{W yo9Ob)*Ţ-[谰* oxgV!k!z_*$ WJy_UVc_ bSք iMD&b)ODGw\Z_px'P>B{=Cf) )* 1r~u!ʁ~FZi~-Ao'2 ?NQ@Z7EeK~#s](Sgs_9 ;x$""m{(Bě}n3Zh#پh A7n_j=G$ 7cRTZy(-2N-C`}`RHDP{́z(/gp1'U\A`Gۨ5h-2F"}v*ݑG_dOhy"!~}qhb`m>[< o*c|AD"G}cf@?AS6 7 Fa=L)cwqX({;y#N#kX$ۼ6aip9!!RH1(kKG_YkuB](+n\aR4Lqއm-hcy.s>2urk(s'=,#m0 Nh Q7cQS2bPެ{Q,vTTV7Ky(b}{\# x} Xz z^I'P}cNI(UdAUKyg<r|»{_0v_o&Rx ,ʢLk'U?cou㜽'BDŽuʥ-Qv{2PJpn(2`!t3(AG+Fxs$߈bO9f σ 5F0v NL33eTmRQ%ĮŠLU&!~Vc?$?eAڡ<`,R.ʞ)g2/ØR&qq:&gD s^|eq"Osm Pm\FD1aƞinW0H7|sH̢OFL1GϔyL AXe00"8R( Yㄒ$"oα"2:儰<ۇExLceƶD* ^C!9oߍa.8δټh8n[kʉY7ʼ~wB!Cnx0O7;8i4!?|N_^jLk tƖg(AdT9Qu2DY״-o#׬(\l/#Y/€̰&&x-RCkM[yPWCe0Z\2K"2s8Y׬{QV}op[oE}`QY$pPB-h"Br٫~WʀЪkʰE'HP`BX{n @ a'6"-LJ߲l;C)BQI׆YSͼN<8EPgµ۔7cmg6?a(y/r-~qH~CixؙUP}m"ƐyE_o؝Wa:m:C14īaK;)y揿EH?ß9CMi2ԱcL>2V^;-rO?37h(klNG$! aP ݂@)𻥧ثͭ0v1Uخ|G]ur B` m]±gm̡6ʫ adg֬{QvQKJߗWٻ c݌F?+ZD{E_Yg桰ew),tuno(Nh,^'XH͊Î=?9W  "PtI+ ͺPU)&!e 9gwԤItL8G }@Zjwu'ٰ#^W Bc|v]e@!P,)?|ͺ_|5dSbs(K61ʪ^XsR{9Ρlc{[_~ݯʖ lddwQ"]@!P@!P@!P T 2 B( B( B( uDujS!P@!P@!P!P ui5( B( B( B`(~{T@!P@!P@!vv]Z * B( B( BXGJ_^6@!P@!P@!P]V B( B( B(RױWM@!P@!P@!Pk@)kץՠB( B( B( uDujS!P@!P@!P!P ui5( B( B( B`(~{T@!P@!P@!vv]Z * B( B( BXGJ_^6@!P@!P@!P]V B( B( B(RױWM@!P@!P@!Pk@)kץՠB( B( B( uDujS!P@!P@!P!P ui5( B( B( B`(~{T@!P@!P@!vv]Z * B( B( BXGJ_^6@!P@!P@!P]V B( B( B(RױWM@!P@!P@!Pk@)kץՠB( B( B( uDujS!P@!P@!P!P ui5( B( B( B`(~{T@!P@!P@!vv]Z * B( B( BXGJ_^6@!P@!P@!P[׿~u/;߷Nzғv'?ɻۯ( B( B( BX'L{o~m$'9IWa@!P@!P@!P[)?[]OySvg8B( B( B( B`7"e wmG??( B( B( Bl|gsֳu_@!P@!P@!P@)Ow}?}w󟿻E/tA;f?o{_?۲/~q7/ WBw;ien|8>5㛒YzVwkܔW&@!P@!P@!+(~B9Y\wJuGuT_mfpܠ׾֗ɰAwF]gs{߻խnjK?={_?]x.]z( B( B(R'teVvue7rqtg3K^,g9F}e|#pG=?ӽMQ ިus}~@!P@!Ph>}+_)9qMF@):2+Wҕ:-}{{[ߢR^%mG>{^֝^tַ]bm~cTr( f949Mw|3\8|<;]W_[B޿;uo@m u+;NSw;)X9oz_?g旅܍k17 G2n>Ot^7o`;&9l_ߗMnҝԧ^v8)0U_S鹈`$/~F7UVד;+jWU_ _ o7&ӟtw󞷻ֵ5/׎{K].ySfg{5nw'?ͳf4Ovu%~~݌Mۺ?G#Rn[p~W9bn~_ow"hin\~u/x STmnsn_:Z< p~{KpmI>oחw܏); p9yζ~o汏}lwӞ]"j?Ͻ}g?жY < {,Vf tŒt3u: 7-K;3jR><}t%:gL%{6K_S /Rn|E'?I?nj !'5{^_Gۈ& ~2}sNlB"7txynF۶--V16[zd@9 Nbn. \vҗ;cP}qb,~B/?<v߅ڏYAhCU!Pn+ $v_KЅۿKk+y^{x;N]o:oa٧͋YD|8Xf}=q>;׽u{'=I5HTz^z/ -rɁ"d♍qxeiG}t=n@ȇ2X˷-eT<6w <ߝZsկzo?%zo^}Y/ax2lQ.WnWvr9 CQ.!Ye288LDT؉CzGκnZY/iQЇvzի' E*ҔS '=" .vN:4Og !ط7Â=evm:vg{ }V}wfR]2pQVF cED֘PAQ6X38j~E0]MB/ "6?uOX]ζ-SVŸO 3Aȹ<\h!<7)xN_/K{c#"$¨E-C^\ʿX%ZYv| ՋaS_0187̇!mc zV:} bhEi>U[\A=ۺjQxn(yi{6F|kR>|s''^pxТcyreYH;ƾ}xOeCHcsyGH2^YƑ1GF",Գ%:h^E^~wcVD;yaH1.XU x>P^^}W[Sr"=+5~o|SH#?;b.Z/ +,Hrx}y|N{zx9X,2B)8 zYR/CO}S7~cE~ݭϪ%ԭo}>l=M ]%أ%!f) &{ݯ€=OWroDxz=!U2{3c < Ehk B<Eie-n s}{7|V u,L|=cg50(cJ[ŐDGd0ia 2}k?~J l۔ԛd *l~_58+w]YBEhJ`gk9f}C그[67HX?Cs Rhu5ƿ\a m&cq͖L7?~B+i'W2h3 `*ڂwPF2Ym*o7,~n<-PXy藾6I#2{,c8\v,/[nn[T!֫vmXyz.m{|<" #eĀa.fCFߗs1{2O(HAZX:-T:_'~MQrHWØ1n}fcX>qV~c-˔=}E= k2V^[_v,.#,2s<7c~  I4#<3>b. Ǝ话DR"-$Rе/| \}Kg($FS~ Qw9,Q&Y0*B%S0 Ttc|;œқZ["<v|Ԁ`y߳x1MFPV]0u}ɔ0x3 q?9紡x5Y,Є| Ǣmv/|7vH?g-l5J:a4Dy>X)xe JIG0^(o%岔/[+:¤-Zl۔K [$t t}( z?DDLPDP؋xU} u XyQGX{4(xWt"x\e"厵 #sYP’D*^(~C1]sx]M!Z#yl3:F'|:C3׌s}5e]\##sxSouW;xp,;Kcs3͘Oxra0Bޞn׌s83/`a|:B&p:p2 {x'/2׳oy.;˜#OkK9 ZE.:!W1ǔMOh'3hU>+c(+>IRF:WQ0E)?g}ףOyS.-w"K"^y˓ْ-Rll>;.9K'XjPrLh.Jiȓ n10,!ee6'Ye/x zm}ӛ^º Q})Q(mi!$*Qxn #g ̴Ċz (U򔻝m[,+WQ a=P< !1,tzcF =$0;3f⭡$ʇ@ dL\O%~, BygcHVN cpaܘR߾#h aMT 5G=?1܋(y-ڜەx?)?d~n#P2$e4N7k6h,y.xg ќD bhePh gݸcx173iOcn3 H Fy,C1W9}!l~jc1wϘ{ ^^>5'JD?QZf Oqf>o\d]\lu>ez^XkDf!?ediڹK= c/ [~Rt5KzrK p/υ&*+yO!"xxdl yf "4:^_UѠbĴ|3+#[BJ^ѐNv4cB, 1b|xԣߞf#0' z-\}4Ps?k?! ExBlO[f1KsDPXpۭ1Ye!R E!Vx~-ݰm++ - CFa,r)f %긧ukQ}+m›` xQFOq[M`lTJȄ`#VO=lI۳WۈG.\Vg C1 x e<-OoaFC[#vd_n+wm*3"[%\LJ Q;_VwO'́e [Dge 0!^ bYF Gg FcsE6)%/9F-<0x"w() |(~m^^Ƽ$CuO)F+ٸ((يfeECxN;<Sgǵ6͵ Oٶe ` Pz)\YK7f疭8zg+oËs{FW < !;A ( E@ [Eh{V b"|f P9Z_2ca߼&,f,f y;~BABn^G)cy^C3c B=bdsߐ6 JH>-^y }Nh 0|,`E$q̸CEpxżNEʞھ8(NCk9(SET_^Px9nǃ9)MmmihM>oM^̯\/25s^S!cr}#P > >Nj<=C^װb9WGΘg/9 \^S ,esme];g^mgۦjB`EqzyM0^;gC=BY oZɏg]>P \{m++T{<ͫ=U-R-ل [j#"WWmlw^Jڑ0Io)1=ѧĦF#B`+^|Ƣ!O!Z@ js!f999>mRVǫOPh qC#y2I3YyɆ,-z@NcoBs=n`B֟(3s\oQyJ}#{VPct).s%iHu9,82{s}˞0C|OSƲ;6T7~/D9l2cسy~m~|`4þ򫺢Nd2>5KI>5` %>[5s9 ǡghh O(³"/rCeҾ|PܐAXԒyȫdžrvϛ Mc~Filq/-&ssKL} SWF|rl 6jG yԂ[,ZYz׻J AY(oaNTGEL0TE?LC'>Xxy+(cOæԳk/:)x\‹@/cIE&(;i 3"!,({b6eҿ{^DCp1mÅRqζM) DPx&F!AYѡ<'bత8opAVESWF_qpAO_2VPB>cC'>G` a_a 3 z`VJ>oY%@Mb D̪}&PzgcAx)oF0}|WJ@Wj a1S);a/`r#2KcJꔱ,ysQ|xP.r ^ByU鿨Qܔܗ9){Q EJ{! s4'8_izI=|`ܙEl4 W  ˌ0"? Eeʞ>er;lQ1p!C${-;Ke|fhn1i`K^a![8}H> EzsC5@cs)~"L|Һ) xwߨk(~Bmo^&Ȅtz:ex˚Y{s> XX"ij@ -&I)B8ML{w7Ƣ,0,0'Y(qyE81,л>D&tWڜv^Y3l۔I&g"@QBa( !B:afB'bԋ)aHus)OS= !l52Mŀ@iefoA7 t?7Vy9B)*}2,mO/Ɵl)Pw\k#dq-/U{Ʈ~Gٛ]'L*_Ҩ׵0<" 2=;ܱyN;xcj_1,LB!\C &NZϑQwWL7l})ES=%#!d6ެUxv)Ɣ <8NwXS>\9*̼1<_DПׇ>)Sڗ Cx-bl\%C\G1&|_d,eE܌Yd GY^-P~ə"kVWA}Yıe|z`/d,(óƳF,KYs[ݏ@)p3xŲ 730PJM8,+Қ&$Ut1&} 7VyY˿-B&`{K<ʓ6 ni;"0W³#mFs3o @c QL(AA 8&L NEfwZ!N=B Sg?E̘fw`?Kx PnE-{<-|Ci>@s=57eG4V.! n⠯#ڒ~XXcm Dtx21˴HO9<d^)/3"g.~/SSQƲe\#ɼ eE܌o aܡ2d錐c1gy6^᝙zǷ4u~msyA >eS,*f=/9-aדb.GmYVD1 G- /eYRoQ%FEi/f`hYm:Stkmxb(>0^ cGaQ6üqa-" i[ޔyL-\/o>y8Q(K_K*-SN1`3-;OFCkq-E0h.+ˌh˖=}2y6є\2fP 5㩹oQƒ=#پU)dwJln=LB $*DW@!PBF3;u-[o^@!PLE`xۃao[IvR޷ʻ( B( B( B`_ hPY@!P@!P@!P#cV B( B( B(R׮KA@!P@!P@!P@)ثզB( B( B( CjP!P@!P@!P:"P :j( B( B( B`(~T@!P@!P@!Zm* B( B( BX;J_.@!P@!P@!P#cV B( B( B(R׮KA@!P@!P@!P@)ثզB( B( B( CjP!P@!P@!P:"P :j( B( B( B`(~T@!P@!P@!Zm* B( B( BX;J_.@!P@!P@!P#cV B( B( B(R׮KA@!P@!P@!P@)ثզB( B( B( CjP!P@!P@!P:"P :j( B( B( B`(~T@!P@!P@!Zm* B( B( BX;J_.@!P@!P@!P#cV B( B( B(R׮KA@!P@!P@!P@)ثզB( B( B( CjP!P@!P@!P:"P :j( B( B( B`R׿u/tғ;O~E@!P@!P@!P:!e IY=]:y{^-fg?1}_Rp󝯻U7r7vojVyYzVwϢun*ճ@!P@!P!P x ={{݃B2x߸% wuzh}o)> ox4SnlGk3yЃԽjn۽U@!P@!P P vF.Bg>Ӈq3{K^%^獂ՊWVU@9y]}ȇb߾@O[&r]_W#WKQwӞv BXx)Nq5hM5a/~ѝd'1џ*dDAXf_ tNHsKտַO{ԣ&[_Fo~ww|ݿ˿t/zыӝt6Vo3JJFJ̴MybXgF??~w~Cv}_sկ~6m4W̾j;O~7&[[~R6>v8&^ټ/~ʔue]iF{|׿@e.s^ܾ׮}]wt'??kV#P QekMozf_ ueՊ B `^Оͦnfw-Wҽu&{w6 uGc}K]R.w6m4DSTmY$B裏ynwmUßm\O9#/6/Iw' Z@G02-{'>*m><ַ8"g?Y~sD$87vVDݼ64.-P0:H *ovg&p7)zng B~M$B/nonպ;d33 r0gnpLfsx5ܘ46wS @0C9dK%oUm>-aJcMZc+^ {.o"oz;ܡ︔gO?թNՇG.pc̨?|#կ~^a}?^^3>3 VBw-gz׻v%J1u{[E.o)$2׽nw]zo3<1ܶYMmw ~71čnt~Rc"G?ڇ׹ur}@Y1oy[\:i)vQb%Эo}49ͦ^W Gqu)b׾ 90ο}룺T}r.pk+C__:?pn%jNʊlρټqRe%Oe>ݫ^E KaHǔQ;Y $R)cDyYG <֔h<6[!7Rѯ18K(=;U??W 䃜{[>t? 4fo`p _½nٳu2Ġs\ ax:F4GULЯFjl/֍h+$x ՔW:~}J=P펱/2|gZ0'6"7 Y.|-A%E"~KЪmO Űq4Ѯ~ ,kV\cWi!poǝCeƐ{pm1<5fnq[lO%e0[ʉdz# ;ϙ۳xhNExя>9Զ s>"k;cQaYҍ2Ս{֯xs/03L3@D8QFtcgųpPo|}{(ېGEd2˜ETp Huy2|=)k _ƂriIk$nїƗ3^=ȸ7\ [#_f5,ReTwSzxxx6C|9Nm^xg!0@)c̸Y`!vY $cUy $?q+>+X7K9`ŸXB .Jp 0'K\3l^am C} *nCz"&c=m .y"O{Cΐ`B׋ (FC 0CD&Xْ[ sZKUzվw0:.Br/}ioƿE5y#AWW E_W g򳵦%ϊ1>&07E@i#.IQOLM<I}{{a-_ܓ_c`^Ҩ:+ا m{-ɚ #AiGo󃢗Rj;ND[2},<#+o,sRdMOE SS?olzŶh&f#c8b7.(:d܌Q~|%Px(>A*u o>mqE?JkB)-B1!ѡM76 ꔉF6[|BC Z`V!=)O|C~\5h{IpbD`{o]fṠw)mH@}|c'L)a "rFa({j2~XsƚP <(e/UF9YDh[D}_ $ ^iM3Gw^Z}(/ۤEyNn=qe!UPv5n\< Oha}'AVXQ~v0XbΠ82f}1WkĞ?kSsQaq%qaȑ2B61sNˬMRni<2 8?㽜 ѬB B BBPfEcX>u Go.9Ix(зhys6،9?`EyMnj"x@[Q;F]a՟YeƘ=Ͻ}E2.XD5@yY(Aq(̖P^'vdR !'%?ڡhl}3ˍ3j&|͚l~ژҹOIkj1FhO./dz~ѰoGGDOx?F'1 ci_V6MokCC|1^+ ƈȨ0֯?af$ʔqP)'$sS]wԵ>ƹx8#`,2{Ev P ?q{jNF #L(&!Jࡇz!!Ŝe +O%Q#R$@mO% dP@1h^D[ 83<RHw)yVƏ1Nw o< !T¢c4cyu^x< Q)Ƭ!)TDY8{dG Z4A]=9PѦ߅V"P tq,"-*RJ-NrX)_y? CH3+j@w9G?5J{Т a{ ϬeO!wj} w `gOd=ClXoܶY vW,Ccm!dTE{qyͺsN4)9<1=X|x(|F %g|lE(2gmF O-@O6@QjqYcezn*ߏ|YwԵ>X8&DBZmϟ`%S~m 9m}/ R' xBH/" <-oڲBгP5kt#"D7z] JG豹GD0晓A❄8.{[9k?WoWVhy<@J1rCaο,X樆y -Yzx~f~ ㏴>Ƕl)sNVe K׃"c9asSu7=C~nW\2ֵ{Zp D-|mm]lO@u3x vUl BhLoǢ-lxԧn$execd <&e%BO 50t#!2`Xܝ>OiDK8#kşAqY}Mic ۬󼶡\00,B\Q(9F*$"xK#ڃkd>8|[VϖXñ#%a C?W c.2b+DP[ʓcLi|w;P*Q5߽bM+⭡!J?$r G|qiO{_>}>=熈R Ž(OC bp_Vy][ .ߛnJq߷^W[!9,Nn=ϳ Crs(|(+2&@EO=e8Aȑ"5WoNu񈈼ah#ǥ8t #ɾxHy6#;\wdxQLQOB msSQZ$M9Cm* nKYțxvۼ`$7fm^Y pi(8c0rR6'[Wmy!~4Og>f܈r`%2QFbG!.b٠egxt쉤,EՀG,9T#f-yUJhTPFDE3b`Ψ( sp^3;;U_ öbEl"oO2`\?i=esxfORyA6r }:v@9_Uy{=WD^cMn(_D1 $+*94=}2i'H_#$ux`H; 7i"0ly](v"PV#"J^Iq}: K^#{{;=RÔ9`d%p yDMB隆k1ޠ_d:o~1fsw=d|\>:,P]E@~Pi hk2g*?x)XSV^nذܼ` <1C8TsC0ona(D0x e}$t'(`Qƃ&2{YaRQH~<2Ha<£Pcy;eGgP`7t0s5~bOjˍ>% 6%e+֛PO1's_'~IT, Uß:چAg^6h H6FxxyC8,bH:}|8bo i3fb(H zDP$ }y)8 :=  Pʁ?HYX~V\s?9luu"Pn `@C4A/ᥥOk9CG0o;5Mm˹'œ2Rf]Ștc5jҬo06у3J&տP} ckqy8F>bG"R"o,oBŰ}%YF{/F>yc80Cˁ#E8<|\ %Mx4 ;8*@B+roYy+C|;mc10rhHG\-C,m}fܥ|!C[z1zj\roL uWzP2xxbz] |pRC/+ ZQ>TL9(^ɡs1rpCEDs~((}@<$eÝWeCa2l~zG>y}m,a"|0A3n$ /΃i;(Wu\Id?e+H=/?8e t=# Q~F0`arMQC"/1oxHq>^u; <&Fj| p܋&M5a}:/mNLaPmE>m]ϐŜs N?̔!*VGqZi9uG { xjop5?qDU' 1:oGeC'u,'sy.mQ~csBƇE |a crěA^I史c`$!{roem}S%H2/!ei@><i3шlo{7)ܢ<߄4Y>w)ߤ{Ȥ2s }91Ǹmon})OD@~v 5B401#Mnx|Q(9/<Qhǃ9bPlQQ`B/R(qpF4,b{< <r^a ePȋc(A oy=X c.yVn\>7w<-&> r(P2 f;۹^X%*B9dL`"Mޜ) >? j@WE^QkgS˽ +9$r_Emp-½*<8טcU(|Y"y G웖nVgF+%"K}JK>>sϠVpdzlèڧ$$?-kẠ>((GD 'Ʋ[:X q!wqWb}_a'>\{<`;,{f}1%qO{o{dC[|<^w#Ii囕"z*q,GC|%j*&<"}<ĹA0ȩoƓƅ2ƒ$7A a͔ &XnZ>H2P.(Eևr0GC˖9' AB#xnȓ)19fzGڴz80c/>M(P;HQ/>!c\YŶ(/P7u~ z ڬB L szQxQ&IgIi̺o:EԍA.u qs&}I&2qRYڦ/Ц\cKcӯ$69(ypEƫ{K{~pq(!7J@$  H@ΌVw[w{4![ouS$  H@$p'2f6۬kL8S-r]53 H@$  H@8kP_YC;?q2@/nvvu"9WUݱ[2y _.>OuvXsÆ ݍo|V%  H@$  4B@~Άo~eׇ?ᅧ;*y>nm$  H@$PPӟ}+82LwЅg#n=$?ݧ??ɾeonst{z]?^gVE/cm HG~U]R:$  lV*sESwu{㊧;Gyd7;#|9x [B:^s+Xavp+(%oش]^L@#QGUz׻޵.jGuO<hw+]mo[$  H@X)92h#{K\{=y^ǜZ*yた%1ǮGƔc|sۀcpu3Wu>OvwTO+AO:SXFLK{^O|mby =B~xws{ql%  H@X 2nWκ nr!3u(xtmj _.wWv 4Fǣ!!MZp ݯvm]zsI #7i (}9zH-\jZ׺VQΙ}ge>eXh;ϕ2Ń3C9kXF NKkWU1bn>Gmo{[w'MVY0OYEp@Inh; x`Uծv2mF7&Pzh=Onߤ ,X-s';i.qkt-'> ">4ʬGuc~X gx1x$  H@X 90>93H:J4yS}<|fw;ޱg'=iX<,wI'm+^.+w]<ܛxm٦R?DG,|cJ8y:7^} .Q |#ʷ[ Q a̸al+x9y|&!X t10} [Qsl|-{[Z"#nu[G}B"j|;_I#@t})[neo?b\ 0ʰ&C#@dP:bL9;moS _*ִ!чHѿ`KG;V), 駹܋|&}cM]2}<2fXV'qmNܻ( z]8vZ8tݬ$  H@+'?Zˍ'%o` I7F~(|n )^ז̀7+&<^WE&-F#x~_l2c׿6!b! ^Ѭ}}ߩ^.k .ykiD<`TO1X#i(gs1 |P71D[bG} |vz=a! DRV5#<:Pb('yi_W/| O6c;F#a; ܩ#}i&1%,eCRkQ B8x؞\&\A ŝBYI3_6>rApoĈs~'h'*m5OR H@$^T$կ~{_ݡda;BCQ(YEExx#61Noy[[x2k夁G""P§@%_2GQDP~«CPH .Oiah0ؐRa,6>'.?C)OK30d˃|X?#v8q}D>D/0~>kH#s +$  H`/4^?q%Yw<;1.!x ',emxb-;N  (x Q g<%'4? ґ.{D0]F0Ja Ԙc肣:| D! QNa"<@7Dm)onŻ>M?e F6rw(r@G;#˖eOI}E&r v/.C}* f_1 YvQ&1)rlW$  H@ 0<(x€㧥e{(>aA'CrjirqFuh ?'I@$ /ol3O&ܗ9@!( h/PdCY+z#1"v}2C)EPP6^3ǂR sBa9v#OK8<݄g&F 9uP m,F[a9y/<Ǔ`pc9L(T1REܵz'cj>2vB: |1Vs_Z@"i8?sQQ$x-_)R_֧fe@Ҝ# y)pf c H@$T@%9 gs"xbv/-gf75yc'|x1O@yY̢s>aLÈeV/Rه"I.e_TPx'5'|>ylTtb~l}ŀwt~ ?ԩOk(9X9JLK0|<ɣ,m B k C p{$uH5 0wP1z aT{yL9X&$üxH>5ii?Ep 2o9/vvn"E:B0K+ H@!?= dW 1V&̓( Yx'9#6l((ܼ c=ޝyS9BeHg L2QbAasՋs&)pȃB4Q>(R.o"jxK 蘅͈PC޴-BA"OTxoRjX<5xwz(Ce'=#W2|wuwg.~^c"&#m-%&@q<#`C ,%94&yPIi?8I2O'3quU d\?A"C3)I@$ yHy,«SGX/^}(>S8N8xXQ2vmK=P#(G0A_%>>PlOyc8Dz;A# ""_E V/ 8AXp̵gUtwŒ:%2C Vf=<Pp%V_sOr;J G8<aXck}"8,@<0S ? H@$D*sKkX.ϑ#lon'&eV1sxQ ZQڍQ9O?a*-!(~7<^Ss9cI,@H&+k#ׄJN%b*퓾2 QNZ+}iaP tmIkN[»YO$|'5ҴQN: QYozΧJ+г"Bn ?"-7YlsZ5e偲LY.(gCտ9k# P`?$%F?/ i1uMүS0J3Z.u}`tV.)5#LоrM$  K@~^r4 ~xQY`G`\/Dm C' OO`y3|-$  H@Poiآ>`Ўȃh!{9":xc{O>|$T_i^|ڙ(ovD$ 3)|3B(i~(yJ@C< IIqn$  H@V3u`w9A?nIENDB`graphql-ruby-1.11.10/guides/javascript_client/apollo_subscriptions.md000066400000000000000000000141771414121453000260600ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: JavaScript Client title: Apollo Subscriptions desc: GraphQL subscriptions with GraphQL-Ruby and Apollo Client index: 2 --- GraphQL-Ruby's JavaScript client includes four kinds of support for Apollo Client: - Apollo 2.x: - [Overview](#apollo-2) - [Pusher](#apollo-2--pusher) - [Ably](#apollo-2--ably) - [ActionCable](#apollo-2--actioncable) - Apollo 1.x: - [Overview](#apollo-1) - [Pusher](#apollo-1--pusher) - [ActionCable](#apollo-1--actioncable) ## Apollo 2 Apollo 2 is supported by implementing Apollo Links. ## Apollo 2 -- Pusher `graphql-ruby-client` includes support for subscriptions with Pusher and ApolloLink. To use it, add `PusherLink` before your `HttpLink`. For example: ```js // Load Apollo stuff import { ApolloLink } from 'apollo-link'; import { ApolloClient } from 'apollo-client'; import { HttpLink } from 'apollo-link-http'; import { InMemoryCache } from 'apollo-cache-inmemory'; // Load PusherLink from graphql-ruby-client import { PusherLink } from 'graphql-ruby-client'; // Load Pusher and create a client import Pusher from "pusher-js" var pusherClient = new Pusher("your-app-key", { cluster: "us2" }) // Make the HTTP link which actually sends the queries const httpLink = new HttpLink({ uri: '/graphql', credentials: 'include' }); // Make the Pusher link which will pick up on subscriptions const pusherLink = new PusherLink({pusher: pusherClient}) // Combine the two links to work together const link = ApolloLink.from([pusherLink, httpLink]) // Initialize the client const client = new ApolloClient({ link: link, cache: new InMemoryCache() }); ``` This link will check responses for the `X-Subscription-ID` header, and if it's present, it will use that value to subscribe to Pusher for future updates. ## Apollo 2 -- Ably `graphql-ruby-client` includes support for subscriptions with Ably and ApolloLink. To use it, add `AblyLink` before your `HttpLink`. For example: ```js // Load Apollo stuff import { ApolloLink } from 'apollo-link'; import { ApolloClient } from 'apollo-client'; import { HttpLink } from 'apollo-link-http'; import { InMemoryCache } from 'apollo-cache-inmemory'; // Load Ably subscriptions link import { AblyLink } from 'graphql-ruby-client' // Load Ably and create a client const Ably = require("ably") const ablyClient = new Ably.Realtime({ key: "your-app-key" }) // Make the HTTP link which actually sends the queries const httpLink = new HttpLink({ uri: '/graphql', credentials: 'include' }); // Make the Ably link which will pick up on subscriptions const ablyLink = new AblyLink({ably: ablyClient}) // Combine the two links to work together const link = ApolloLink.from([ablyLink, httpLink]) // Initialize the client const client = new ApolloClient({ link: link, cache: new InMemoryCache() }); ``` This link will check responses for the `X-Subscription-ID` header, and if it's present, it will use that value to subscribe to Ably for future updates. For your __app key__, make a key with "Subscribe" and "Presence" privileges and use that: {{ "/javascript_client/ably_key.png" | link_to_img:"Ably Subscription Key Privileges" }} ## Apollo 2 -- ActionCable `graphql-ruby-client` includes support for subscriptions with ActionCable and ApolloLink. To use it, construct a split link that routes: - subscription queries to an `ActionCableLink`; and - other queries to an `HttpLink` For example: ```js import { ApolloLink } from 'apollo-link'; import { ApolloClient } from 'apollo-client'; import { HttpLink } from 'apollo-link-http'; import { InMemoryCache } from 'apollo-cache-inmemory'; import ActionCable from 'actioncable'; import { ActionCableLink } from 'graphql-ruby-client'; const cable = ActionCable.createConsumer() const httpLink = new HttpLink({ uri: '/graphql', credentials: 'include' }); const hasSubscriptionOperation = ({ query: { definitions } }) => { return definitions.some( ({ kind, operation }) => kind === 'OperationDefinition' && operation === 'subscription' ) } const link = ApolloLink.split( hasSubscriptionOperation, new ActionCableLink({cable}), httpLink ); const client = new ApolloClient({ link: link, cache: new InMemoryCache() }); ``` ## Apollo 1 `graphql-ruby-client` includes support for Apollo 1 client subscriptions over {% internal_link "Pusher", "/subscriptions/pusher_implementation" %} or {% internal_link "ActionCable", "/subscriptions/action_cable_implementation" %}. To use it, require `subscriptions/addGraphQLSubscriptions` and call the function with your network interface and transport client (example below). See the {% internal_link "Subscriptions guide", "/subscriptions/overview" %} for information about server-side setup. ### Apollo 1 -- Pusher Pass `{pusher: pusherClient}` to use Pusher: ```js // Load Pusher and create a client var Pusher = require("pusher-js") var pusherClient = new Pusher(appKey, options) // Add subscriptions to the network interface with the `pusher:` options import { addGraphQLSubscriptions } from "graphql-ruby-client" addGraphQLSubscriptions(myNetworkInterface, {pusher: pusherClient}) // Optionally, add persisted query support: var OperationStoreClient = require("./OperationStoreClient") RailsNetworkInterface.use([OperationStoreClient.apolloMiddleware]) ``` ### Apollo 1 -- ActionCable By passing `{cable: cable}`, all `subscription` queries will be routed to ActionCable. For example: ```js // Load ActionCable and create a consumer var ActionCable = require('actioncable') var cable = ActionCable.createConsumer() window.cable = cable // Load ApolloClient and create a network interface var apollo = require('apollo-client') var RailsNetworkInterface = apollo.createNetworkInterface({ uri: '/graphql', opts: { credentials: 'include', }, headers: { 'X-CSRF-Token': $("meta[name=csrf-token]").attr("content"), } }); // Add subscriptions to the network interface import { addGraphQLSubscriptions } from "graphql-ruby-client" addGraphQLSubscriptions(RailsNetworkInterface, {cable: cable}) // Optionally, add persisted query support: var OperationStoreClient = require("./OperationStoreClient") RailsNetworkInterface.use([OperationStoreClient.apolloMiddleware]) ``` graphql-ruby-1.11.10/guides/javascript_client/overview.md000066400000000000000000000015651414121453000234460ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: JavaScript Client title: Overview desc: Getting Started with GraphQL-Ruby's Javascript client, graphql-ruby-client. index: 0 --- There is a JavaScript client for GraphQL-Ruby, `graphql-ruby-client`. You can install it from NPM or Yarn: ```sh yarn add graphql-ruby-client # Or: npm install graphql-ruby-client ``` The source code is [in the graphql-ruby repository](https://github.com/rmosolgo/graphql-ruby/tree/master/javascript_client). See detailed guides for more info about its features: - {% internal_link "sync CLI", "javascript_client/sync" %} for use with [graphql-pro](https://graphql.pro)'s persisted query backend - Subscription support: - {% internal_link "Apollo integration", "/javascript_client/apollo_subscriptions" %} - {% internal_link "Relay integration", "/javascript_client/relay_subscriptions" %} graphql-ruby-1.11.10/guides/javascript_client/relay_subscriptions.md000066400000000000000000000121371414121453000257000ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: JavaScript Client title: Relay Subscriptions desc: GraphQL subscriptions with GraphQL-Ruby and Relay Modern index: 3 --- `graphql-ruby-client` includes three kinds of support for subscriptions with Relay Modern: - [Pusher](#pusher) - [Ably](#ably) - [ActionCable](#actioncable) To use it, require `subscriptions/createHandler` and call the function with your client and optionally, your OperationStoreClient. See the {% internal_link "Subscriptions guide", "/subscriptions/overview" %} for information about server-side setup. ## Pusher Subscriptions with {% internal_link "Pusher", "/subscriptions/pusher_implementation" %} require two things: - A client from the [`pusher-js` library](https://github.com/pusher/pusher-js) - A [`fetchOperation` function](#fetchoperation-function) for sending the `subscription` operation to the server ### Pusher client Pass `pusher:` to get Subscription updates over Pusher: ```js // Load the helper function import { createRelaySubscriptionHandler } from "graphql-ruby-client" // Prepare a Pusher client var Pusher = require("pusher-js") var pusherClient = new Pusher(appKey, options) // Create a fetchOperation, see below for more details function fetchOperation(operation, variables, cacheConfig) { return fetch(...) } // Create a Relay Modern-compatible handler var subscriptionHandler = createHandler({ pusher: pusherClient, fetchOperation: fetchOperation }) // Create a Relay Modern network with the handler var network = Network.create(fetchQuery, subscriptionHandler) ``` ## Ably Subscriptions with {% internal_link "Ably", "/subscriptions/ably_implementation" %} require two things: - A client from the [`ably-js` library](https://github.com/ably/ably-js) - A [`fetchOperation` function](#fetchoperation-function) for sending the `subscription` operation to the server ### Ably client Pass `ably:` to get Subscription updates over Ably: ```js // Load the helper function import { createRelaySubscriptionHandler } from "graphql-ruby-client" // Load Ably and create a client const Ably = require("ably") const ablyClient = new Ably.Realtime({ key: "your-app-key" }) // create a fetchOperation, see below for more details function fetchOperation(operation, variables, cacheConfig) { return fetch(...) } // Create a Relay Modern-compatible handler var subscriptionHandler = createHandler({ ably: ablyClient, fetchOperation: fetchOperation }) // Create a Relay Modern network with the handler var network = Network.create(fetchQuery, subscriptionHandler) ``` ## ActionCable With this configuration, `subscription` queries will be routed to {% internal_link "ActionCable", "/subscriptions/action_cable_implementation" %}. For example: ```js // Require the helper function import { createRelaySubscriptionHandler } from "graphql-ruby-client") // Optionally, load your OperationStoreClient var OperationStoreClient = require("./OperationStoreClient") // Create a Relay Modern-compatible handler var subscriptionHandler = createRelaySubscriptionHandler({ cable: cable, operations: OperationStoreClient, }) // Create a Relay Modern network with the handler var network = Network.create(fetchQuery, subscriptionHandler) ``` ## fetchOperation function The `fetchOperation` function can be extracted from your `fetchQuery` function. Its signature is: ```js // Returns a promise from `fetch` function fetchOperation(operation, variables, cacheConfig) { return fetch(...) } ``` - `operation`, `variables`, and `cacheConfig` are the first three arguments to the `fetchQuery` function. - The function should call `fetch` and return the result (a Promise of a `Response`). For example, `Environment.js` may look like: ```js // This function sends a GraphQL query to the server const fetchOperation = function(operation, variables, cacheConfig) { const bodyValues = { variables, operationName: operation.name, } const useStoredOperations = process.env.NODE_ENV === "production" if (useStoredOperations) { // In production, use the stored operation bodyValues.operationId = OperationStoreClient.getOperationId(operation.name) } else { // In development, use the query text bodyValues.query = operation.text } return fetch('http://localhost:3000/graphql', { method: 'POST', opts: { credentials: 'include', }, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(bodyValues), }) } // `fetchQuery` uses `fetchOperation`, but returns a Promise of JSON const fetchQuery = (operation, variables, cacheConfig, uploadables) => { return fetchOperation(operation, variables, cacheConfig).then(response => { return response.json() }) } // Subscriptions uses the same `fetchOperation` function for initial subscription requests const subscriptionHandler = createRelaySubscriptionHandler({pusher: pusherClient, fetchOperation: fetchOperation}) // Combine them into a `Network` const network = Network.create(fetchQuery, subscriptionHandler) ``` Since `OperationStoreClient` is in the `fetchOperation` function, it will apply to all GraphQL operations. graphql-ruby-1.11.10/guides/javascript_client/sync.md000066400000000000000000000220121414121453000225420ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: JavaScript Client title: OperationStore Sync desc: Javascript tooling for persisted queries with GraphQL-Ruby index: 1 --- JavaScript support for GraphQL projects using [graphql-pro](https://graphql.pro)'s `OperationStore` for persisted queries. - [`sync` CLI](#sync-utility) - [Relay <2 support](#use-with-relay-2) - [Relay 2+ support](#use-with-relay-persisted-output) - [Apollo Client support](#use-with-apollo-client) - [Apollo Link support](#use-with-apollo-link) - [Apollo Android support](#use-with-apollo-android) - [Plain JS support](#use-with-plain-javascript) - [Authorization](#authorization) See the {% internal_link "OperationStore guide", "/operation_store/overview" %} for server-side setup. ## `sync` utility This package contains a command line utility, `graphql-ruby-client sync`: ``` $ graphql-ruby-client sync # ... Authorizing with HMAC Syncing 4 operations to http://myapp.com/graphql/operations... 3 added 1 not modified 0 failed Generating client module in app/javascript/graphql/OperationStoreClient.js... ✓ Done! ``` `sync` Takes several options: option | description --------|---------- `--url` | {% internal_link "Sync API", "/operation_store/getting_started.html#add-routes" %} url `--path` | Local directory to search for `.graphql` / `.graphql.js` files `--relay-persisted-output` | Path to a `.json` file from `relay-compiler ... --persist-output` `--apollo-android-operation-output` | Path to an `OperationOutput.json` file from Apollo Android `--client` | Client ID ({% internal_link "created on server", "/operation_store/client_workflow" %}) `--secret` | Client Secret ({% internal_link "created on server", "/operation_store/client_workflow" %}) `--outfile` | Destination for generated code `--outfile-type` | What kind of code to generate (`js` or `json`) `--add-typename` | Add `__typename` to all selection sets (for use with Apollo Client) `--verbose` | Output some debug information You can see these and a few others with `graphql-ruby-client sync --help`. ## Use with Relay <2 `graphql-ruby-client` can persist queries from `relay-compiler` using the embedded `@relayHash` value. (This was created in Relay before 2.0.0. See below for Relay 2.0+.) To sync your queries with the server, use the `--path` option to point to your `__generated__` directory, for example: ```bash # sync a Relay project $ graphql-ruby-client sync --path=src/__generated__ --outfile=src/OperationStoreClient.js --url=... ``` Then, the generated code may be integrated with Relay's [Network Layer](https://facebook.github.io/relay/docs/network-layer.html): ```js // ... // require the generated module: const OperationStoreClient = require('./OperationStoreClient') // ... function fetchQuery(operation, variables, cacheConfig, uploadables) { const requestParams = { variables, operationName: operation.name, } if (process.env.NODE_ENV === "production") // In production, use the stored operation requestParams.operationId = OperationStoreClient.getOperationId(operation.name) } else { // In development, use the query text requestParams.query = operation.text, } return fetch('/graphql', { method: 'POST', headers: { /*...*/ }, body: JSON.stringify(requestParams), }).then(/* ... */); } // ... ``` (Only Relay Modern is supported. Legacy Relay can't generate static queries.) ## Use With Relay Persisted Output Relay 2.0+ includes a `--persist-output` option for `relay-compiler` which works perfectly with GraphQL-Ruby. (Relay's own docs, for reference: https://relay.dev/docs/en/persisted-queries.) When generating queries for Relay, include `--persist-output`: ``` $ relay-compiler ... --persist-output path/to/persisted-queries.json ``` Then, push Relay's generated queries to your OperationStore server with `--relay-persisted-output`: ``` $ graphql-ruby-client sync --relay-persisted-output=path/to/persisted-queries.json --url=... ``` In this case, `sync` _won't_ generate a JavaScript module because `relay-compiler` has already prepared its queries for persisted use. Instead, update your network layer to include the _client name_ and _operation id_ in the HTTP params: ```js const operationStoreClientName = "MyRelayApp"; function fetchQuery(operation, variables,) { return fetch('/graphql', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ // Pass the client name and the operation ID, joined by `/` documentId: operationStoreClientName + "/" + operation.id, // query: operation.text, // this is now obsolete because text is null variables, }), }).then(response => { return response.json(); }); } ``` (Inspired by https://relay.dev/docs/en/persisted-queries#network-layer-changes.) Now, your Relay app will only send operation IDs over the wire to the server. ## Use with Apollo Client Use the `--path` option to point at your `.graphql` files: ``` $ graphql-ruby-client sync --path=src/graphql/ --url=... ``` Then, load the generated module and add its `.apolloMiddleware` to your network interface with `.use([...])`: ```js // load the generated module var OperationStoreClient = require("./OperationStoreClient") // attach it as middleware in production // (in development, send queries to the server as normal) if (process.env.NODE_ENV === "production") { MyNetworkInterface.use([OperationStoreClient.apolloMiddleware]) } ``` Now, the middleware will replace query strings with `operationId`s. ## Use with Apollo Link Use the `--path` option to point at your `.graphql` files: ``` $ graphql-ruby-client sync --path=src/graphql/ --url=... ``` Then, load the generated module and add its `.apolloLink` to your Apollo Link: ```js // load the generated module var OperationStoreClient = require("./OperationStoreClient") // Integrate the link to another link: const link = ApolloLink.from([ authLink, OperationStoreClient.apolloLink, httpLink, ]) // Create a client const client = new ApolloClient({ link: link, cache: new InMemoryCache(), }); ``` __Update the controller__: Apollo Link supports extra parameters _nested_ as `params[:extensions][:operationId]`, so update your controller to add that param to context: ```ruby # app/controllers/graphql_controller.rb context = { # ... # Support Apollo Link: operation_id: params[:extensions][:operationId] } ``` Now, `context[:operation_id]` will be used to fetch a query from the database. ## Use with Apollo Android Apollo Android's [generateOperationOutput option](https://www.apollographql.com/docs/android/advanced/persisted-queries/#operationoutputjson) builds an `OperationOutput.json` file which works with the OperationStore. To sync those queries, __use the `--apollo-android-operation-output` option__: ```sh graphql-ruby-client sync --apollo-android-operation-output=path/to/OperationOutput.json --url=... ``` That way, the OperationStore will use the query IDs generated by Apollo Android. On the server, you'll have to __update your controller__ to receive the client name and the operation ID. For example: ```ruby # app/controllers/graphql_controller.rb context = { ... } # Check for an incoming operation ID from Apollo Client: apollo_android_operation_id = request.headers["X-APOLLO-OPERATION-ID"] if apollo_android_operation_id.present? # Check the incoming request to confirm that # it's your first-party client with stored operations client_name = # ... if client_name.present? # If we received an incoming operation ID # _and_ identified the client, run a persisted operation. context[:operation_id] = "#{client_name}/#{apollo_android_operation_id}" end end ``` You may also have to __update your app__ to send an identifier, so that the server can determine the "client name" used with the operation store. (Apollo Android sends a query hash, but the operation store expects IDs in the form `#{client_name}/#{query_hash}`.) ## Use with plain JavaScript `OperationStoreClient.getOperationId` takes an operation name as input and returns the server-side alias for that operation: ```js var OperationStoreClient = require("./OperationStoreClient") OperationStoreClient.getOperationId("AppHomeQuery") // => "my-frontend-app/7a8078c7555e20744cb1ff5a62e44aa92c6e0f02554868a15b8a1cbf2e776b6f" OperationStoreClient.getOperationId("ProductDetailQuery") // => "my-frontend-app/6726a3b816e99b9971a1d25a1205ca81ecadc6eb1d5dd3a71028c4b01cc254c1" ``` Post the `operationId` in your GraphQL requests: ```js // Lookup the operation name: var operationId = OperationStoreClient.getOperationId(operationName) // Include it in the params: $.post("/graphql", { operationId: operationId, variables: queryVariables, }, function(response) { // ... }) ``` ## Authorization `OperationStore` uses HMAC-SHA256 to {% internal_link "authenticate requests" , "/operation_store/access_control" %}. Pass the key to `graphql-ruby-client sync` as `--secret` to authenticate it: ```bash $ export MY_SECRET_KEY= "abcdefg..." $ graphql-ruby-client sync ... --secret=$MY_SECRET_KEY # ... Authenticating with HMAC # ... ``` graphql-ruby-1.11.10/guides/js/000077500000000000000000000000001414121453000161575ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/js/search.js000066400000000000000000000066071414121453000177730ustar00rootroot00000000000000var client = algoliasearch('8VO8708WUV', '1f3e2b6f6a503fa82efdec331fd9c55e'); var index = client.initIndex('prod_graphql_ruby'); var GraphQLRubySearch = { // Respond to a change event on `el` by: // - Searching the index // - Rendering the results run: function(el) { var searchTerm = el.value var searchResults = document.querySelector("#search-results") if (!searchTerm) { // If there's no search term, clear the results pane searchResults.innerHTML = "" } else { index.search({ query: searchTerm, hitsPerPage: 8, }, function(err, content) { if (err) { console.error(err) } var results = content.hits // Clear the previous results searchResults.innerHTML = "" results.forEach(function(result) { // Create a wrapper hyperlink var container = document.createElement("a") container.className = "search-result" container.href = (result.rubydoc_url || result.url) + (result.anchor ? "#" + result.anchor : "") // This helper will be used to accumulate text into the search-result function createSpan(text, className) { var txt = document.createElement("span") txt.className = className txt.innerHTML = text container.appendChild(txt) } if (result.rubydoc_url) { createSpan("API Doc", "search-category") createSpan(result.title, "search-title") } else { createSpan(result.section, "search-category") var resultHeader = [result.title].concat(result.headings).join(" > ") createSpan(resultHeader, "search-title") var preview = result._snippetResult.content.value createSpan(preview, "search-preview") } searchResults.appendChild(container) }) var seeAll = document.createElement("a") seeAll.href = "/search?query=" + content.query seeAll.className = "search-see-all" seeAll.innerHTML = "See All Results (" + content.nbHits + ")" searchResults.appendChild(seeAll) }) } }, // Return true if we actually highlighted something _moveHighlight: function(diff) { var allResults = document.querySelectorAll(".search-result") var highlightClass = "highlight-search-result" if (!allResults.length) { // No search results to highlight return false } var highlightedResult = document.querySelector("." + highlightClass) var nextHighlightedResult var result for (var i = 0; i < allResults.length; i++) { result = allResults[i] if (result == highlightedResult) { nextHighlightedResult = allResults[i + diff] break } } if (!nextHighlightedResult) { // Either nothing was highlighted yet, // or we were at the end of results and we loop around nextHighlightedResult = allResults[0] } if (highlightedResult) { highlightedResult.classList.remove(highlightClass) } nextHighlightedResult.classList.add(highlightClass) nextHighlightedResult.focus() return true } } document.addEventListener("keydown", function(ev) { var diff = ev.keyCode == 38 ? -1 : (ev.keyCode == 40 ? 1 : 0) if (diff) { var highlighted = GraphQLRubySearch._moveHighlight(diff) if (highlighted) { ev.preventDefault() } } }) graphql-ruby-1.11.10/guides/language_tools/000077500000000000000000000000001414121453000205465ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/language_tools/visitor.md000066400000000000000000000122041414121453000225660ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Language Tools title: AST Visitor desc: Analyze and modify parsed GraphQL code index: 0 --- GraphQL code is usually contained in a string, for example: ```ruby query_string = "query { user(id: \"1\") { userName } }" ``` You can perform programmatic analysis and modifications to GraphQL code using a three-step process: - __Parse__ the code into an abstract syntax tree - __Analyze/Modify__ the code with a visitor - __Print__ the code back to a string ## Parse {{ "GraphQL.parse" | api_doc }} turns a string into a GraphQL document: ```ruby parsed_doc = GraphQL.parse("{ user(id: \"1\") { userName } }") # => # ``` Also, {{ "GraphQL.parse_file" | api_doc }} parses the contents of the named file and includes a `filename` in the parsed document. #### AST Nodes The parsed document is a tree of nodes, called an _abstract syntax tree_ (AST). This tree is _immutable_: once a document has been parsed, those Ruby objects can't be changed. Modifications are performed by _copying_ existing nodes, applying changes to the copy, then making a new tree to hold the copied node. Where possible, unmodified nodes are retained in the new tree (it's _persistent_). The copy-and-modify workflow is supported by a few methods on the AST nodes: - `.merge(new_attrs)` returns a copy of the node with `new_attrs` applied. This new copy can replace the original node. - `.add_{child}(new_child_attrs)` makes a new node with `new_child_attrs`, adds it to the array specified by `{child}`, and returns a copy whose `{children}` array contains the newly created node. For example, to rename a field and add an argument to it, you could: ```ruby modified_node = field_node # Apply a new name .merge(name: "newName") # Add an argument to this field's arguments .add_argument(name: "newArgument", value: "newValue") ``` Above, `field_node` is unmodified, but `modified_node` reflects the new name and new argument. ## Analyze/Modify To inspect or modify a parsed document, extend {{ "GraphQL::Language::Visitor" | api_doc }} and implement its various hooks. It's an implementation of the [visitor pattern](https://en.wikipedia.org/wiki/Visitor_pattern). In short, each node of the tree will be "visited" by calling a method, and those methods can gather information and perform modifications. In the visitor, each node class has a hook, for example: - {{ "GraphQL::Language::Nodes::Field" | api_doc }}s are routed to `#on_field` - {{ "GraphQL::Language::Nodes::Argument" | api_doc }}s are routed to `#on_argument` See the {{ "GraphQL::Language::Visitor" | api_doc }} API docs for a full list of methods. Each method is called with `(node, parent)`, where: - `node` is the AST node currently visited - `parent` is the AST node above this node in the tree The method has a few options for analyzing or modifying the AST: #### Continue/Halt To continue visiting, the hook should call `super`. This allows the visit to continue to `node`'s children in the tree, for example: ```ruby def on_field(_node, _parent) # Do nothing, this is the default behavior: super end ``` To _halt_ the visit, a method may skip the call to `super`. For example, if the visitor encountered an error, it might want to return early instead of continuing to visit. #### Modify a Node Visitor hooks are expected to return the `(node, parent)` they are called with. If they return a different node, then that node will replace the original `node`. When you call `super(node, parent)`, the `node` is returned. So, to modify a node and continue visiting: - Make a modified copy of `node` - Pass the modified copy to `super(new_node, parent)` For example, to rename an argument: ```ruby def on_argument(node, parent) # make a copy of `node` with a new name modified_node = node.merge(name: "renamed") # continue visiting with the modified node and parent super(modified_node, parent) end ``` #### Delete a Node To delete the currently-visited `node`, don't pass `node` to `super(...)`. Instead, pass a magic constant, `DELETE_NODE`, in place of `node`. For example, to delete a directive: ```ruby def on_directive(node, parent) # Don't pass `node` to `super`, # instead, pass `DELETE_NODE` super(DELETE_NODE, parent) end ``` #### Insert a Node Inserting nodes is similar to modifying nodes. To insert a new child into `node`, call one of its `.add_` helpers. This returns a copied node with a new child added. For example, to add a selection to a field's selection set: ```ruby def on_field(node, parent) node_with_selection = node.add_selection(name: "emailAddress") super(node_with_selection, parent) end ``` This will add `emailAddress` the fields selection on `node`. (These `.add_*` helpers are wrappers around {{ "GraphQL::Language::Nodes::AbstractNode#merge" | api_doc }}.) ## Print The easiest way to turn an AST back into a string of GraphQL is {{ "GraphQL::Language::Nodes::AbstractNode#to_query_string" | api_doc }}, for example: ```ruby parsed_doc.to_query_string # => '{ user(id: "1") { userName } }' ``` You can also create a subclass of {{ "GraphQL::Language::Printer" | api_doc }} to customize how nodes are printed. graphql-ruby-1.11.10/guides/mutations/000077500000000000000000000000001414121453000175665ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/mutations/mutation_authorization.md000066400000000000000000000114641414121453000247360ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Mutations title: Mutation authorization desc: Checking permissions for mutations index: 3 --- Before running a mutation, you probably want to do a few things: - Make sure the current user has permission to try this mutation - Load some objects from the database, using some `ID` inputs - Check if the user has permission to modify those loaded objects This guide describes how to accomplish that workflow with GraphQL-Ruby. ## Checking the user permissions Before loading any data from the database, you might want to see if the user has a certain permission level. For example, maybe only `.admin?` users can run `Mutation.promoteEmployee`. This check can be implemented using the `#ready?` method in a mutation: ```ruby class Mutations::PromoteEmployee < Mutations::BaseMutation def ready?(**args) # Called with mutation args. # Use keyword args such as employee_id: or **args to collect them if !context[:current_user].admin? raise GraphQL::ExecutionError, "Only admins can run this mutation" else # Return true to continue the mutation: true end end # ... end ``` Now, when any non-`admin` user tries to run the mutation, it won't run. Instead, they'll get an error in the response. Additionally, `#ready?` may return `false, { ... }` to return {% internal_link "errors as data", "/mutations/mutation_errors" %}: ```ruby def ready? if !context[:current_user].allowed? return false, { errors: ["You don't have permission to do this"]} else true end end ``` ## Loading and authorizing objects Often, mutations take `ID`s as input and use them to load records from the database. GraphQL-Ruby can load IDs for you when you provide a `loads:` option. In short, here's an example: ```ruby class Mutations::PromoteEmployee < Mutations::BaseMutation # `employeeId` is an ID, Types::Employee is an _Object_ type argument :employee_id, ID, required: true, loads: Types::Employee # Behind the scenes, `:employee_id` is used to fetch an object from the database, # then the object is authorized with `Employee.authorized?`, then # if all is well, the object is injected here: def resolve(employee:) employee.promote! end end ``` It works like this: if you pass a `loads:` option, it will: - Automatically remove `_id` from the name and pass that name for the `as:` option - Add a prepare hook to fetch an object with the given `ID` (using {{ "Schema.object_from_id" | api_doc }}) - Check that the fetched object's type matches the `loads:` type (using {{ "Schema.resolve_type" | api_doc }}) - Run the fetched object through its type's `.authorized?` hook (see {% internal_link "Authorization", "/authorization/authorization" %}) - Inject it into `#resolve` using the object-style name (`employee:`) In this case, if the argument value is provided by `object_from_id` doesn't return a value, the mutation will fail with an error. If you don't want this behavior, don't use it. Instead, create arguments with type `ID` and use them your own way, for example: ```ruby # No special loading behavior: argument :employee_id, ID, required: true ``` ## Can _this user_ perform _this action_? Sometimes you need to authorize a specific user-object(s)-action combination. For example, `.admin?` users can't promote _all_ employees! They can only promote employees which they manage. You can add this check by implementing a `#authorized?` method, for example: ```ruby def authorized?(employee:) context[:current_user].manager_of?(employee) end ``` When `#authorized?` returns `false` (or something falsey), the mutation will be halted. If it returns `true` (or something truthy), the mutation will continue. #### Adding errors To add errors as data (as described in {% internal_link "Mutation errors", "/mutations/mutation_errors" %}), return a value _along with_ `false`, for example: ```ruby def authorized?(employee:) if context[:current_user].manager_of?(employee) true else return false, { errors: ["Can't promote an employee you don't manage"] } end end ``` Alternatively, you can add top-level errors by raising `GraphQL::ExecutionError`, for example: ```ruby def authorized?(employee:) return true if context[:current_user].manager_of?(employee) raise GraphQL::ExecutionError, "You can only promote your _own_ employees" end ``` In either case (returning `[false, data]` or raising an error), the mutation will be halted. ## Finally, doing the work Now that the user has been authorized in general, data has been loaded, and objects have been validated in particular, you can modify the database using `#resolve`: ```ruby def resolve(employee:) if employee.promote { employee: employee, errors: [], } else # See "Mutation Errors" for more: { errors: employee.errors.full_messages } end end ``` graphql-ruby-1.11.10/guides/mutations/mutation_classes.md000066400000000000000000000124051414121453000234670ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Mutations title: Mutation Classes desc: Use mutation classes to implement behavior, then hook them up to your schema. index: 1 redirect_from: - /queries/mutations/ --- GraphQL _mutations_ are special fields: instead of reading data or performing calculations, they may _modify_ the application state. For example, mutation fields may: - Create, update or destroy records in the database - Establish associations between already-existing records in the database - Increment counters - Create, modify or delete files - Clear caches These actions are called _side effects_. Like all GraphQL fields, mutation fields: - Accept inputs, called _arguments_ - Return values via _fields_ GraphQL-Ruby includes two classes to help you write mutations: - {{ "GraphQL::Schema::Mutation" | api_doc }}, a bare-bones base class - {{ "GraphQL::Schema::RelayClassicMutation" | api_doc }}, a base class with a set of nice conventions that also supports the Relay Classic mutation specification. Besides those, you can also use the plain {% internal_link "field API", "/type_definitions/objects#fields" %} to write mutation fields. An additional `null` helper method is provided on classes inheriting from `GraphQL::Schema::Mutation` to allow setting the nullability of the mutation. This is not required and defaults to `true`. ## Example mutation class If you used the {% internal_link "install generator", "/schema/generators#graphqlinstall" %}, a base mutation class will already have been generated for you. If that's not the case, you should add a base class to your application, for example: ```ruby class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation # Add your custom classes if you have them: # This is used for generating payload types object_class Types::BaseObject # This is used for return fields on the mutation's payload field_class Types::BaseField # This is used for generating the `input: { ... }` object type input_object_class Types::BaseInputObject end ``` Then extend it for your mutations: ```ruby class Mutations::CreateComment < Mutations::BaseMutation null true argument :body, String, required: true argument :post_id, ID, required: true field :comment, Types::Comment, null: true field :errors, [String], null: false def resolve(body:, post_id:) post = Post.find(post_id) comment = post.comments.build(body: body, author: context[:current_user]) if comment.save # Successful creation, return the created object with no errors { comment: comment, errors: [], } else # Failed save, return the errors to the client { comment: nil, errors: comment.errors.full_messages } end end end ``` The `#resolve` method should return a hash whose symbols match the `field` names. (See {% internal_link "Mutation Errors", "/mutations/mutation_errors" %} for more information about returning errors.) ## Hooking up mutations Mutations must be attached to the mutation root using the `mutation:` keyword, for example: ```ruby class Types::Mutation < Types::BaseObject field :create_comment, mutation: Mutations::CreateComment end ``` ## Auto-loading arguments In most cases, a GraphQL mutation will act against a given global relay ID. Loading objects from these global relay IDs can require a lot of boilerplate code in the mutation's resolver. An alternative approach is to use the `loads:` argument when defining the argument: ```ruby class Mutations::AddStar < Mutations::BaseMutation argument :post_id, ID, required: true, loads: Types::Post field :post, Types::Post, null: true def resolve(post:) post.star { post: post, } end end ``` By specifying that the `post_id` argument loads a `Types::Post` object type, a `Post` object will be loaded via {% internal_link "`Schema#object_from_id`", "/schema/definition.html#object-identification-hooks" %} with the provided `post_id`. All arguments that end in `_id` and use the `loads:` method will have their `_id` suffix removed. For example, the mutation resolver above receives a `post` argument which contains the loaded object, instead of a `post_id` argument. The `loads:` option also works with list of IDs, for example: ```ruby class Mutations::AddStars < Mutations::BaseMutation argument :post_ids, [ID], required: true, loads: Types::Post field :posts, [Types::Post], null: true def resolve(posts:) posts.map(&:star) { posts: posts, } end end ``` All arguments that end in `_ids` and use the `loads:` method will have their `_ids` suffix removed and an `s` appended to their name. For example, the mutation resolver above receives a `posts` argument which contains all the loaded objects, instead of a `post_ids` argument. In some cases, you may want to control the resulting argument name. This can be done using the `as:` argument, for example: ```ruby class Mutations::AddStar < Mutations::BaseMutation argument :post_id, ID, required: true, loads: Types::Post, as: :something field :post, Types::Post, null: true def resolve(something:) something.star { post: something } end end ``` In the above examples, `loads:` is provided a concrete type, but it also supports abstract types (i.e. interfaces and unions). graphql-ruby-1.11.10/guides/mutations/mutation_errors.md000066400000000000000000000076371414121453000233610ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Mutations title: Mutation errors desc: Tips for handling and returning errors from mutations index: 2 --- How can you handle errors inside mutations? Let's explore a couple of options. ## Raising Errors One way to handle an error is by raising, for example: ```ruby def resolve(id:, attributes:) # Will crash the query if the data is invalid: Post.find(id).update!(attributes.to_h) # ... end ``` Or: ```ruby def resolve(id:, attributes:) if post.update(attributes) { post: post } else raise GraphQL::ExecutionError, post.errors.full_messages.join(", ") end end ``` This kind of error handling _does_ express error state (either via `HTTP 500` or by the top-level `"errors"` key), but it doesn't take advantage of GraphQL's type system and can only express one error at a time. It works, but a stronger solution is to treat errors as data. ## Errors as Data Another way to handle rich error information is to add _error types_ to your schema, for example: ```ruby class Types::UserError < Types::BaseObject description "A user-readable error" field :message, String, null: false, description: "A description of the error" field :path, [String], null: true, description: "Which input value this error came from" end ``` Then, add a field to your mutation which uses this error type: ```ruby class Mutations::UpdatePost < Mutations::BaseMutation # ... field :errors, [Types::UserError], null: false end ``` And in the mutation's `resolve` method, be sure to return `errors:` in the hash: ```ruby def resolve(id:, attributes:) post = Post.find(id) if post.update(attributes) { post: post, errors: [], } else # Convert Rails model errors into GraphQL-ready error hashes user_errors = post.errors.map do |attribute, message| # This is the GraphQL argument which corresponds to the validation error: path = ["attributes", attribute.to_s.camelize(:lower)] { path: path, message: message, } end { post: post, errors: user_errors, } end end ``` Now that the field returns `errors` in its payload, it supports `errors` as part of the incoming mutations, for example: ```graphql mutation($postId: ID!, $postAttributes: PostAttributes!) { updatePost(id: $postId, attributes: $postAttributes) { # This will be present in case of success or failure: post { title comments { body } } # In case of failure, there will be errors in this list: errors { path message } } } ``` In case of a failure, you might get a response like: ```ruby { "data" => { "createPost" => { "post" => nil, "errors" => [ { "message" => "Title can't be blank", "path" => ["attributes", "title"] }, { "message" => "Body can't be blank", "path" => ["attributes", "body"] } ] } } } ``` Then, client apps can show the error messages to end users, so they might correct the right fields in a form, for example. ## Nullable Mutation Payload Fields To benefit from "Errors as Data" described above, mutation fields must have `null: true`. Why? Well, for _non-null_ fields (which have `null: false`), if they return `nil`, then GraphQL aborts the query and removes those fields from the response altogether. In mutations, when errors happen, the other fields may return `nil`. So, if those other fields have `null: false`, but they return `nil`, the GraphQL will panic and remove the whole mutation from the response, _including_ the errors! In order to have the rich error data, even when other fields are `nil`, those fields must have `null: true` so that the type system can be obeyed when errors happen. Here's an example of a nullable field (good!): ```ruby class Mutations::UpdatePost < Mutations::BaseMutation # Use `null: true` to support rich errors: field :post, Types::Post, null: true # ... end ``` graphql-ruby-1.11.10/guides/mutations/mutation_root.md000066400000000000000000000022161414121453000230140ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Mutations title: Mutation Root desc: The Mutation object is the entry point for mutation operations. index: 0 --- GraphQL mutations all begin with the `mutation` keyword: ```graphql mutation($accountNumber: ID!, $newBalance: Int!) { # ^^^^ here setAccountBalance(accountNumber: $accountNumber, newBalance: $newBalance) { # ... } } ``` Operations that begin with `mutation` get special treatment by the GraphQL runtime: root fields are guaranteed to be executed sequentially. This way, the effect of a series of mutations is predictable. Mutations are executed by a specific GraphQL object, `Mutation`. This object is defined like any other GraphQL object: ```ruby class Types::Mutation < Types::BaseObject # ... end ``` Then, it must be attached to your schema with the `mutation(...)` configuration: ```ruby class Schema < GraphQL::Schema # ... mutation(Types::Mutation) end ``` Now, whenever an incoming request uses the `mutation` keyword, it will go to `Mutation`. See {% internal_link "Mutation Classes", "/mutations/mutation_classes" %} for some helpers to define mutation fields. graphql-ruby-1.11.10/guides/operation_store/000077500000000000000000000000001414121453000207575ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/operation_store/access_control.md000066400000000000000000000025311414121453000243030ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - OperationStore title: Access Control desc: Manage authentication & visibility for your OperationStore server. index: 6 pro: true --- The `OperationStore` has a built-in mechanism for authenticating incoming `sync` requests. This way, you can be sure that all registered queries came from legitimate sources. ## Authentication When you [add a client]({{ site.base_url }}/operation_store/client_workflow#add-a-client), you also associate a _secret_ with that client. You can use the default or provide your own and you can update a client secret at any time. By updating a secret, old secrets become invalid. This secret is used to add an authorization header, generated with HMAC-SHA256. With this header, the server can assert: - The request came from an authorized client - The request was not corrupted in transit For more info about HMAC, see [Wikipedia](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code) or Ruby's [OpenSSL::HMAC](https://ruby-doc.org/stdlib-2.4.0/libdoc/openssl/rdoc/OpenSSL/HMAC.html) support. The Authorization header takes the form: ```ruby "GraphQL::Pro #{client_name} #{hmac}" ``` {% internal_link "graphql-ruby-client", "/javascript_client/sync" %} adds this header to outgoing requests by using the provided `--client` and `--secret` values. graphql-ruby-1.11.10/guides/operation_store/active_record_backend.md000066400000000000000000000051461414121453000255670ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - OperationStore title: ActiveRecord Backend desc: Storing persisted queries with ActiveRecord index: 2 pro: true --- GraphQL-Pro's `OperationStore` can use ActiveRecord to store persisted queries. After setting up the database, it will read and write using those tables as needed. ## Database Setup To use ActiveRecord, `GraphQL::Pro::OperationStore` requires some database tables. You can add these with a migration: ```bash $ rails generate migration SetupOperationStore ``` Then open the migration file and add: ```ruby # ... # implement the change method with: def change create_table :graphql_clients, primary_key: :id do |t| t.column :name, :string, null: false t.column :secret, :string, null: false t.timestamps end add_index :graphql_clients, :name, unique: true add_index :graphql_clients, :secret, unique: true create_table :graphql_client_operations, primary_key: :id do |t| t.references :graphql_client, null: false t.references :graphql_operation, null: false t.column :alias, :string, null: false t.column :last_used_at, :datetime t.column :is_archived, :boolean t.timestamps end add_index :graphql_client_operations, [:graphql_client_id, :alias], unique: true, name: "graphql_client_operations_pairs" add_index :graphql_client_operations, :is_archived create_table :graphql_operations, primary_key: :id do |t| t.column :digest, :string, null: false t.column :body, :text, null: false t.column :name, :string, null: false t.timestamps end add_index :graphql_operations, :digest, unique: true create_table :graphql_index_entries, primary_key: :id do |t| t.column :name, :string, null: false end add_index :graphql_index_entries, :name, unique: true create_table :graphql_index_references, primary_key: :id do |t| t.references :graphql_index_entry, null: false t.references :graphql_operation, null: false end add_index :graphql_index_references, [:graphql_index_entry_id, :graphql_operation_id], unique: true, name: "graphql_index_reference_pairs" end ``` Then run the migration: ``` $ bundle exec rake db:migrate ``` (You'll have to run that migration on any staging or production servers, too.) Now, `OperationStore` has what it needs to save queries using ActiveRecord! ## Database Update GraphQL-Pro 1.15.0 introduced new features for the OperationStore. To enable them, add some columns to your database: ```ruby add_column :graphql_client_operations, :is_archived, :boolean, default: false add_column :graphql_client_operations, :last_used_at, :datetime ``` graphql-ruby-1.11.10/guides/operation_store/add_a_client.png000066400000000000000000001451251414121453000240630ustar00rootroot00000000000000PNG  IHDR֠/a|iCCPICC Profile(c``*I,(aa``+) rwRR` ` \\À|/JyӦ|6rV%:wJjq2#R d:E%@ [>dd! vV dKIa[)@.ERבI90;@œ r0x00(030X228V:Teg(8C6U9?$HG3/YOGg?Mg;_`܃K})<~km gB_fla810$@$wikiTXtXML:com.adobe.xmp 414 904 1 VQ@IDATx \Ud8L2("<琙٠נuoVn--oV6iYZVfuS#s!QDQ<߻8j~rYkw_~z]FFF1HHHHHHj=ZO!>>999(.!4K$@$@$@$@$P(Tzz:ѧO4nvvv"~ MK&&&}a I     ujj-7Fr > b ̘se5&IHHHHjbq     @{J$@$@$@$@$@5JJAjԫ>c#3f̨;wN;B:5cոlقC!99Yk-Za}78qkye^wɓ'5֭FzU[[4D$@$@$@$@$@9sKU2C%`իWEիL|\rEjսoϞ=Op1dee}vԯ_6mBTT&͕*9HHH@6m*qF̙3GV4m۶Ƽa_  Y]V\$o X awڭۅHIq8s.uMWBFr,Gř䋨SnuK#~% n>+pUYi)*tJ_ʴG3#WӓҥKw=)), Mu{J~s=ooo&RRRkiu{VV_rbk>aj{v^[Y͍y$T5@qcT1I%֯@cxQP&w :ʚ@UhK>S``H.'agU>$ѩ򔪏0Ԕ8[ |Qxz e~y,{v5ipFum(=-xh%V'jopS"c w0QX}C1S/Mdq;a!WkL㵰rh~\2o!L6˽o\@T0wD;ǣ="Z52`O?&o^Чb NJX!݃ͬlv&wUX;p_x?~[/xg H~b} )-^Pg$ngߟ k˶ɻhӹl-^ZwBrՈIUX87AFױbjc$std\'圈 zTl#3r!5'үB}9ڥY@J,Zzr~'^㐞%v!U 3ω`KFʒ>ƛh,IhM|Kn-q蝯͞_Ҷ27#Uޫ3\Sc|؟!gyn_h!Y.f-t@&A1PdocH D 7Q`id ǮVۿTIpC~:b%=)}\'wFRNXVS< IM .#z"%)h,Ή[Jry@$Nc{/+b{:Ib<:G" rv&i,ΦW8Wf<    ~G8{O /TgZh*MU<>>>U=ŪilJ-+5ƩS(O"QE5@<-j!.~3En%M^j1SIT|[t@ "vÔPL]3 b"#}jgM>'Wie[k1~xiw7ScNM+;lp<1~}ySÝr6]u: A֋'ֽŻ3S9PWc)늺͏?Wޚ4ZgWƤTRG|x 6x4#B|:"gb#l<,1m4v1x$V>zDXApKb#ˏMC뇧ogp,yevp@p}2z#g~D=:ؠDl/<-_M`9 &me&͕,err`n w}N?)S0Y{eJ@p wоtvo-$=waўV1j8(.8y mwcp#h#&)qR썙IG6w۱='u GDqLHƑ upB!WJ'=js֘x$`x6D_FԵ#GF.FohVxE"@U6z /L8XeZi,O\a[u0mxe#ĐJJo' ` CMyr5J\f0Cͬ'-lZ4Í-0뷒'+W7HOOÉ b˩84e2w_(,l Ũ~-LUBumf݆cam~n62(/4Ć!1-Pl#t(F\AAFmFOi1rSy;U 3.{RuN∜/f#5I]UCIo-7&hnөoX(#addҥ$YϢ,bm  {_t wD0jD41C. #iӢ^6Sq٬Saft0,ԫ9uD%^[ٷ_PZ!,9a9Ptp܃nr"wD'@#qj?5am"sGGƋ׆ <KbAR~N%Umkp:ܫ'ōH[.@m#WġJ!g7[etvxe!l]3ss)MeP+d@1_;배0Cb@ZfV:W6A$@$@$p(s"3cX'P1CTKMUkM*"hyMroV7|pSTgL~%:s[c%ל+n56wiE:e#%n 6|MPGʅo`,R5[%`˝+:<$  )TD%Ի?c-Jz-q%^kj׮vzaeI-<|0.\PY*wM[2^eQYRuRuN?OZT/+eayʗD(ϕ@2o`H:Kcgf1>}Uz t18ʇY",Ms*L9IdjK F fkrZ?T 7^rK}xHc@w-1~X ],v'h&[]ѧ[zQ%h z6{(,Z )iHI؏%VYhnP>XCH<OCWFk :MܻphjBqbL(?n cܿTg d,=71nA©ٗ>L2V ۯrI,5.mŒW]Pt1?8$ɼ}Q1pMde=zy!?f9>[ صNҺʌ鋫vͬٴz` |lOA}kcLحcZQ?@~[!?#3> D5[>!w-Y^ᭌshmc3tXKJGO`X$FwFS%סο+d*D.ez ^7?TmT2WU \t$rS'Ԅ8TոT@Kj5hZnHݻWzWbcfѲw~\^=e>"\1xx,i8Ʌ;1PLĹ"}JmMWʲ)XTeDAtsxyB| -D-T]C;=Ŵ[{1A "!{x nhh߃qK|f,l!^~wO0{oy'']'7c8C`Lxt9)wkEJIGyCNY ;`tW,vbv=3FΕFD@J L.JtJ?cnū/ժwnpw6duX\Q >$DwFIDzQ-1:ϖlhU 38enU04/FԚ%CP%üU9i[OƘ+K"j=NcpO/_q/')HY}vfyWDK^\^i(p CCeTTU*i c> KT%خZ_.WKFF߫6%xM6K۪% j-FG'.Y"zd$VHH*]3hz6EGcqݸ;U8WmHHH"`Q\Q><=6le],Yjɢe=eTBw%Z-}qFU> ZAAAZqxRyx t,98٤,VlyAnwDU )7KDmQCv鳐a\Mnmddp: s'{g4ti8 s_"BJKCeo: _,8{y($l 4Q>o!fbTΰ[=.:Z۵()mXwq!1}-3BW _(NOۼ__`厵NzՌP/}WSUQMUMx YV<ͩR |+m]㏶miͰ,AVN\ aC_vA׺^k;8'$)U\ HHHFؒV YzwҚjj//-[@ųgjKO',}TKQk* 8PVz7i Hs]wiJ+V1wAr%+Y[JV*+\L熆\ɩ.hذw;WAyuPH:],S,"9  K`7TD)ۢP WbWF\pW.r\B37[2j|u|X:Xג03iMR+{9v,ulmۢ5Wu\u(Y; {Tbd#"F2[ @MԃXmVŃWgPƧ<9"K ՜Xlu%ht(yUB\QZvWm 9)q9C0+O^y*c =22DW5p د7*Z&_ޘFV7t`̸D-0͕L}=?zQ$[/$@$@$@$@$@OҢnę7hܥxQX;y6"xf12qlѷppq8TD$@$@$@$@$Ps(k-- ܏ CШc&CɲӸt4 +8k-M{Ǣwcdˀb T7ww#q= G*L/  &vts[Ջv'g&pyYi8w3/+VvTNIЗ:Y"[W?RnzED[彗Kpml\8|/9z8ȧfo9?е-wXvt$wƭ #2&=rc )b+Fr,?q1H?BZY}Z@{_jH*as#W3ǎDbE78y.O7vF^CSp&9UC2iL|4 5&tν%M]4QCۄbxmaQ'Ȟ¾}n;a=0dY $0mfW=Jd!ylQ缊~;Et W;\tS0'kRxȩp`S54vΛ؝DX2Xd#6cS2L`AgqJE%C> sR,~(8Jan.\Djߨdut $@3ŹM;l4&Pd.Ԣ"䦥Kd/c]{]ok֖wK\G޽Deه8i\lKƮ!9dV8J[X>7gMB-jl,w vQ}]8[۫:p}Ս\=\r`s  ? 2wԚ(eNٷw#aV`un6eh'~7&*op~:u,l=n6$Ka">'>7Yn%= >{=<:_Z/9iZYo-޼-~b.S;wԪV_⑚sO~xKƬ<6eo&߇ys&SUIEZf]$iX;KaSi,;QJ O)_֢F*â<? qZ@jp ~yƏFw+}*I2ho7/D{:LhͰi;06YQt1%Oxna6.EVp{zɷX,|Orsoa|kd|<h?rӵZrtNf#2BnޣA8wBz^-[ /áȽH)~FF^~iD:}%)~rAQ %~a6.ƿ}E:l?p#/8}E8croh(<F/"q3LR^Z>=@gڥs1 # i7oԄx9Fة+ȋqU*c]3NwŒTNѶ^")`X $@$@7!* :"@x_ұC ,MAZܼ[7𶩘>ql $OI-dVmUERW9Z%~&?VX55}+鯘mؕJ90g,5 gu>JcQTNҷ`륙LMH2mxbz?ԇg6eD+5_(ɋQ180- g$̞T 9Vtzy$؀ñpu l,93OճR1874~Jѡ]cL*n>7<]l^AFnWϲ1< ~0n468=Z/.nQ/l400@-WgyT|l'iW,tyģǐ@@t;qA5à^SuO"^iC\Pu.p F YdQazgh,z-'BOxJ9ȼYE9`(㑅hբ) ڞ7{Z-DVEiмUZtMkpJĮ]6Ɉ=.G/j{r-Z(3ͅѠ.2g5@,9\4ojN0*ʮ˲]{bæM 6h |\uam9~ "2Q!i^(/75,.x@R\`啷rLřsyh`Tfl ۲Nmַ+k7m.E[K˒Z& FvUKB'aQ{'N(3M63v7/-fy*͖Sp7GF_\w@naqZO8<Ȳ=-p؊>,ݼ ۶F_^nWu}C 8I@\j.O\+^Y H0oAx8\wb-A> 3ʯcndaLpBҳw[Yja-]y%f@ʽz& Xde1HLNcDW:Br>oY/uU0u;.{mycލdv26mZ CüᎣH=d.G# 1D,^Efc߆PN&cvmƍ;i3:HԆ4)g1;Z$&\`;|~pS`Q "1CP:FrZ8Oo }+Z*hL_3DZP#ѻ] 6pG;3bLfI$@$@fw8F${.:7H3VBmU/hP剷~6f<N<B:Y增~_{JLV$X_אcY\=Y\;J]mf͕\|<ݯ;~huZI[ )N;Q<+sRGoo`o*iԲb9g앱A&EȖCy"CxT%7CΖzOg\~sXXUAw> Uiq4|j9P-7d/uDiG,<.<LE9v9i2wKՑT^V?)9Bԃo3LEUt_v{Hr#{8IšV1JΦs~^X\E:I*.PGNi(œqHZCJ9l5껽]jS u YUf`<4Bdylv  ֮)8r&Bu3)`G6Nfy84mSnցqߌRpS {VfFӶ]${UQqhTݸ$Bjx .`8^2-QVK%%WqUCKctB>mDzX{'+o:>uMUTMr?ei+y(HW,R>2.苰NOj[F~ZxX*9,… G µM**Mm"Ǫ]3`ҾhmE=HTCϮ*`"  *r{+v>p ߼p_Yjeٓtgߎ[}]>+Ken}%͗fc⵱tn,||s-lڑ~J1Wr~z˻__7MMz7+K;㬅@)/9* L?%x+9zFh0?lxa&%7]\t>9G^UT;iУ{򄓛+dA4a"R~%(uUi)/PnNGx$UZ厎}0Wɹxqj>B+C 1[ǰHeơ} x9'ZFn㎲C[[R UWJG&}a$7ˣ|ÛgcҴ #)S‰setqG֤-*Iġ:š6LYg!)f#l _@~wyäثWU..硝]CUf;kP7(P9L$@$@$P12a3X2p Kx;=RE6D3?hIgv~nL4}Z8a-x,=SB"E3OĴ$>O4mg .{k~}Ge5FȻ1(#E;9R?MDH54ׅa+/brt8v(Y@,/lM֙sLR:Ç+дagm?uN-Ѭk̻PMo Gڤ^ޓ6N gg  ; AơH\D.=+K L5yz8\,,j7 jl6m|~nz^~#QKUAoi+Xkql'-з\h h /Sߕ4(>4C./@I֭ \7^|">WrcLG h9Ga'cd_vK$)[p<+s~h'be)aId\cLjT//{ \'-\4\/1ȒS4Y& l3G/,@ͫzqbXk5+5qB]zú@f<   X?6xe~D]}4 hq@IDAT潋%QCMv_a x`>.1SlH:y:w}~tgI9X,kUy˻?ˌs)B PmiF[[Y՞`'[d/7$7F0T"zM_,'d%am=D=u~qgΜу_buJ ~dX:V[|sX{ gcjӘp|!Wiž!❿.GhHFEX?0onII_-dtA3+Q[& RpNz[^٤9:" jd_oh W䁛!LS>VK<!P:O,!Ŀ }-CЦ+NOWuЮ{{KhhܩrvASh wǎh>"7zk IKt 'pbx҂s8u ZAMРA#h$pe욱k}CaZPGD&xX1 C>ּ(ּ7ƛăش?nnR6$/Hlir])/ ΅ǰY7|8 ?W~&c% ײ7@_udOCw}1uy[cDQOك!^A 1W9| eds5 S&Ϫ* d:"2ޅƮfWu+KC=p~quE6h2f[p\ĊJ}[2w셖Bѵ/fGTԖp% GKnmMZJOw6]3M:gJz6ix+*\[ a- VΣ/b|"}2׫WC'=-%dWnvY'Q`?x/|%M#*QYj[yhG\.o9kY]    K@KNTڶ}(̫,^zrVgk* &iX>ujl3đϿ.[     J M]"b     (k#d+$@$@$@$@$@B"l?>:he,DnkLy_݈kg6C ңS@'߼u%y^wLsdџ6!    i Լ@K9+1pǻ P,zkczwACqO)ژ)QǴndffbw2Ou$@$@$@$@$%p}`|}?p0|Tid޵[mmH*k#7=RK-46?׾ZV lTu9oؿehyڛncNUƫq%ƕ+"5F[7Mj urN:3WUHHHHHv.Q#w wߣ숫U[@z #dϟgGw eyw:@H C}Z3r#ge`ۿIdHHHHHKZjq;~~x7jKM#fw~B{_ce)Z/m,< >rxNܥG2")vqWє[Ӯ ׫dMh)cƝxmوf}5qPG Ey>n 6=5@s9عE_5xe_~x! _u\8_>}=p \ivg2 vc惷Ka\UQK^ 6Fk5,R~Z*jh@pU^Plga]ݹz\P6jy89&]:/3JY$Y\}~:)RRTLj:f>=UfHHHHHC DW/'|G'^{R!~ʟn-T'=ְ[o3U¥J^/ cOz0=aVas*+: (/܌â,zә[遚!G„r #IHHHHH rLA,}(GY5]Ad4e+q姃>V\e'KxS9UM(?q^+o/&JhVJه2TEJkmՄnxaN (9 ~f S     8c9bbck\y?f<:}SWUcv@ߛ#Ì_ak5T#}R\ʇk*7Sj:m,88ҍ|u BC+0C9:Ʌl7$tC$@$@$@$@$pQQhS1,퓲Mc~7q𦈟o-D_>Jnvm[_u[_t큖!R5F:KGCRL<ϟ+1Ac뛋-񨧎˙WtoDEoⳍ'"Fw$@$@$@$@$@9 $#Y}& yӸF+ʴ‡fg1bm^M,%\OdliaQQ ܯFa{pŋB#0b`_mU4+[Xf ;8\V7mV݄/CVqK1R2] w 3ʷMM"RX3kmb<ɍ M0br( 8huxy}ܯ mCq= |/bxX ޖ/k(Ͱ= %K*b?h2Ե3I/K[%~Si"s?F[KIWqu    xhH i4|#E}!&E.Txr"z*)NZzs1$    zIIMTs_1$@$@$@$@$@$- f V %     G b3ƘHHHHHlJ$@$@$@$@$@$PA}y @-X)HHHHHr*/c     T+ @#@1cL$@$@$@$@$@B b`P     } Ts_1$@$@$@$@$@$- f V %     G _IfQx!" % 0`퉠pu lW5tu;R=i^p'$@$@$@$@$@$@9D D[65iQP!׬:oóS<|v'OFn_͖Cݾǟ:# 8 \=3Qzٟx"6W_ C&mםƏGWqg_"z,MZO? c6Ԡ ]4&hbi⫨eeaFl{;kJ #UQ(g`$@$@$@$@$@$p~QpS/ǡ#LŇèfK KqS]x/o k?œ1v_7ccL7u]% Pz~;3 C9Q[ 8>X =x@$@$@$@$@$@ cbjr$om7-_[`*GaX' $Y{Y?WFh"WkTƑg\\ nymπQNF }>Cx^N\aq<HHHHH.PgeQ5 ߺ<] _ܿ_ >b]`urP4'dcد8|_B5q(4ZneW 3     `UQQh }c6Z驍ڳ# ᓣgVŖVqBxTl};-[9-|&@ B[t(Ѣ)>$@$@$@$@$@$p!8&kQtx"x̔/N0Ǝrs&~\ދbH(\^EcՂϑxFBHHHHH;9 x]xL{tl?56lSsgʣ[mbj_Vn4* T|u{?o#[G/ی #     8 䈂8eHVN ˦ŬA7vph G>Q.m.\ y^ &_>Ub[9^3WP xR@'B6̐-Cyc:vC     8 $%%Ld6`!Éb6k.~C0 K     @^(N     - 斜bc`kxߝ—zS6q&,@;v 1sW 0}}vLmmP S6EV\Tuxk⿶w8_ىofaf}{PG>sr C\4y:| sѦUґ(tk Ӯ6ø]xH$@$@$@$@$paH9 :!2֕/4ELKȈo Sʲ_w?ʯC=M9]AGtԾR&y+ݍwƐnM$^bkEiR~RuGK}ZaabSnۢ^o+?{*5nR_ 0 ;x W]ˆxUQ`\>Jvv*fo yN Do+}{c꺟ټko/ލ!8b/U%^󯞝C:^Ư>RŌ?ۇs;zx00f;g\{I>_j1Eǿ4tEӱ}| )@ 1ݫ\61R{h^J_ 0 k0潊=5&ϯ;cm;p>,| 1ߎIH yMXf]J^؎g[W0xTń{[V]~oa'eY}3HeNFR`TbnǺɦ_O!KڵO.]H0܃aoj nr|?Θ/hH$(ӵ]1\95,99p]x"45eiyxTWožqD(&4넶ډQ=J?0 f熂xU»_l:|,{K9} M:jѾ*{2tļ*j_j_`(l65g1K]ꦾ&C*uƀq5QM; QPzfߞ?T#  +=R[,$GVMorv29+A2,7'    8 #&ghmh|?VL@.D1/mg-ƞ0NAoP]ׅ 5ڧߓ|Sdї^J9AWd`^AnzXvp/Tv!,Ϭ zHPR9+;~R 5o Dɖ-=DSHHHHHCWAuv#K4hSj,^&zAe|Q؊7~NgNHM9 ͭ[v._ٻ5r}/H0O Mp\չ{0 e &Z 5w?tAjҾ{V9ymK ωG$@$@$@$@$pȵ&*ix͓vC]q`h xm=[/mB=xS_Q"n-Ed6%(m-]p!05qצxPxG#ͼŽDcSxT)ud:9* xHHHH.9 Z)WC.x-;YiG9T "]'.)GuDvJ-RS{k<#pQ{w`Fo|ܺ'&i7H>a:bpDA|u<؀{nwi# F~zF{UMd[ ^onYdlsgOHVw$@$@$@$@$@  2}#} (,ߝQPYгR|K{ [N)+HFhs)}Tk-Qpap3tH:Ecyh4ƷOp/U%΄ITHHHHH ,J-ARQP'%%4~9yqI6{ovrP%:bPE2*0Nn#>6}|㌶t°"5*aods1zŐHHHHHr)\ &cD9NGJvl_4JC1Y98ɰIH T+2µI2%    Y4g1t     84lg#     ;:     ĈQ!  ԑ @2X2b9:T) \Ax<Ia#7      !,)KaYeX"E \@i`&e Pzҿ[+$DF+'H͊R 'r.)=,+,WUCٔ&{8ŃDHHHHHH (Ro)P_Y ٢ }\8v@IQf"A$@$@$@$@$@sJjPQ Wkܓ d-~1kyR ZTsm1$@$@$@$@$@$ f-OJ#     \K b:FHHHHHĬIi$@$@$@$@$@$k 58=ǰc^VGsd;Vl>Voptð3CN>N+zx,0ڵ'Q/gn{*0p:ՌعtdKE?K@Lĉb:VyX9I J4˱2O]\S$?u>J?}ڕ[Cs;UۑaKW_pV5JBKkW( u>~u,V'ci\tC‡kxy/bRŕf{wO<^[6K3!tFW+ׁ[%x}̗8b_V!6s@u%aqS^xf(Mot"rqe#[Yܷ{O o9toշ64bI=v/tÇ`1"pbN\4똰&s]5_`D]Ցy>Rv^¹~ã( r'4ꄶ Jn~1Wbo\?S?~a?Zz,sHHHH.(III!ӛVo ]mv\r3h{>1ۖZ-srV?){1<{ߛ˱UM\zs\gЦv " ~ v3Rwǐ _a5qg#F2 4ja ?{`[ dhiQ0%,*:>Ȩ^~c5[W&!ebehԽ_yc(W k67bd]Odr.Sێ„xj^|I\I I0xS|BD9܊$oi FוXQN}.xﹷ0{>hP䵱hG#Gc]Q)H9w.ye0UMJHQ8A|R•v_rXe߇fˋYS& $yVXuA+ l0ib߲xs8ؠ.2xT򰢤o7Ʒk`l܍^ϡv)OavB2*ޓ0t|Օ2wt=F&e.)sy}@A K,Ú-QbIYHgy=|1PNF*a2$vlI  صnNTU.]#pè{K(-OMQ[**y#VDQN'g-Pɭa4jZdܮŋJݱ 0 T(N<>C%*\+maFʡi(1Ev[F3߫̋T{80q}UQĽ)%ucb4TսUTIzLYz\=rYBe,$DF+@%!n vY{E6<mX'#mЌ[uhB5CuM7U|lʠc;QOnR^{(Z+ƢW&/#Oq\"e -$c!p G2BJ|0ʍF%tkq3 C[Qnr5GD9;&'c(qWgEQTSX ÆvF)eޒ}GJDK.yE9Ua) cR?ڊr6r(mH9}t>oL#    Jst#J<ףqɏJL%u0fj*G`TzDT55r-9qܱn[l_PeQ4'qTq1>rS6"#z3 Cúb*^8(up{??79cթz Z>nV7kpI.#zWY޲_A?pjS\4 {;On^o+.z,7e_fmי:aq]ej7skDB@w$/R#0.  8:ntڸ =|l("aجڋ:%v.Oĩƍ^Xhk(WR—x=v4ym)?kyNٵX"yV2 1UW?}[*&Sk^9*n e{h\+/6ᕿg4(&#զCUWA)ǣJL^%yF@˕zT py# "O—Q\ԛO﷕_Qe/'eN:%R<\ ↊"OV)0ML\w WtժgI('*bWU1(xV$J͑]2nNVzZlGD>Y5['ᴬ^F2lpHAK/ub󜐴L ~"|뻓)1sD>[ҡ}sycⒿ31T+/ZiA)1I݂M"3&[ T4W!-˯h5U8mx? ɇhasf3:TJiW~-?U+r̿`˔ wI,[uZteXE8.&ǖ1^/.<]SUG| |F;!<&    @6 4*يa˱iY3l\wj_e(,y2FL3 @IDATw|#u[9lb9(џmE:Y#ϵiYMr F]mFVH 6jVOk[a-R oQ}R+ 2:FK$?I;ЛNQ/~;EY :\Iݢ\ű˛)=P9+qg' /* E93Jމ?a!)p^qYs;Nv R:p˜0,+X [U̍q$     \I "\4 9p ⿝w1O      ss*;     9Ts=C&     ss*;     9Ts=C&     ss*;     9Ts=C&     ss*;     9Ts=C&     ss*;     9Ts=C&     ss*;     9Ts=C&     ss*;     9Ts=C&     ss*;     9Ts=C&     ss*;     9Ts=C&     s@وu_é 1xzPxug# _ ×@s#NFHHHHHH d/3"oVj7İ-Px[<䜙8m?3O_$@$@$@$@$@$p>DOzķK7:zlAwXuY>zr HHHHH!;AIj$و^~sNOǎB -})Y%vԺ}fj?zmn-T p-ׇދk%w`㉀pyB$@$@$@$@$@v 5v۷vO|H@W6~ɾÇF2a͟F?(z,A xw",:y+V>yAJ]:vAŐ}_>r&-?^\wK)3e݇ZrkDюw$@$@$@$@$@$pA߿+P@'Z-6f(ɷm@D'XxvUz`\xf<{ѯhE,kNjO_ՉÜd<@qϢ?ѿiLjVOWF6tqBxʩ8zsځO{[Qby'4qFH͞哱ɋGImb'EEeYiʍ*p[nF;^y](m{_m`n(d^ d²uQh\y;5g*qpuA}"5;V./nI>(~)elظ;\G94e_\ 'b)'ЩqeX=۱ 5ؒypmQILJWLXx"\F)pD9,W+q%6\cqxh:$C㯺O`)?)?/zrWw"Sय़Y۝x0'\E!     A [q'Mw᧯F_bppV 8Qn/q9e.\ yS;JM2lѢ&aþ#8{%t\bpzqw>䫟o\> -߄v,_:UA+gȇi#}&6=? Ė ^7v~ɇ*R3QX3kyϐ{[wPk:!z8EGTitpO$@$@$@$@$pd*pP/<΢TuORtl"2Gܱ^YUiG\%}M[J=hq\aR8>Otn6mcx T/x?4ϭ򹍅OWtB uuROl~z} Jj^fbIMnis8vżzi6W8qsqgrUK@ѼU)HHHHHH [4D'!2WЗb/Fˈ_9#&˷#9ލ>G˜?e{dcb"ZEVe~6'f=%ߓs.*^?TL dp b5-"!! Q2R"9{'Sh C(p?9_=יq C'x@$@$@$@$@$p ijs.$*z~qTs_1$@$@$@$@$@$- f V %     G b3ƘHHHHH9 &бIᅱw̗5 K!$U})"?qO"M~6E[1Q=E wޞyK # s A+[?}>D_O:u{{JdPw #Kb0pʼn:OChOx) 3ѳcOI*-?e_nIkb\<ۼ><sX+%|`mśpdUzEJg7['E\e+OU}\])i= dw˥(v{zS):{֛"ǼdGfgPW1(vU1sKZN'ޕuA܃n=6fnPې2,6%aTx0i*m;?#JoN2/忛Շ0MݳulwG9dt4oWҔaR=Fʄ# zm,?FXʮex]$e BD!yFH +Ƽ64voIRW:@D|U,! Tz{doP!r# ߿пcǎY3d;0`^?!k`N A]#v;<ֽ͞猏/6~?sQRB9︋KG9֏q٭ѯÝ̍ᅻzjӎG!ݚزkR^1tK'.E kvU2d NNV1Aǣb~fY6oO3ahF6, $ϼuVxPmt|`ԟCKvySʓl~l*39AfΏ~yK:֛*\!HݴD1nu8R<;:vwu2Q7H!o6o )So?T:e*`S:ĆP بuH<]^%) TNikɑ PtGNUӏӁrU%Oyd}`UٹruC! Iw޻kt^ 8/g,i?եea*R\Ôi 1"Y녈륍!vc~f! ;J\a([@鎸ԯdU&L]AG1vu#PϷ0Йԕ깣0vFI];k灺!zvh?NjۡʄmzǬk~Xn q{ǑWw re}v80ly꥖d`*!,L\bh__+Ҥ_I:8R.]B ;7Ґz&2i'ʨ0U@`w%bv=BO4!r5Q!DQ% \N X[t縉 },sĤӘF迄_Gr+~B[s9Ɍ``.fv ONWӏIA&Uo߄0叽 ~< u]扨o)^S#h?-yԼ4:)YgV*R(ٶ/8у;m!,cmcP=_tf-͓(5m.2#˷W̻\f`h{xk wxZ%H! - (~%1k1'[L"r2i=o({!/d0,SM1l3qc];/_JQw*NͭHժRۀm+~ 0T7wL탮K>r!I] R69ˬR31vVL&Ǫ-:;b|$Hȹsn,)R#|4 kxߌ V5ۧS~3 FNZ Qq_ndW9NQcZT,tB_b\ b>bl=,2a:nS6qV+p4PfV|Jtx~zݚgbIکo}\m_aQɂr§RFyqc;\T(u vʷzϦ5?6t#&ֻjK70U\}cGZ ^mjKT]5]齼!%?gKdgqda=UiNyYStUTAvu<Լ o]dۇ/?픹誏aI뀭`.u;Q $@$ Y3#)m" mb&سY͙vH&/Aoy3#K°LZoV.&͸Tlui~dH֊c.ƈQ#֕sgd$9H&;oݦ"fb2dTPnP_d& 3՛cejx?}bL;3n-ޮG"q׍mbE-nG ~Ye~(z;yvuycG_̴ݨg䥆(ߦz4^kHW<`{ö~Gڄ7} z[xwkFfww5km5+`•f/~,]^gPFU' xqw-`UC_[eDm#~ZW3C٧) u9#.Fͤ%􈤎6QeG"n?1zt{Ȋ2Qұ?5 OKt\3Wic.-f@!,&A{WWa/1뮌4}EdeNL7<ggQxwt_*m:?[s8wljYF7^k$̇7;dFmah˄6"mR|tiP?j$i&vʛ,Aζ`Љ;V[Vnu}XRVu13<ÌK&q#}FJjϛʸNl)~K ݓKz$obi&mxkJEv|%~T9z=M7 =oկ뿟G+!'ȭ229gb(;;luj|3`;}-zTxΟ_׫fSs0Ld[Y h/g1aӦ.gL6^6;.QLU;af: [kF_Hy+, J@}k?L f⮴(6#唣*_O+7ZVg/Mb6^Si}?G6]ZZAќ<\./(1vxD,0 Hꩆds]D7TrDI6IH!}R>1Q7i:GWG?F"yٍypx:~N~K#&ot$P2 11;zTCgԐw_MHW#Kͳbg(Mv#|iYbjݴXZ~KF?hdge$ThJU+G5A a8iSaiɨLC~L(+[!,eG+:]dn7ar͝mܮs9aZ!ԵJME% xQى+EV΂U~Xgfn[U2yGDnǫ5"*/kJ"/J[:}: <#J?z[zi~vosVY*Gy!lF3K[r~.M# @F `FJ8Dqpn\sC!Wu =1P3dΥ>*pq-iũֺ0 MToWU:ҙ INn8ȍyGs y? _Fv߲:feT_p컄K'2v40֊-+eٵLҦ9wbD@p݁'d1U~,+)-sƔ>[e4 S_UyN~4˒6[U77jH:--J+;i;;`;k&6ˎSC+º~\׫4uTW}w;[?1i7&EcޒMARBH(׈.N.a^5b,N⒩M(u2^S4.`}ˉ>V(Npov"S^ע/2 '!7/ɀt y0E V,Y=B;u7a19<4` ~J|\xIWV,_>Gk>\qWzTX\c(E{a$E8 j{q&dŠif&<U#dYqE%,X̌NUJ,2弍Y/d-P}1 |+F8*N9A=ߛT6$ v(S7J|V;y>ln-'7M_3۫ŵ=_ZϚzRTb *eBy[-T` G-ie13Lʷz ďlZź~9"M{Ykm߲D^Y׭~rT]"M A.Syu7|RMUDWmpu'eg3A)p-"v[_"-"5"vt2zf* j<(㑴{)AH];>RPn<ں\ <&, |bsTp":ܓ @pkiZneSsQo~eӎTe%!! ɛkW$;4oP(z΅qF!Ԝ5O>ad-FLfn: r0]q`}b25bPt6K6sP3 Y(yKz鲺}5kǣiFrS|eiYڼM%ʝZb)H\s0O?J;B'?JB/"ʡu!_wГUUR??^9#) 7(͟m&wβ;&]ՕLs OxހHڽfSDWU39I?uH烮o]pwpA~&I͂ݻ@qэ(dػ5F:hZT! gfYEwltG&Av:0*bY4HiToa.f!VvzV.ʂ/6PFQʠR'B,rM攽&_wVW[ZfHTeNxhg;{InVxiVJTy;81`ecˍyG;s Jd,Z$EJA)Ƣˌɣ5#FATeYcؼ!c_ X,&IFvQ9 na gr7yx2m ~b "~U@{ȯ_Fa ($zS%C+)AVɴ+ 6TfL7~*"ԙ$ayƼYݙ V|w"+P\fʖnyѾc&sM:$`-/seMyWV洏yR^LJhk"N$@q|DWU.3KW GER>;mEGATo ]<=oLRYٍ[^lG+͟bcv]w)FR_"-JqHq=u25qe ]Sϝ%R; E|4vB/(&<噱xLY /?T @—sR>Q|:Z" I7zrsrc:*of#ƫ:gn:ʳZȠOcݤkaWFˍ:n'VC;iݓnVoBVWYEo,s>Q΋;&i'[J%k^>v~(#H˜7t~hiG_J2fkSZ$Cw4o Zz?"!MX[TT&CQ*YXTxYWN6[At3i Z7jq /390:3X> /IV 7 H]pwuP^CsF 2_ϕ>( U`M'p4gebsHЛb9 S@kɧ8YŶP8Z s^rNw̤Io{<jP7ED+J`:5,Tύ=1%i^20*~}&ȅi5U:"/G4 SXs 6vRj:H"3來י A) @'Y0ؽ[)t{ԉ<̭@lG/X`ĉ7PbQ^@G3V$g܄ăL~J ;hH|cB09Yn2O Hd "S#a|Ybŋϋe J!$JG)U<}24&#ާGl1Y> 'a&K 3LTOҨ%T_mIr㓹tEgK9d'V%Fz=3f|~a&s6Qz#LG(-b"35+n NM'r̼Ei˔;iOʀWtD9ҩ`F[OuEX#nDic_Y+@=sJh1?( .ǏWNJ ^wn(*iL$@$@$@$@$@Rs| +Z     8w PAyGi @@pѽsC@@@Gr   ^{熞!   1@@@D =C@@ :Mc   {z  8*@@t@@@ ;7 @@pT(7!   wn   QnC@@+@@t3@@@Q4  W޹g   DGi @@z|OLbHA@@jZ . k7ar@@@NC5   ap*Z m@@á=1ԍ  \@U{@@@*Pв|>_5LLL&P    *V5Fݠ@@@1$    @@Yf   @(  D1f1"  !C@    heƈ   @@ "   @4a#   1$    @@Yf   @(  D7<1UPk0áef?WP-6@@j@Xbmà   @4 y;   $@@ `@@fb4>cG@@A"  ,@@g   @1]@@@ <@@+*HR[G V8əˌ } ΚOu7jRǝcݫ7^~[unyDcqV@@@?>4%+utߴZ=SW:)_5}`XcGȅ!\Nf@@"CF/1Q+d%nV\:(>7h4c4M?R]Z)h*sp0щ>!  P jv$@vc?tVUWgtƤv/Okהtv)t52Q}z& +օs~oJӰ O7,{Q)^vy:V:+_Q; /O^}c}s;_vԳU]3[_UZO{TY7Rۇ^/ݞn2e*~tIk2 Jzhpj?Kx@@@@LVc&ۺt2V7MHC?n:D_C`Guz7f(wugmǾ=ujf R_;sQ@?oѱ"7ӝˍ<Ό9 ^zTxjK, Pi~޼˄zJQ!6@@Xj=դPmRy.,YS?Cg%ViDl{R.^ڲ?϶k}*]Y]4wKj`uZkģ#1ɬ(f(<'2-2<ѬHLnA@@h6lWM+MNܭ]K^U31ޭ=2u[^2C;,bu~)iBfѮoY =l^Q`¥}:SΨגZwDY+m3ջ睹ZZUZ[d} ղڳ]ktxWS67ߡE˷Wjxy"iyuS;9:n݋_?Y?;VtN(_7Bzfʠ{ ټWEpW$V?VmtQ{v֛ e(R@@j@ &=Rs=Hut>#h-5-F9ꞔ߿Vܛj^<4pY *\CD&VL %m'$gjB?]!:vqh`IDATuÔչM8ls-U/lR;ۏQjxhUW'YlWѬp=μ#-bqRtH;֍;9Qtkdw#cԩ)c^IgCy/a+ָyyUJqz݅5Ɗ72)ӱ,;Ŵ褑O?d뉩fS`1OIڳ.S7//S^Ԙ}.@@@v xrrr*jH UC: @gJJejS e; cc+Rzv~sbUL|O@@@(,,rdaĴ@@@DWN B@@ :oN   +:  8/@@tޜ@@@W ]9-t @@p^9-"   rZ   ysZD@@\)@@t)@@@y洈  RiS   Di@@pѕB@@@Λ"   J+N!   7E@@@DWN B@@ :oN   +*,, >   @ EP@@@ /1@@@ Q4 @@r:C@@HEP@@@ /1@@@ Q4 @@''@IENDB`graphql-ruby-1.11.10/guides/operation_store/client_workflow.md000066400000000000000000000057311414121453000245170ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - OperationStore title: Client Workflow desc: Add clients to the system, then sync their operations with the database. index: 4 pro: true --- To use persisted queries with your client application, you must: - Set up `OperationStore`, as described in {% internal_link "Getting Started","/operation_store/getting_started" %} - [Add the client](#add-a-client) to the system - [Sync operations](#syncing) from the client to the server - [Send `params[:operationId]`](#client-usage) from the client app This documentation also touches on {% internal_link "graphql-ruby-client sync", "/javascript_client/sync" %}, a JavaScript client library for using `OperationStore`. ### Add a Client Clients are registered via {% internal_link "the dashboard","/operation_store/getting_started#add-routes" %}: {{ "/operation_store/add_a_client.png" | link_to_img:"Add a Client for Persisted Queries" }} A default `secret` is provided for you, but you can also enter your own. The `secret` is used for {% internal_link "HMAC authentication", "/operation_store/access_control" %}. (Are you interested in a Ruby API for this? Please {% open_an_issue "OperationStore Ruby API" %} or email `support@graphql.pro`.) ### Syncing Once a client is registered, it can push queries to the server via {% internal_link "the Sync API","/operation_store/getting_started#add-routes" %}. The easiest way to sync is with `graphql-ruby-client sync`, a command-line tool written in JavaScript ({% internal_link "Sync Guide", "/javascript_client/sync" %}) In short, it: - Finds GraphQL queries from `.graphql` files or `relay-compiler` output in the provided `--path` - Adds an {% internal_link "Authentication header","/operation_store/access_control" %} based on the provided `--client` and `--secret` - Sends the operations to the provided `--url` - Generates a JavaScript module into the provided `--outfile` For example: {{ "/operation_store/sync_example.png" | link_to_img:"OperationStore client sync" }} For help syncing in another language, you can take inspiration from the [JavaScript implementation](https://github.com/rmosolgo/graphql-ruby/tree/master/javascript_client), {% open_an_issue "Implementing operation sync in another language" %}, or email `support@graphql.pro`. ### Client Usage See the {% internal_link "Sync Guide", "/javascript_client/sync" %} for using OperationStore with Relay Modern, Apollo 1.x, Apollo Link, or plain JavaScript. To run stored operations from another client, send a param called `operationId` which is composed of: ```ruby { # ... operationId: "my-relay-app/ce79aa2784fc..." # ^ client id / ^ operation id } ``` The server will use those values to fetch an operation from the database. #### Next Steps Learn more about `OperationStore`'s {% internal_link "authentication", "/operation_store/access_control" %} or read some tips for {% internal_link "server management","/operation_store/server_management" %}. graphql-ruby-1.11.10/guides/operation_store/getting_started.md000066400000000000000000000067261414121453000245030ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - OperationStore title: Getting Started desc: Add GraphQL::Pro::OperationStore to your app index: 1 pro: true --- To use `GraphQL::Pro::OperationStore` with your app, follow these steps: - [Check the dependencies](#dependencies) to make sure `OperationStore` is supported - [Prepare the database](#prepare-the-database) for `OperationStore`'s data - [Add `OperationStore`](#add-operationstore) to your GraphQL schema - [Add routes](#add-routes) for the Dashboard and sync API - [Update your controller](#update-the-controller) to support persisted queries - {% internal_link "Add a client","/operation_store/client_workflow" %} to start syncing queries #### Dependencies `OperationStore` requires two gems in your application environment: - {% internal_link "ActiveRecord", "/operation_store/active_record_backend" %} or {% internal_link "Redis", "/operation_store/redis_backend" %} for persistence. (Using another ORM or backend? Please {% open_an_issue "Backend support request for OperationStore" %} to request support!) - `Rack`: to serve the Dashboard and Sync API. (In Rails, this is provided by `config/routes.rb`.) These are bundled with Rails by default. #### Prepare the Database If you're going to store data with ActiveRecord, {% internal_link "migrate the database", "/operation_store/active_record_backend" %} to prepare tables for it. #### Add `OperationStore` To hook up the storage to your schema, add the plugin: ```ruby class MySchema < GraphQL::Schema # ... use GraphQL::Pro::OperationStore end ``` By default, it uses `ActiveRecord`. It also accepts: - `redis:`, for using a {% internal_link "Redis backend", "/operation_store/redis_backend" %}; OR - `backend_class:`, for implementing custom persistence. #### Add Routes To use `OperationStore`, add two routes to your app: ```ruby # config/routes.rb # Include GraphQL::Pro's routing extensions: using GraphQL::Pro::Routes Rails.application.routes.draw do # ... # Add the Dashboard # TODO: authorize, see the dashboard guide mount MySchema.dashboard, at: "/graphql/dashboard" # Add the Sync API (authorization built-in) mount MySchema.operation_store_sync, at: "/graphql/sync" end ``` `MySchema.operation_store_sync` receives pushes from clients. See {% internal_link "Client Workflow","/operation_store/client_workflow" %} for more info on how this endpoint is used. `MySchema.dashboard` includes a web view to the `OperationStore`, visible at `/graphql/dashboard`. See the {% internal_link "Dashboard guide", "/pro/dashboard" %} for more details, including authorization. {{ "/operation_store/graphql_ui.png" | link_to_img:"GraphQL Persisted Operations Dashboard" }} `operation_store_sync` and `dashboard` are both Rack apps, so you can mount them in Rails, Sinatra, or any other Rack app. #### Update the Controller Add `operation_id:` to your GraphQL context: ```ruby # app/controllers/graphql_controller.rb context = { # Relay / Apollo 1.x: operation_id: params[:operationId] # Or, Apollo Link: # operation_id: params[:extensions][:operationId] } MySchema.execute( # ... context: context, ) ``` `OperationStore` will use `operation_id` to fetch the operation from the database. See {% internal_link "Server Management","/operation_store/server_management" %} for details about rejecting GraphQL from `params[:query]`. #### Next Steps Sync your operations with the {% internal_link "Client Workflow","/operation_store/client_workflow" %}. graphql-ruby-1.11.10/guides/operation_store/graphql_ui.png000066400000000000000000001423071414121453000236270ustar00rootroot00000000000000PNG  IHDRE"@iCCPICC Profile(c``H,(aa``+) rwRR```\\TQk .Ȭg~~|AKoZ0գd  Jl_( {a `5!A@ [ 9#1~d$!#I)JR+J@s~AeQfzF#0RD``.߇S{}Ep0~c)N3630Ns8˚ {100b`8 `pBiTXtXML:com.adobe.xmp 1018 207 v@IDATx|E_I C* U `W b{Q;I!|Lcos-#y۝ݾu25lPH J{]KRǁ(HGocK6#   (2ʉe# @!@E?|(۾YK.%G")Kf?wH$@$@$@$@$@c;^,Y);!{@)U$s` ʕSn.gPUw& pq폒E-^0L22N+HHHHH2,jH'sAX#z&UZj(^̑cu/:KpɔHHHHHR&9O!Kr|fY;${co54wɭ;$mi=߂OSHHHHHH H~ 羢QR9_VOl&$PjPȥ3g=|@P8|9KSwɕ8HHHHH3D);CL2]D9sF.o>*XW?@:u(ǏO1WyɓG2+%5_ѣGeذe 7WYn| %uū&ZJR;&SN_`{]1rf~9zRi=rRt:-wLt =m_PRqï|d\fqIHHHHH 9|V[h|('NݻwI@@f)YԭSWg2sO-Z*ޞIrʕ(EJ{xx8xV)"6lL4Q1(QBie2ԪUKe&QQQN#̙SYz)٣GZ@~"lUUa ɩyKΉ ;rVQ̯|^9u2~W q :% eTD0.\\GLL[Nf'#'X͜qY.'--ԿO{2HPP^t5kVwS@HHH CI 7^S+L: Ztzf[vTw:H/o~xVrG(ޟ}tE&O,ׯwlQ /_nݻ&>o޼_i^L`xH! $ޢsOܫ^UpóPBTyCTN:B(0>}q9r t k֬]vɝw8˗͛ɒŧG. ΞNz97nkN-XDž#߆uI|BQ߲e߿߱ΗjUk͒ #|韜6*VRFp6_wֲbr9z쨣˭ZKDaYfcHHHH|r>@?!v%֡{vC#'?b)ޱ֯SQ&WG L6'ݶd+ ?>Ӊ't:H>}qGL"HH-cşUU&7J>;i$ݻ/-[Ԝ&OEd" (7@z $wܲ\)ӧO˖ e^0,qW7Cv-7mLo3 u"($@$@$@$p3 vY>QF=>TB JiOHe5ҠAC`VM>'CwFѣ0ҿO>UxJ tRڪe+fۡj DDD8[K3sFmxxc _tbȑ#dOc0~ͬq P=ua>B'X)2(U+I[`-ߌ֞ҽʮqSTfݺuZ)ѣ#^|(?f6m#f?*Wl_ǵ c!*_o>m)ŋQQnu/`p&M3xbA-ZʣJiG3gȪ+ xx *˔wB~i+ٻGEԑo>~> [n0"@!uAFެY3ߵ[mkתرtupCYzW ֹܱPpΈ'֭[҆ԃ6Hٲekn#!nE۵} kYZ~_dHJٿp>v[4}u6#hA}vAz mHmFฐ;loIuZJΝ;ehѢWtϞ=h";7V7kLfϙ夘H#կSN5zB7VRUVhk`!+TQìYZvQFMtzYXh}ھ}:uJE 8 7#* k:ӬX\_e˔URY}ծUGUm۶Jn njJ9w>H|z0ǍڲuJا 8`c^fS1HHH2<}SQjߞ JQ6PWQAyHX2J5xlYp Y}8^x`xlIyIL%7lh5Vj<3$Ay!TRh/7[lC:)fRCJ( [P(RF *6m/" !߸q2B R)P?oV 9s0 `?Pw_JP sN`{??Hd# b FҥKk … Qm: %⊋/###$ZM$FE`^^*J*9y?   Iѷ{fvx):( >y_Gp+vB¤ɓ9.'w(fBR~<e̘J_dZ92 mRQ]:>(>BAZ7 (DԈ2h F߾ݴFv<@BqCjU`/RᇂO RǕbY9y*4e{I_ !?^))XB*o<=4`kz2^?Qa6(FWe ^kA(xq} ]w3܏8!0^6@tBGFE8qʩicUD[x%l6ݦu[]K>m'W,DB aB$@$@$@ JM&%K:6թ]GvBvոQci/P,Сh2.̝;ݱw^퍄 xb\ <P@ I2}xjowi]יx8Dpիͷp6XWV]o6Ʀy]Khe(. xl'ʷ]>CU.V^GFMi@gdlà LυݮHT|''OUmFSouxž33 )THWZ3m):wtXxq%GU7$z@fE J`.Ӯ]VpFIqjAٷ9L͗R9xOUdstU}5cnH@̛7OE(f3ZL];5k^.o`_NB1/Upv?pk7 ϼULմi^O8׏T:GNly|_YĪ.U(# <4)K(;z mq7 쭫c{:~<ɱR-*F}{*H?0{0+w[~cqHHH IяR9h ]ɛP9ȨF^Nŋ@h9t8aEc33Ͼ| tZk1FH󱮃B|r9Pp/%Քˢ (^ RBJHX8857eu-1 qQS"r3f = @#弐 4_!vXB{L)V!nh0+qUI7}}E (P<ʥ1 "r*xsu|vIiW\S׊&c=E qjf(+QԎ0/ 8xlgkV|*rAȁi^m:&ڸdX/~N)O \}v0< w{0n.؎HHH Iʃb:0C!=MQ1xdwb]ܳGO{l]ٽi5ou[b׬]}< c?árRUgWۓpP(oͶﻨ 5iDڕS,*5wR $/; A}rn!==_^]%Dy|޵W8L#/u!>T r1$;#LJ><~5kj —1λx:?Pӧ'PlG;w~ ~2m"I0AA87(O{+' c`b#!?jx4^Z߆*`JUQ8(ˆ@GaݻW"a|>TϝzDakq&#{}v0!5a;|׏7G ow%ƈkb$*㜠̣=% aiu`?vkN}?5P+8V/8"WMxq`XZ@!   ;B0=~ߴA%Liŗ_8(`߈ao7~Gw] S!oV,+rzmC#>7J$J8|MqgVF,ٳPRD+P?:l9D>= Hǎo 3dCq6;<#ٕ+WK.Vb <-[sRaPWX!˗/>8Nk-CR #(c0cj=!c`3}׭[ש.ItL뇙 `s.qY/<."3,y:aeQ+WЊru3=6!VB? -ZvkurL">.=!_N:˰dϚ՞E>}ʀbv"Lh3;vtѯ>TޠJNB"Jfd9d޻z})qt=PIxxiklǃotx@Ng$K?_JRݲtn}beJL O> ~j@8$Th~"rCU:o +^xGnfeåȎ5 ߵtxG>qu;(Vz:32W2K &R%nTQa*SRy5H '6l Gw\j#E=Mߦ/r@9tp_zxᱴ"y2dSHHHH HVO6=g+崰RޣT=׍(y^])J?rʹ?,GI53{ej œ$JMwJyp*/1gsAyW+~lTWԄ޺@_bH mS·HiXHHHB M(t}RN}nCTI;55+9%JYU}D["vVʽ C3S.LJ/ظ;)Yrd~o}HHHHH:4u<~$[X]9?O2%H ^Yʦ/wvqIHHHHH U*>.~mګy _S i'A$@$@$@$@$@7I?{^'o]F6M?wL$@$@$@$@$@$ϝq_$@$@$@$@$@$@$@7˗ _ P+nHHHHHHn,*7/G'      W XTo,_N$@$@$@$@$@$@~%@E߯3      2EDD\$@$@$@$@$@$@$@"@Hs?$@$@$@$@$@$@$T       / }?@.HHHHHHH_4C$@$@$@$@$@$@~ @E       *" PdHHHHHHEHs?$@$@$@$@$@$@$T       / }?@.HHHHHHH_4C$@$@$@$@$@$@~ @E       ,C$@$@$@$@$@$@e/b 5C"_zB wyS  '%/_KH*7~gC!pb&YQ}J$@$@$@$@$ O%?ʉ??\򠢟xA$@$@$@$@$@$@THHHHHHH '{ @%#{ .x<K.ylg߈~gIHHHHHH Iꫯq_'uE>|4lPJ.-jՒ%J-"ƍ+W$OUծ][kҤO粟3gΔrI۶m<}p; V>MWX1}yu:'NHNdRdIy@r!0a 4H%K:wtYo.P^ Kʼy矗'jCݼ1b={V6l s̑6mڸi$@$@$@$@$@$@$\׾=]w%˻ᆱ~X^~e6<32m4f|ZWs~ر2j(8pvOo^pBY]VAE1n#     Ho| wuCG^sRO?Tr)SŋzWJ|ldȐ!zԩS]?CoѡuȀ "mIJČo^:uF$U6qiz&U^|E.q:+$Iu|;ʼ jm8lVÌqU}K1n+/66iH/O!   'z+R/lvRĞ:jt'Eǎ:Ν߼y^~G B}$sr뭷(G}$!!!2x`A uIc+e CTb?+=gr;+RIYL#m6͕f*dݏ$`MjR(t[+#H]C= \kxWZwZPSR׮];Rr.qx:BZxWx衇?"8V{[nn y$@$@$@c] NrP9 q1gMK9P5թ.pqeYr!ꘔ{Tzv%oU?$sHzH<˞=e/Nm&G-;o.>)Nxrxx͎uf5'E}/ʑcP ^ۣG(-[U'>[2c q5u(֊~"rɉU%1y۳_j}=TrU*'K2%g]Psb^Hr'CCi3C@nOJ6@ glYf>)Z-Xhأxt;ugkއ }myo.TUO0{Ow 2*>uB}z;C5j(/(תU+0`;SKr1] rxwtToY&FKݝԫǞ<%PrzV)zmyٲJ1\S.|~^396(UBto/5,Su{GU*HgH-3c&DDGG01Ί+t'&-Ǐ)B0 Cf!~=_IHHH(98_)ѳnvj9xVYu0I\%qv?(.|~R ~2i$&Y9xȆԩSMݻ;) fCʕu-[*kR8:'b;vq^@,޼A~ e[לKke+^, ˊnwR汾gϲo{sA$AÿFIX沪 9rn I%:(e JfusKE6Q4\P[>x .78OH_H 4o uG*!di6o5 Νߐ2Oeo;9_65LJ WO-ꪏ> eK)(}zj]  mڵg- =2zE144TZlpX2fJѣx(PNb fb=ʀ1xL?져Ca=>c< Rm * Cpm/^\ 0XގWZp~qwkDG(,0 N1M8!0NRkLJ4Ha4h9nHHHC^kȖOGƛ_am(kPevY֨u#֊oɅ#Jeѽ%ןg־llS?( |=39r>b5gٝ *s:ޫHG;`8i^}VE1CBj$[= CּXI/` _ju# iB{NĒwv\hwN XЂ ceqߧ FhNH+ڸUm{pͷ${@PU5w/xo%:#G_|QzJ Ppas}N-0M땋Y)ft%qgI`W$0$oIlC֪*sd J.hGH^BZŷYE=!ĒS鵧ŪZ~ agJ_S{cr;OJyh@)ҭ^PgT|V751\Q win,LY,'?s/2(={͑ڂ(g:2ۼye|.4ع[.]tژsBJEժU(. 3yEQbEFL/zF_- ?3&_IHHHuNag)eP![6Vy5"irx#BX=gtP3gQ:oG^$ yDv|6El偮sm.w y*9QtC*}CϚ]}3u ªAo{CS'7em" fֲH.av%rVq[b|'7W!Ů7a6Ke$N.WJ>aJ1LQ Gev{d_(w;",Lۤ$#~nCMsilzp[_}|'N&y̘1.1ʑRTYOMb'};9G $VmixOn Жy0 @ɇm֏qaU.D} uhԥmt|Pb&u "&'M然06dU:A(mP/L>N[B9 @i+hHXc|Xg?(8|DؕTcЍR? XC<k?'F|X:5 Ezf7c#n,ab"G$@$@$@>@8drm{\i@jZ?hcCe,TP!a-0xߍUjyQ;  u ǯEyn8E:ܪCu ͓U+HzG%11MRSzUp> F|(s^ܩ Rnmo/)>ʄaMh^^lxH٤t㖈el+ncy6x`B(+ox݉oxƍ=P$\ <xVwy5)ġ={3NJwe=*"A[wʵ+|.F[+KU>%UTQYvW7̶Z Gɖw>(uc<*Zs-ex}5F{`XcU%;z:NF#̟0]bKWFEW:   0BJgV9#r =< *Uȕؖu7Rnׯ4 ׭y]9PWs^M釼s(F2eVX!_׵^$S@2ou>fSλzGGT̑髜U7M+(ʒ/"%?qiz7xQQ*PRѶ % P8ٳihP|(D!=D7߸P4;w: eX/(0H0Xgn7fZEfX)uIHHH %˦J߻7PcVN!}ߟ\*P7t*@NB3pllȻ7]S+*~aw8^sz[0$l]<дAOo_o5r:?KPE!0/>u>y+ {T=F) {}j?#pSĤ.myMG;}wCpYx۷oF> !2OoGjCX>cQeӔSAp ^{ੳ+] %U1j $OTTs Z8,ޫn*SU_3ES]YFo:+,Uq Jτģ(ƗJJnCszvY<Պ` */?\Y^=jTVzP/ٺX=TX TE6cs(&HksXڿuTXrJ!/sSؾjL({DW{ah ByTAѣuwT7('G%yTG$ mmXv#ջm4' ء~ o#hs j> *蟉:vfWFb%/D= Fpln:d?/Lj ;@C Hƻny(܇׽SjtTǏQԳ= \Mod/<|<{PE]U.Hmnן\'C@0qgUUT2yk^'dY]')w+"v:R oۃTx)ܮ^w本{CP{S3Vgc[g&q〚{όO)5fpc.x1&|`ڱ'x"AU~<^}(* ?dD `j-ڵ6L`UQӏ8gc= 2Ic< bUPoӛ%HXs{YgMʨ 7ےt+yO)||I(go{ /z{\!(_S[)rgN"4o$P} dɕüub3/hա/ennzv^}NJX@EuWP@1@~3GY>gϞLe=LevPqB`ݚ@_Dՠ^_|Ao޽`۷VM#='9PM}mqV0<°"ɷ/91U^Qf:,゘Z>?#\S˝Uͥ :$ʙ A{A<{D+APHr MwCpؠfzPԧx#bQ7ʲu0cs* 3pΜ9b{;?_Ӵb\-q C>O\:   ieSzpNeG&vgp]<#e3+U-[#ԵbOmN{%)ih^x4Z! :VP7oy;7}%+šr^EF[T{)|T1t4@D:*WQ=:Z]"{ UʹNZVoݒN}R{36`L)Ȧұo<CȽ{$2LJ?"}R9 d\eUmAM?#sf]zy5¦Ι2i06hɰ<:      H*$@$@$@$@$@$@$ POݟHHHHHHE~p1 nTSã#      D(\lL$@$@$@$@$@$@HHHHHHH Qxk~zoMHHHHHHH^O{G'LlD$@$@$@$@$@$@i9(IHHHHHH'T}F$@$@$@$@$@$@$6POHHHHHH|"@E'LlD$@$@$@$@$@$@iתi4x$@$@$@$@$@$@ًO%4:Y斜u\7wF$@$@$@$@$@$濎U<5KN$wEiA @ O~zPi+Y2rH8q}5ߓ@#Knĉ mӦMO8S{. &uAɚ5kmnj#xH:]v.+}z|b;R3\3fV $8TX=5kٲev\A$@$p-߹[}_Ǚ9sI\\tաr 8={/ň0vm'uիرc}v {7~$ $K7'ɓ'["\qvk[.,\P+&tkݶQ)RĺJ/r&\A$@i@b[SS{83y}VJC8$coײnO_K~֬YҹsgiԨ ڀB)I 9?\ rEW̺dWرl޼Y!ߝ argF?,ժU]믿JXXz]fʹ{ڒWvmcU`o?cgϞp= hxkӦ4o\`ڳg*U-\gWXQYneɖ-tAC4u;֭+=}97 @!wʕ+H3A׮]]DDnwm޼yw5*E5Mėq- ׯc6mTn*7h@8t萼;?rOZF" Nw}'b-e<C<=S̛7O Bٳf*~mp;K.9r = O\?.Æ cgɒEB3"/܌ո-B1itȗSְ=)"w (ًۓ4x*l~``<Ɇ ܞ҈#8=*S}i(믽ކgyFKի|KM0z)=ƻᆱ?D?HP?~pC'ٶm9rD79|Xϟ/Gv ~w`uK! HVN#?f͚駟:)<_xF'b ]30YJq~HB~> 0;ޤ% gԩ#L7nFu81F@@N7Yx(P@ׯeS#SLZ釣} j:~t 9k>g$אַ$56H?7ycҥ͎eoH%&|!-YD-Zp4Ǘ?AqȐ{NnUJ?x`ĻKK@!W)kpzsV̙3:Z~ezޠ=z8_\x 4תnHH QzWTIBCCoAO^Μ9~{5M^uC3{ p@8- S ҁz.P[ti2IgTQ&bdɒҿ3EHH3<\4 x.^nF܀(\ > ;DO?zjӦM z02"gL-@ԁ1M`_SX綒|iRo8zJ2%ּTy Χ 9kT4P2e5/ 'TۥH@`9_i( ͯF̘''M}+ȚȩۥPR${xf  _Yz=h%E+9֩t(l *8Bp*8_{u X VhA¿֮]kVy|J>KC4YUla?VM4DPHH >|Z ~ر#;1%}=|/C|G7o"p\  4(DA9"bG3E9q aD ܃0pAg^TȮ]F2~W6D]C5c5c@^=:I-rfnwIz{юe7c_sȋRGe#g/2#~}J?!1G$[Pr9N`\0vy*'e=J5WCⱗLC<{4fu=]rdR)u_V^)=vg K^VH`REy%[P!m@$͖Sq" ʸX|w/(cܴM+ß]rm_$ @ Lߕn McxTF=T㇒Qk}B#h6XG!y HO|nC>b \*xGb|7**JG"qĉګhM!HЀ+H jS)bwpA5Rw%ܠl#^t믿tm,$=!{;LQiF.YJ 8R_1WDcxN޽sQB!G;̵bg 8)Io?a0:ꂓ (KHc;6afBn"h0C*-"^ylX 0&~Y= C%c9)G-'N'}lj me݀1"{Vֱ2S@|>c*Ju?<7ÛV=OR%M}M}sG2kX>nr܄r)!9puB񣢿3@›XQ!MUH`# 1E<:?p'P yiW H݊V]&CARO(/mR\9 Df *#ėqP||Y2g3q'/u\Θg`[pM^2_Dag O:U?8xE<3 Y'qg X8F܃&]^H@: o({TEDEˈ}NPpi3x2yq&>W iBs,&V9P%zF۶-y'7lwPX~Ow9T| RQÀ%sp6B,?7Q<]`Mj<갊#LӅULx4,vpF{3ڙ~ S}/Cif2jv/p]k ۬Wvwb6  ?>@Z xztcG$@i@b[ egȐ!<\DB;ҝ xnn 1϶ sM/_у_o"n݆e< lFBd"r}BvfarHbDʾxw_)l30h.룏>Say#x6tBZ \s?H(0 H׮]q>G̈s:32"&Njʔ):U݁5aD=zc6%w>84e>r󭂜O"?z]U׷W}IY ׬j觗m1nfuAHh::\Pf9[|Tׯ퇲z[ڻ_u^w3_3쩝/`%ʓ𥟯m067c`ۑ@r _}Lj}k_Ʊ2 un|gS5Ex^Ft#tc:I8}y=%֬OkܹIE`{qU?"Evkdy{]UL8{ד@r!̾$@rx o7'/x$c=IJ>SAmܧZXG!E Kw Sx~j;A d$T3ҧs%     H'H$@$@$@$@$@$PH6ϕHHHHHH ?b Yk7p8ůU}r @"2K%oo [[5QNIgǞ$@$@$@$@$@$@$POu HHHHHHN~ٱ' :TSG"      tvI$@$@$@$@$@$@@wVonniEDE.B.QE@)|,{9g/0sfv<9R*O,t8>, v:|{8;u8d᠚gɔ9dΖ-]ݹSvm"9JWiپj(ZX+̖LYHd| K,[-|K3[xR7J<$K^=ٵqd˟^]6d$ϔ{dז-@ 6Μ#GmRʋb櫽vɞm;5_Sq21F`sFɖ7dd߇oo̙3?Om &s/_>ɒ%K|Qk 6HBhol1^f˕+Wxz@(PXQ3U-ƶm$Ŭs`xҁ ɥC`ʕҫW/;wn,^zŊ .c9K;OjU۟ʏ%xLxپ| _}_y3YC{dF͢xo?JZͅuFSgJ:-e_2J3|;7͙/6=O&?zwa36;'nCqcq-dT7eإy %KNÐ&i9cʏIK{6ɜ#[ ҳT=S\t5nT]嗪M"}07w:۟\?G^=0#G'1YO^O_5_4~ϐ?ȻnYvy<>`?"Y1/ɒ= p8USن?cbN%_| ~iV9Z׬)ϽvfӦ+nyiC.l,\;!~e]~[™/}F,]O(m Ӊ/,1X|rWǤOJ*%{Y;ulNu/[L.R)\{Z?e{9GsdI،R׺u(+WN|dk;wT2 aei~_oɬYAOO:uHɒ%xҨQ#9rh_=;ըQCn馘{ǎEq߳)UF!mԡ6}η?o\GGƎc1]D C 'x2w}zj\|۷7zygyƻSz|**^|U*1=rb3ڤcv^W$'} ?ZƟvƚb/G zE73.U_qg@'ż"bf&".?' 'gvZ0mN)z6Ⱦ2oy3i~M|W5=Aq?cjX/g5eJ*SN9E+oIvy^zN'k4`?C9m' y5/"UT}?,UVSO=U¯{ҬY3eNa=n8Z1J]\s 6lT\YxfQk׮1y4X,詛/^,vfrWFy8or 7(;&u2m4)[l*c|X/֭s9ÔX֬]VƏ/gSZ֗^zI۾XeKm~gekiK@/BO?TyI"EԞ2m'5kg;?s2gꫯ4}ժUo=ܓf]b;Rn]͛0>`*nGAiA+[|+PTZS p%BPb6[K*\FNCYiSR%~9g_`__y:ڐ9[V)uVˤϳ\[=`2XWFȝ/LJIּyB֗i2g]=d|M)]sP W޷`Syyʞo-o1e&}H@Ms.) :>B 3kEc \l_Z\t4MZ&$S;+c5w./fԶ׸l@QVfZj1={2RpzKyQag Qcv. 'E]> ڵH{77Z}0X$"4\wuʸ`91xZ5I&I'oCԊP|a|^yWucy/1CZwqRfMfQ߈#ªUq2?VH۷ :mWRG}]}4YR]ۖ.np|cɺyta<Q̅1M7Oh0Ŝ:Nd"'Si>tx>8h f0#[*P3Qz ֲeK`/ ߮%,hR:R^>H_}{?0D0.u?,f͕ObRޛ5]z/S-fC.AΙ8Pr(svi˝~WVmKR֮ϛ"_{Žo=L)$KF0Yysep Xg!^A=`Pfy 3}&?jY; X5} NsNi~ru2 |*w\Fo~,"a^pBs52|K[ҢwW)HN`>:yr߿zaq! Oʏkl{}i!_L BX4.9_t7{?W Vï#8/D`_J!cQuK[fZo1dLKjc@9 de&_|vS7eʔW4h20h=Xۄ06m*cƌ?^aO0P9DEӏR&hA}>4ɟoFt颂`P)̀ 7K +vΑ{XGkz _{LvZ"hWe L5͟юcm`η)l ?oeA$h`QPB жm2>=U]~Ee]&3fP!;-:>nQNG<tQ04h0i&,v?tQAmͺj8ev`UOJ}1G``DcOkG?y](қ|I֌M5]?uFԯ^O׼r}*8B0,&z.@0{#:خ5}ًV`]C\vDQ jLO|4w1Ύ{>evo3"я= d2tQ*4R\#T;J_}FktxT~kzzRekA3D>~2?G*2 'g9v/PV>=w)>_6^\]7ǜy0~`@8Rt !\c|`4}/>A 9Yny#/AaڿR DGaw)!F-A/ޢ cn'i}3oZ%N;EL \ ;A/_a'_K<#W*$揍a[ argkoy?0' x1D+RHJ>xuk?0?ВL >p]6;foQ[yqJh!͎L=>9s~,cy ʵ^w$#㈛/nʂD?' uӏ+1 }00'r/ocTRW\z]ha3{ $ןמM ba![t&~Kٲe|* l^w<8hFM?R6p{d MhYxh_4Oh?pah4hb`vsUm~O-y)z?}VaiY,Yh4@ۖԶM1 `}aYQx@IDAT?仆y_ AV,Z|\*\[vg ~*~jS`*,cϢܞs-07U8f?oۘ\[{D8?{_z^j뛌*X2,&CnD:{va0#Q۷Zhp5Byч0|o[w:uH~0 Ad&P|̳])/eI>4$4-sj$72 ?QniMKĜ['~X @L2գ'a-a X9 7a; ڬG0hXF!"K2jѢ.xѦԍ-pcJ4[(m;Kޕ ps@.ǘHv=P&,MKoB^O4L>9r9ᡇR聸Tpuc%xtJZk(=F" kcXIv|V /VO. E |ڴisB$  DGs )jf?.Ѧ1I#վF~Le1G%$ `8څ?-941 B',YW8Ň} II ph5\IA!m ABhVǶ9OP&~h1}.E{FA'@2ϳ??m3!>-/_cl=W̙3Fxܷ~0X7Ks.'a̭=y3Ļ{=&{ů~ѢE tKF 1w`aTLo߾gz96$Dg1{b[*^-1:{oi~8g3G֣`,,u4H;ץ]k\a_է䢅ϝ=25Z2a32^/M#.'+ gɠ??PWnO?`.+Z,E#$ܞ3O}6~l#6ϘQ?7~И~A[4e~CL 獑|)U︞lJڀP {_'zxMO:Nc0b9 ƣuZu & mFZ4 Ll"¼<@~{;O< o@'z^X`CN4A:sR#, Jm/1GQUSv/^f+Ns 9d(`r1 6?k۹~c&Z=c"/'=θ.;mE ^ T"(avKzAon덥X@?#&Kᅢ_6 ٷn?| e¶}Al:G[K.R7?-\P}us  ?"6ծ]ip(f#, 9}˽DTB݁qҰr/v?8 fa/ܷqU9ie&O#í0!j Dep b a#\rI'.ԺdzxwfޭbMW->o2hV֌&~P¢ Zuzu 9ŧes45ӐYPAh1[ǔ.\@& |`WHN`ljB.Dy&f/l.*,XzL~1 m_?wo\`j>D5eX8fc<@~h_klui`>;_˞&1n05m?["d[k?/d\j$6u5[frz?^bztό]sۣ9A3Y\4\#/,+Trl9ł?=ߕ;Ly_-ұ..)0("%Vk(M@v/JaS'~,-x& )Ͻ>M}GBToiAHxst7y*^w7ZLm|zsojp0Bڅ?B  ,LFNҟF^IדP[|sl-SgWfd @nx@12{p 9c&߼j a7͞ߋpϫ{n{= 0i#P췡z;MK3,OXZzıƲ_obݠ꼁=&[,&(Qq bBef8\l bOt{5-W{%.!MCw‚kF@k9QA$#\4va, 0QXO!%(Bp ċ˚F؃[ fwb0X0cA@<VEcua ckaGl4aZiԾO!,@駟ʦM ׅV+Qh>l xb5cǔF1W_pd{[E YL}Q ոZ?.N{x>0ŻuMp:჈OP2 z1\O+20'B9 I2B(h`BU4X^齏M@8oc$#20q1gz#s€!'FX훪XZEi7뜘N40I:D40ϬbS7&b m2eR+Oi +[nLȭi/gMkG`h`/b}2kޞ0O"ό='>lg[_P펾xF*#_v;ۚE-N/j,F! ">\Fo 菉2F 9W*3 lGH>-\v|נqv%.sT_D7_V^ `G*%O{cF""PBK^~{TjI#~6 1R7g4=ˎ=fV(i)g,SvʋonjE?@t2ygvxw3Pjςd#`rqm 6A/"|0cexK o'k\"x". ~JTogT\w*^0h!Ɲ92?S&? :kw FDQ 3׌C\{owp+%@z6xY~@<IEeeK4x#CY VOU3?["N ^a[Bgx& )^0H~-FSwun%QQbЬs" fvD5`oM]l~hy[O:Q,a@Ol=e5,1j]¤%[ѣFƶ#K߾}'~i g1Z)/օ6W0 ??,a^0͏GhɈOf n6Y8o#^a%䏥[A)# v'[7J0F吝=σ6-Bpx… ǻٙNHF0<9g2ؔD/f}HzCĢ@H~)X?f3'<zg)l f<1qD~qh݇G įfh۾b2a&:G9 llF?R5 sD݇ hgwY/ًT nzއ}*"*D럽p;6n6x\5/ߖa$þ<[7,(Rx%=%5,8?k|,h[99-ygYZx ;1?>譹`<{5j-|{3I5 -n?oLqfڟ?u^u~#x-h Wg aֿ-S_[z!Ͼ_ϹF6͝*T0<d-ʨ曵؆HVIׂU^x1mL%!+'O9?MAeΜb^nz MdQMS$؏D3AXMZ: *q2/U6e&M`nzM;ڏ=΃̅3?(wIz/dO^zVG'OKʹ+ٲeS&g2ڵgØg͚Uxŋ$J]k׮/X"Ehwql߾=DvZ;/^ȴRuUϼ4{2sLV<`/w}','sZjɰaEӜiީlٲrWٳ/4j({[1j{%/AF:gĥw=S3RV{Sf}ι˧;1nθ4e$|5k6o0e)GM\GMi3SV]gÍj6X>eZ5mȱx#Ja<k&NIГJyW;RnVe﯌eaaƌ!;MaNt '{zF},)SNղC (uޠ`㣏>t\+sa0\p)^ziO/BH'w5ר A10s `f842Z /?q#2͟ t϶ck&\2_;KO&~q| 'ˇ%Jb:_0r-SK.Ybv-o4Ȗ/ݱ#MMїR )B_m{l_F1GcɢfY$覹 d7_7iG?]M?/U cVd|VRf±fST˓Y-e~l_z|Eb:%06@={\#wݫY%7hY3M[Om2g*[,\ SX18Rmu#`>b~SใS s{D,*_FJ *V'd/T0Qq2|mv؀'l.}J6͋ԑ #bbԸ ՗kQGK'YA'9bO`>?"]P_;A˻;Jðu]% S,?Һu܅;ThQ=&{RfM1Z㘢||1Y=p-ȗ_~)O>d=E|#G38_D2sLLzRJA5J SPy.C bKBF=0%BUc7`Y0X=@>]wmZf>%_l !դW:Iϑ 4L`=48 339N0NH޳IG ꎘ&W 8k,u h}F~y#xL*Ӻwu'× a+_~ '\eSOy<0=rϑ#.Ay7&Bc;aYfb4ʈpׯ__𻇂9q`*~vZy+HQ tEv]Y+ b[o%0~"!tb,=Z }e:Zl)e_czBUTQ%cY!#F`!p8 \4Iow<)O>_h\:s)SRk+~RCBԺ&ٮxeh1Ϙ' „iן\tE1 1@xhd=㫮ƴ`%`U/n[= C}YMBh_kHoi+W `^w M\'X3p hLeҤIzp1!{Q7ިyޑ<Td7gaEF5e!Xq6mhCaz7fB0nIʗ/oC!p$@ ɩ|TMGӁ$QW=Q1ck[?<4k7`[vhwxVIz?3&-%oqbDr^Ζ}jlS3V' 5ǒ똢9nh [4{FsH`/0>0RМD1Q'Ae`P$ٲyK0*h1!ۛSm%wKfmXG ׮00] 4n'WnGuj;'kr%ژ򗜣x0&>u-YpL~{Q3Օ ]!ڂ BTB0O`,:iUoJ~=*F GgE'\P<؏2/FNα=1n| 悱z*_Nqa)o| u1Q!L?n3%Fˇ~( _X11+wtҪi2˗/Wfm6̟(u:T#%'R|2AG+^=+;w-[0~jQ>QÞWB|Z%-z}]}'{8r!GફJ|'$q}_&Xaf3#C!po^Zecʼd_d+WL / 5 d ;O,L-O|Ok0aZz9{sܦq?g c@.wߤL~~&ߖE@$I4mGˏ:0}m > 'Tj%=?+!>0^}J6fC0EXe W=Kcwjth sliXUɦS0izS!/:J*7<\U3O >6fɨ8 m i1ll]L5:1ovy= \ЗqLBhǟy晘7bduc޼yseo9Nl')^]h֭B 1=x/m۶z p[ sjl6=h3BH?ύI$.wB2{a@ C`}K~&km5ޱnCHuY\9&\l^iuF+, `$\5Rj,m&@{^zQYԞ gd B揅1A኿ s }^_ۃ8AXha]㎶vKC*b%_Bwγ7cz5b[!0cE\a̸bFh!X/Of1i30H1V8&/  c%O};֮5 JgE Ko "~fDhM:N4 mNڠQ%B}A?&NIp5 hlF(DOlxҠ4 e.2qJ֧0BVb4bOķ"!Vdj%UXa<>Zx #A0Gnjϴ~a/lqimA\-%wwyml1=Zwb `"_Uß.9FPZ% #p@`x{N:aD(c#@` wXѷe&T [%NB޽"jb>_lkC!pd"MY:ѩai|/Ps.aAŠL eJv!WEsIxˇ2@[t0 a-f[MCC5̙0cm!%0)DCG[4Ky X`~]7s0•_5>&F>HgI6%rÕ5G $4CkT1AS%bD|cjl502|i|˔P/;T3A*ҰP2{I4Abv/Z|~BvZ!H-Z@{h?ŞGڡ`y. xX@`^wM5~Ho.C!88`FLmnUc&(#BM<(CERmᰛЅ=7n(Qy(Qha ǖr .T3vLmDu7z0ho65+Q"?BCˎa駟w'=kwܡ?5Ҡx] >;~uEy eAz٘_}պMwRoN>@CM* (#CF (D>t,jZJ1׶Fa$^h\Ld՚h6)BTHYw/*nj߹=~ ~,~DMf fb[&ea3$gȖh# dI¬"{9[;%uňzO^LaBX =!Lͷ->P62T4kw+DNk{a `:0`YBs G:q=z>ލ׺ƘD7y4{=4q" []9cbѝeg5{m __t sVzb aP ؾ@oav~ȋ ;ȘRLWF駭K+z߳Q(bIcn >3Uh1nd)Z|qga V}'{l7c`mc)্@^Zk9CG(?u12T5+b臟q^4,Z~7>41ƘSqߺmSbAvذB(sye^ѬP(2Ovs?ѯ=p~8" ㏭0s9F``NsIG `w~ٳm9& u_}?unҵj:7&{ʀ„E\`v팠tW _[bDfFZ-f{c<|Fx)gq5CDi,;Hm\f) fp&X09U,w5ۚʇb)ɶ3n]mr"<40a~&@92Xl7oqa0nb7Ļ~+V0A|g9ׇ߬:tqG~zh!HwQ-Չ:ة 6tIDATW\O f'pu8G_Bя !p8|NfB~ κ=QO JY!p8nt{{C!p8C!8觵K=€pp8C!p8 M!p8C!p8xw{AC!p8C!8p]C!p8CG1G|t8C!p8 M!p8C!p8xw{AC!p8C!8?_IENDB`graphql-ruby-1.11.10/guides/operation_store/operation_index.png000066400000000000000000002147551414121453000246720ustar00rootroot00000000000000PNG  IHDR/9@iCCPICC Profile(c``H,(aa``+) rwRR` Š\\TQk .,+v*^13;f2zZ qbrAQ c\^Rb"E@G3@t{ a r)@ [' I< ؍MB}-M PZQ *23J!`d`d o7(Ɓ+c`!B,v9> +H,J;KqͽuÁ^d`{Áop]~liTXtXML:com.adobe.xmp 1016 422 J@IDATx_: @@iiPPݭXOT)A:$4mk Hk/=cy z\kKJض-[b֭V~MUSfX|yÀDD@D@D@D@D@D )pCvw#Vl3o^1ibֲ|lKG4ߐt:+" " " " " 9rsكƶ[>n^kkV]s@#At{_ЮW};Kڟ?@ j/8k.'H!+X-m =ke+׾ wJmFH~ls8O\`;B@ rwym7;|iJg/Z͂[{zm^)x𰭜<(l/" " " " "CH!bF@ߺ)(μZ~ קp^AI`{ 6@ǡlsﳵYLJ,$" " " " "^wNr#o <4xVǮ^|A= v%pQa?h6y& H˿}X~ēn~/Pxc74E>[}m\kNF@@個yz<θ| _"{y  B?}Z|g?[am<>xeKLSKPe浭7YprV1-kӪ~MvH]u?mΜ99YʕQFֳgWrm2eؼysmʔ)nݺ}vq)"EX 2ڸq͟??\=y ]_%CƤ' W.om{M[lD@D@D@D@D@D`")ζ; .ڵkO>ϻ}np~0ƣ:ږ/_aCIyJjX'> _z!sydwӧەW]Y /J`@/f7[?~-8qb6neP֊4=J5om}ӆatU?Ă/ Λa*u:´XߺuXD4/ng6-_gH6lUVYr"{as0",Q c\mZ@hѢΨv]EĪUf+W^{EW ]jM<)v]oyhKPkP~lksαI&و#Pد~оr*6|0aB7lx')7?kڷoE?d_~U]P˯TXi֫gEJT׿_][P BNI8P#Jذw28/{ </?ht_;k[7lTd8XA(~XmQIuԩS ;!ݯ_?Ղ>;%ձfrBSV^e'CZ;צT#q++dYэ"> <\)~$x+VhW\qEQ+W Ͽ!OSOj7x}{}Y#e]B8#m_Fy7yӃԃh`,qW:e+]%Ӧ.X?c֤q <3{5]RliQ!W+μZv{9ϘQ 7mg^=vؠ/NUkv|\ gt;%KttW_<ׄuQPiڎ?ݓxiCGqDͷ~I.^y/1,XزF?}/##}N Rγq )Ryp|w߹)I2҆ćG& †P|Ɗnw |_8 prwH!rx\8G7$k=t3V3jMQ'ڶ޿r]b7<$0W<֡}>O+^/Wlc۸ְVFm]Ά x~:w:yK*| KZKޮUX!S}gC q9M7 ƕw^`4S397f͚igLw}UP 3jH?U掽brø)`Kʄh= mx`ܦVDD@D@D@<~!;9?&mFdx 񡈣ugo{X]3)q1߹t? ɧGIyt~x';"kԨy(K$TkGr9{^~Xoc?΍XpÍN~ݷ]H*o]w@1 ~OuJ>} %+׮yDw={(iOqi:8dW5ʡ '܇,"z"w傒O?EΎ(ٸqc׆ȍ^#^zu\z(`6 >>PhŘbԦ0oWɓ]!H"1@/hhg0H]@h/n?խ[׍+X1h>5o(|71{:̂HER R,Jk-߭3g#(ӄ#D{<1P~.]b{:FAЌ@{w]>2_ 6fϲ9x($KM(3q2s}3)˖-wƾ8UHѠD 3FwDSyfCʕ|fW0^p^SN /A." " " @J|ҚE+/>'^ŰKo{ëlP eM2C ?E0Mzr:҉'G}n/(a 8߻!s^%K;%p;h+VԳLruqvFQλ?b\YH] 䨑njo5o-Ӯ﩯yilx۸4Bm+.\?P['?.n(.l?'mOXe->z/Xd=*w1XDP8bؾ%/\O `bAYsn3b6k׮jʭ^#[q衇F^`yc%=׌H/Gv/w&ӧODkҤIDi𓦃=Ɔc=֭k֬I=g4r)5k: m2b睯x`?wy{q 0 :v꺁"5kr>}SgC1#HDn^w|ٌf{Fh~[1mEfa?&wͱ(}|Oq;o3DSE@D@D@n)*y3PSO>a=J4{?p_'4;ʽFy<1B.!{#^0yl޲7%0H |߁L.?-$ֺpoD._`Sx}Wr4 0Q@=QwpɻR xՓؘkH;AUlN { pVAo\1Va ||Zj9CNP iӦs,Y.:62(Gmҩ-_fSo֬y{U+# 6+dg+n\!?!J+4U΃/^5{m(PoB 0|gm<^/LvǤ{}_LrɧCn#KiOsnt!y ^~**'a؆tʚ򰍿]v;z _Vl#~^ne^тGy[6;ǏVD T^b^O_v!ƌA(}1+\'~{ttSd^ʹxʡRFRAA}kɨh@u~*&4O<\p?²94bx}xyun u??d(MneF%"r4hh Ӯ[֭ˬw=@J >HorQc|^xsዬGq!lqT%hz{$!e]HVADkY&Q,v:ߎ(^)[:}᩼Am]-/;f&C!~â.T6l˔Ilܼvm^%:܆;N!ho%&|xgA0 m>"ȷ;Ԙ ooeX#s I5O{(0Hq< ij!L?R$ljUWw=u=&a(^(.,'R>PБ7:zǻO \3T^Ul*|]pɖc}G2X^eϜ p&oZ," " "{)O! "B_Ҿ C=v-7WA}<ʧҸ#2{wfqwEPNn+޿v(O2 gVيשm?KS}H}18%?xOɿΚJ7o\3m9.~!h}| EW)GP9>㽏V`L!E;^9wޮ EPQkǐl}xgM:c}@' s>0z>ᴝXc~ܽk=ub:<E/e^`{BEEo(b+ ӱaZCQF:ns-)B.E;/C33x:~j%򃈎Ν)N2)lk dchCI96m3x#ȒXǦ@e{P ]hA:Df -s dQ(]f])yy2L5=J'|ytUX^vn;5jTwUyX _uϿ;BSO="Zbe\)~8D 'ګ)xȄ2&PL("u27+>[vw:#M!svPſrPڇ+'#KV/^#`Wc`E,;BK5o5;ʵmf/zo{oZoߴaAGy?zl8M"J+ J6JttTob⭏͉':Tq(61u.Ogyf+E @A-(a:|Na#m .8&$ 駟=]P6Н&A<h =B1&Z|>ӆ]Á?/[4L2TWP;qDӍWXH?_l?z^|b:6~ݨQ#"ƨB" " "  W\G(ax(yEъ'jjbC/W4z}0>G ǚ6?(8xՕW"=Ҽne6־Lq#I)AX= *a8Ϊ(n l&H7҅}۰^vt~ƹO_!p=%~F3V~ck}z.O̞}G*6~,{{g93 ǯ"<+$b#M.T=@u?Mzx Cقsxkլ)F%w9}>fX}[BO3v63vyx{f{YGo1Wkmxn'(^ʵkn5?J5i`4oě 2we0ް3ߡ6'l1;./8fZn݂H.YvgE-:10%DD@D@D@r='AA4yRꄟ'{`Sϟg}b+&9}Gxg (xzB,ষE W.o>c7"Co (l[(z5 %?"~>J<VOICE\45@ FD@D@D@a?N{aÇ痝'-HO(cjQu&^V#ȭgۊSlC/~C#AqGg =<|Ն''BrA"R+ȉī1B W( O:YS Zkg[>vS櫟uU޶]gslnx6,Z90Ȇ aOӮvJ>ƥqM" " " " " " ?0^K񝂐>~xlm5UӏqjQû9*kcx1pxk[ǴpS-C`1t"YB Y9"ZlVRyuv%<m^wu" " " "@6|'^Sth95|6[W"" " " " " {*){a5wۈ`3^x_]A`)++h" " " " " " "C HϡFZj+" " " " " " 9za4, )" " " " " " "C HϡFZj+" " " " " " 9@ nˡcӰD@D@D@D@D@D@D@R$ ~LD@D@D@D@D@D@r2)9hl" " " " " " ""))R3䫣@JD@D@D@D@D@D@D '&" " " " " " )"(5L@ ~N:H@ ~LD@D@D@D@D@D@r2)9hl" " " " " " ""))R3䫣@JD@D@D@D@D@D@D '&" " " " " " )ȟb;5@sՉ@N"hѢ4\7)i" " " " " "wX5lq탶~eU+ZH[-?!m]Pu{J\ww !Gw)6G9 h"~oWaTo+" " " " " " YH@ ~TW" " " " " " "H]u\B;oڴϟok֬¡ZV\}_ۼys Z#" " " " " "k ~֮];w}CZjYF졇˗4/=ܝ'7vwYͳcLjҰaC{56\F e[ok֖.]jW^yw}v':jmڴq^9ك?c 5kXbg&wƍիUC *C4h}gvG;o#[n1~idD@D@D@D@D@D@D@v <#FpA+̟?wy֩S'_o۶-ZdׯO̶nj .4SR֮]js۲e-^8n{۰aC #YG(>٨;#MMׁ|cQkE@D@D@D@D@ظdmY޾yZ۰hIVw|`<`<*T(fΜ կ_ߪWr{ク&L`gu֠A~؜9s?PcS5kZVcD+SNu'*Dʕrv@_(O>7@ժUN8L}*V7޽{v:iҤxHv믿'lUT8Q˖->pX0={v}8 .wkqfL|Oo lnOR j׮ֱ޿XnoꫯN0uֵW^y%j$" " " " "9WWg=Kb}j^ZZmn([.[C%}`ߊ-aP(1‚bڱcG nN=z!t6f駟 |HzYfv9غuGƍ^z7废znuJ,ڿo> \.]3ϴ)۷#GZ5"ܷn}># RK.̂qg(0<\ʥE|s=K/bŊ9?p?k?ܫVj'N-<1 q3\_|9D…]TIdE6-` C)%" " " " " YO`ۂ`2Vcmÿm=K-kY?rzܺ)))vaֿ|N,_|3'9(^Pz۶mA-ZM}}/^<S%2e[n21xȐ!.:e袋 Ay%%1 ~tJ-D=.{gE6o; 5F pڠMCFhAi>(xww wIӦM/vQx ;G];iw |g,YO~}DbH]nJD/w'|<-G xG/PuKsռ\z96Wb+#'sH&TMMg}(|H1skG&;1^wy/q${FIGa~<EG>C?RK/]w_tG |TRvg;/36 ~-^j#|~loٲCڼy +l'jVwV@/;05j G'jwߵ"E;sʽ7`PȬpӧ`Gq,.Dq (I` ?ؓpW׌h/@FEJ'iU`X+ӢtC+ZGakX;O^|?5Ͷ_{ndC;c'A4\{oovӢ͙oÏ$omχ72Ďΰc +RR@M=a(5(8(4P|pXÇ;#Ax; btߎy;vuٍϭ G~=a^1 mry,!7PKsc+C#sڇOfŧDG+,矼9&n!#<2j9!v>70 |HQDR|y;\Bٲe3=o0t8jP~ß1Si rڵkZbTd$Pv>6/lW?يkOYwʧ6oҭ۲c#hRyΘmokx6Vl#kavQ׈rz\gu7;Bw8`h!mYb 7ѩ c'ѺT@}LLDP;(VNۼzMd%ժ_6=ʉS-_"n{َZ~8/(ݳ4{vk?{k"M+Vٶ@ˆKlâ"룫J~VIҞȧ'ëRZN6;kQSCV  `5MuHrUWL19pwER+0>xAKӰ}x 3 \fJpsM7fw`t#v`E>Eu{3U^t?<+1.u=r1^qz7pPM6uԞB?c51p޾⋑nn~J-^N }n6l?췺9"ys!uOg9(29}kkWakI-bܤ`܋>]paw_"mf%cDhmCC(W 8}.PDP\/[GDd <$=2Dtl ';gB ɶ7 YdsV_"; &dJ}wt[//臶mzVwv'xM8|M;vG{~4ԭ[׮j;C>3gδO?*T`?[סCk_~_ԩSYfvM7{\pad9r*Tڴig}GD@D@bSƍm/_>{w]c<\{q駟nŊIko?l,h®Jo\v#/TT)ر.]=o=z_|axv%X˖-fo5˗7=묳,S9D 'sB*U3ϴN87w\\EOѢE/J(a7px}F_;#'Ry>|>:ay)4$RPy$੧.S@& [oՅqON>aH>쳶_o/vC#!R?31)8"E6na ?&Olܱh@|xE=J3>ay-~1~[oYʕ]d;0`,.P=o Goܿko믿nxfF#|ΈL; |u=҇D`O TN;Ǝ=X#c-Ƴ'|k^zS1±kgd_*z&Mr#M4q:=Ϗ8ɠA ^я~G:dcvQY8蠃gy}= B:{1QԨQ#@N"A$w-?&4SOuJ|Æ o߾p^&pnadխÇq!t M~,}5׸|nR!v0Vh  ҥKf3) Rǫߩc9ƅ1ѓBuX!#B#8;H2e|wν\rDq0w@"" @)4OCǃ (DȗǛk;?yd-$" " #;D#?a2c/,FV^(@+.#}.,RQz5A$.5+0=.\ޗ_~v2o{>{rGF6%Ry)o7EZ9%"O~.(" 9@3#׉\|TF=s$#˿Cl==uQb{eվ{r<aFmy&A&ϕBlgvr{# q:H#xGb6B/RB?{&=6En%=}8f̘)?{l)0d_6y3Ʉ{1&τT8VtgR5-'ƠS?6/fҧz" "dT¹VJ77BxgȗC@"mV[}L?˃Ot޴3nj(Dx~)\s!# R a|ٟP^"B8 /b}Dxg*1_ 5"K³VŽA;Q/Sg&?rLE8> / |7g=Rw_am3p6:3Pܑd_*z܌M`@ &{DF+UEA_̙>R=D6v*DX$.ϏϝgaxfPwg;Ⱀp0g ˄!R1L*iYD@D?x}N+Ǐ6,H>੉6d z& †ÒoF#Pa@q`_XoEggߩs*U*hC Fxmbg%J7(%/ٳһVb.Uc @D@D@D`G $<ހA|f͚hOD@D@D@rd ~FE; HD@D@b8ꨣW^v9'uִi;iC/NKD@Ȑ֪U4hSD@D@D`/"̃//2TE@D@D@D@D@D@r/d ~O{Qi" " " " " " "{ HϽN#)ZK@ ~vDHЂ^RsE@D@D@D@D@D@D B@ ~D@D@D@D@D@D@D ȳpm_hDMD@D@D@D@D@D@D`Xvm£ȃ6@ ?w\'RD@D@D@D@D@D@6@ ?w\'RD@D@D@D@D@D@6@ ?w\'RD@D@D@D@D@D@6@ ?w\'RD@D@D@+l֭Yڧ,;л@FF߶]`۲Hʚ5.-keMgED@Pcƌg}{1ۼys85k$U\֯_o/%]r;N~^{֮]K@,|'w_6mZ;.^|7ƍ @2Yl6W>s[*>yzɒ%IhYD`#S ;5H]`h۶;nf*T!| 6؝wiŋ5jX%믏k,d۷7^ wzYޏRlY;c ?73KN Jr mu[J20x7ow֠At}?Q;$7O<)Æ 3N:{GoٲU^o0x0`]uUv7JW8/2yS+C *Ǎg-J:VCA`_83GP`D<#*g"P|va9;vDiŒ^zѣoZZ'"k 씂P,n^%4}>:Ŭ?uy t )>K(ώ,'v 7<,c:tۃڗk%^yJ<VCq0^qvi\p~_Ê0Š :#G߾}3OfX" @Z,e@V.l8Mlpӭemȴ'^|lVBYna{{/+j=`钶qr~EvWg{8(2&3-qU6ض[x:Tiڏ"8=1'FhCVb oLlw=Rm3;GH'۟h;^|ߍlg}ܮ&|>/s=ׅ9ǽ{k)A(ۗ_~y!ϝ;ׅ_wunLFr~.e/xmٲŊ-jM4"ϗ/<^"ZKA?J& "O$=JX}77i$ڠE蠱cZV"?Vǎ'+/Rٽiƽ)@}$мyڳ`nu*R91|Yg^팓DJev]w9;#Gyap 39{]{O{@ - >V,|{w[7!@LͫV;n6׬7YVm]z+(Ӌ6kzN۰`Q?kfϳV|lWvm |!l?eí> sF}lqX}+Yn/YBmOڨW[A_>Vokk|= /9ׯn͖ i.<ʴllfcϿ Wh vZHH3h J{dcJ; 6^C9̙s?3!(#E]^ƍ;6xnQOomrk:cX_B x!5jˆQ&>s;s\;[񞱝a^7-?dg <<B} ^>X?ƭh7އ{]nڭ_ t>$?܎~?T o/8q?N?l&LnͭNU*ߞ="JkE$)HW=άU|y{ѹsg2dk};^٦ug$Pv<L&Vq}+R->鷬GZ㏟_RysM,mQS.(s?, oZ-0-G7/ HJ@_`?% u] /} ?BH " 9@=@h} KxٯNqڵkGc_̏ܰ0Br|'R/%KtEv%<@1JJXPy&2z+#W 'Qp.(G+D W&*&M@Bg%#5,R1@S\kYv~+c"NK /N:>(s]t7T:P *{0Zbmwlck+|㙎m1TPI#9DqR@OC~< 9˗/재oy1X!&\C> DsߐCJ IxX2Wx2>/ކa’V!ܯ_r';cCǸDмbA ?v I0>~/-/ƹyh 9%W]D@*̞8 9xj8D>^('T_v2L?((1"V(_(9PX3 :PQÕIs~yfIDSL1>o򵙡޺v|!',7m4Bwީ/KLq jQX3ZRe9DMe\P)JW*G'4?TJ4յ\{7kL]?jvW_}s /|-L$qs/ļ$$M 3>}e5ۅEСCunsxu릯 I5Gymڴqw*C$R d (')jM{d7w1*\_v(ZPߋDzt^@hZ26!u9[ Xs~w Ix+_Z~B?M9:f v.fMwQ^~u";:m{bWQAV"DiRY~KCe]Թ~bdH!Ox$Lv.fo^m x +'sw6ē;0bBᦏ_Xs#^~}~6 >q;e<+(C_Pd X*sjN (2pU - @pBQIX\,'`3ǜJ4b_AC7VpPk pHBh<0xo$4u0y~xk LxADS0I "O 3qRBm~o=ه/U,p} C zX UtMZ>YnH׏ W;׭wu~왵ߥ$IC3wRTa <\Fa5{PeRٮeʝ2zHTE]Lg$& ܭn~Lk\78sL¨h>mȳ趧D@t'Vї6{bt>ûe$@@PP׎bT:2$ FkQDXqQ8HXΝ+U9 `S$&}TKtc5aK3hI$@Mq$@$K `' AQE".Pr" T@a 6)΢ۀp$@$@$@$@$@$@$ P*HHHHHH#@y$@$@$@$@$@$@qHHHHHH;TL$@$@$@$@$@$ P*HHHHHH#@y$@$@$@$@$@$@qHHHHHH; ûɓWIHHHHHH@PPhIHHHHHH v;'HHHHHH @ @?v|O% x%@+$      A ~8J      J W<'R$~ vr . S4|$Nʥ;$@$+/Jʔ)%Arm6l:uJ+&/r< W\੯Ӓ1cFnܸH8tMs^ԩSXN$@$@$@>^29^״EeiVx56UԬVYlؘlOlOtyKd.e! @QڵVӦM+ &zɱcǼ:j(wE7o|2gGUVI>[lʊ+c.mѣu_|!*TWvR;$@$@$@$QR] jJ] ڿ_dvZ1$MbRal?IWlD%jcHwo`S޺-!︔qH# ^ǏXw!W^Vwz<СCNl٬:cǎO_v*Xww,ޤH" ŋV-Jaub*YXʍFV}D5}(nQ$F3Wk\2T5~ T}H/GVivw~Mo{왥duwoWw헵;vamO[Tϭ5XdHƛ`m% +AGN7eF imݾcew#tD.e[ɉׂ$E|RvגFE]s Ց"_/F!ӌj$@ɓ'̙3p KFCz@D֗^zI/ZH{z;xSue˖+W.}͛eْ;wnٽ{.;rry=1uidÆ rKOBkx'u;I>]Ć]F7o\_'=N8ϟ:เ~x6`%C o>{IHHH "mcʢ$1}`u@?M1P u MZVūqčCNUT2L)˱)sd3mqL s$;z ə˥O_K?ojS:4ןU!f,WeOexz\Xcs7POKݥDOʎ2O ˩ًu~K?K<9dՋ]x *=ġR>zcafb0qk{ą՘6'KJ\J}J^:ғ$& cZ=9$7]\\H .\پGw Yd`͛7Ž0\\ϟoNן=SLzРA:&i^#ma!ѕar\$@$@$@$9ˇIҥdwU,\)TBZNYJ ӓ +ooMXl` Ч%Ou-84m>Qn<+gʲb\o;8^_Os$Q0:RNuhm(& ~Drs/]WP$VP~Ʌ5$CJr:ɲgqafKGyv5VdoH+ww1iZŜJ}%Q.@96]tq\39>eH^pH"`;/J}X}m߿>|X*ڷ{vƏzP}]`ç3˝ ŋ-77jeab?< `l +`B((CBԩ"I&R [ʐ!C= ^,/BXHH 6nw'yU<#{]*R𣶒+v`8q)SYu¸mY}]pXvkt}_\G~ \Н|ᲤQY,ͳR[yLabn=Gk#Tb0DR !?=Ya|8ϊ#CsO!`u{U_-baw'Pd#lnem@F SkCrtҹ !9ڞCgg= -&*U M2jpܹұcG 7U<5> @t H|M'O0AΞ=שž`+L Q^۶mtm&@0I.s]xCY@W D($@$@/qȚD)KqM;txʢ\-SsXvϞ?1p@ÿm6rs疯$@@Yct3v.o٩c"WNl)Rf TD!SSc)>|S+h]%"؝ $֓.}ABD?6 @! Tv9D32n;vX6 n]=ܥCd]"Px%̛7Oۯkya/NPO^ EYwIHHH <Vam~!ȿҙ vkֽ K<2csKɳ$=E9rz$7NKwmuHpLo~!{>;cKuXJorxY+R900d#pI;+ʶ|oK~V;ڵկ/ۺЧ` vȨgƎ_hfXe`Zu)Pԑ8׉UBֶXH~orm @'G}${Gk׮Bhs^)֐3i^P|wڥ[v{oK.pd9P'NX#+XqHgn$Eذa}L2ոqtرc2#;/x 8΋y> G R.ҤY>ĠCQ;Nrí>APwo,Ki?גb>y۾,WO S}B9̪_AedCf^k98jOLjM-%uQ/d=U~W}ڨ2mH/qԒX]#Vj3F |.ItQAD)ƌA=x*(GR"j@IDATl@|#y4S 2* }CjT+S}4IƀJų]W=`|cUTzBńI jdiu5|_~ ;/. @%еkWCutΞ4$W{@U_5;<n$~hǏ/SL1Jbt|OwcF{pxzi?ŝ ##/"?) XJFɜ9sPC\9XEB$@$@$@!O*΃C">đ6!,AGNH i%}a}ׂq }?ʬڤ֯O ?^j\/W,0d2eϿ7dY3Jt); ڌlpG{xy(͟|IxUkSv{mvΉ[($~O>%2|^~}UV &1{ >Xnz2nݺRzwq?vG΃.]x xCbW$@$@$@0H?͡ ďS7n}$NXzVXQjԨq|<@$@$@$@>2˿k^ [t5忂u%VYlX}npYM=-`߾z-Zg$@q=zH,Y$G*U*iܸ?~oZ]EI^z~t\n 2k;vÇڵk֋`4aG}$Kŋ޽{yA$@$@$%d5a~U]2AL.o!K=%/kJ#K*&mECB{vrn7hi MF%'O(׮]~Z|?9rT^]O 2'NK@S>1^XIjժRpaoբu_ 0aB[ʕ+}6B$@$@$l Sy+UbRsLMg|冈rwn!^<:>yCH}hCzb x$@5k׮SOivʼb <./V VJÆ ={HZ$I$nsBO!wc}9]DIڵu(i+"ڌٲe .D%   0%?QꔒI}z[Z34wd?ʡʭW%IRg'Y} o'-;?'7q%EuiR bW7|]-,]eGѺ֕rz| }GR/(Ň|!˕QK;q~j'F(>$ɔ^تReKۮrIՇVNJ$˞E_ټSv$VF'͖I Yhxzrll\ټKK-_m"$/^)w_r|f7%[-\`9$ V޼ﵲ_ްM6T" U+j'v A$X8uꔎGE.PǾi&V>wL&\z@JÇeԩSeɓ'^xA``~ \/]$Vh#+Wu۬Yd˖-rKOB5kԇ0p@y뭷t}e.ƍǟy~ЯKȓ'uʡC5sy Kt"n @ DڂGe~$4`}~| 7ҿ$כ/iEs E2}e}wtw\UJ]n<#n+eRlPw4o$ϓ]Vm)y[v`)7 wX5`y h&Bt{oɒ%`m   DS,;7ݞ}~IPQ}`\FR.*rZ!Y'`$sТ@U.,['yU9^{^3?Ey꜔6\R*K7_zvdD}=VOD)C31K+9AiCYTg"IgMe ]pQQkJSu@ KZa sM$@Nv ӂOO(㏃H92r~o`.N}L /w(H|gmۦy(-w:mbb ;Ga,?SHH"D ;Aq޶aj8uV_ Tn$ϻ2munҬaɩJfw{*K'r%?,UҼq粺QkZuRdzu񊤭ZZiA >MSH~91yua=OL {B?}Nʒٝq=`5ၸ|H&@b % :⃘~xNg' KVgob$Sldӧky%ڝ!]iӺ 9̙3ˁ\gʔI6lؠ|,~@~>s &+ X>cưжS@!&'#z}QTX~<<0Ye?R]*W1I ]'K"{ًvCڒ@ nM,y(VB?4t%_R' ~PwRuoB][Z65QrX#an <RqFP #;EA|$ۼy,YR]J+ =&"C?m43uq}]Dy-[ ӵ$&Oqn/Kw|9uHHhI''\ ue.VtdG\y${$"\Xܫ%u ?;gK,@VnS/\۹m>d`WNwЭE D t~(HNwf\X^wx`c-յ%ɐNA~Ⱦ6{J}-__:, x| 1Pjd]#f`uoܸG K:&?ʯj/6^ -1E""L0yaWNPza.#S;pHHHADZ~aSo,ݎ VMg̠̟6 8:^|]ӷuHpusƉӲ09p֎=$XY踩:^  K+=/GFOS:K=HBS-:%aMսoj5NN .*${Y_|J υ| HY-wy&$Ѫ\޸C:O$xe  s3pAE{wIXm>P|ݫ{9ŋk%|Ĉs\_zy>GVLM$Eرz L4IgGpĉ!2#;/>8C   W&Tkêl,81*\_[YٱV=aX!Xn+>26!u(5ww^P_8Uj ok%ʞrtj 3Fɦ;2{~]dG޲cO}mcH/QTV<=+7y^vԹ~ i}A=\bo_.Oɧִ߬ꙕ@ uFH"1_*R|hCY^-T%x\ˑQU׬A$@@ΝqG9\(q>>M}iW{1l-ħ;!3> &X`?q^w&R̜9:pzY:OwcF{Uwha )SGL?2/XcI<ּ[VƂx;׉r D@[R|,P̍b9ֹ~$Θ-0˙UM *|69&xDJA//kZQ+ |X*W,3f̐sylfٲe(Q"i޼DEXc=tDŽy$d޽qOw_D4=ҽ{(ԯO:9(u 6۶mʸAQM֭[;n8W\jI{ͫɺz|o;ζ'Oz +t]6eԨQ qiYf!VK/䲏gw-]ه˗G? +_* ʥ aGy%SV&X\ɟ"E"SܹsF!~tw DSJfOɹ{n_ddǗdπ嫒4K)'筑0C6wF~'8M*A 3%=wPxe>s X4AsUYf9>uܾ#Jr#J㨿-ޒ4s}|Jv%C@%m!YrAՇdYQʏNrd6ng_SvǞ,{f)3i]v`ؖ$K*;ڈ;q4^Ky[NSU*-U7em;\_]j  +Rzo tΜ9*]{  ڟ7oK5kViӦ}+%:*U׭['UV~"pEWVy뭷aYF OږK#vp;O/`oxؽpva(d|ᇂ{XDF0d޴3W_}=0LŰޣ>H - >.-erh}Hwyn iJ({"髕 WVIٵ|z/JW񋇎IeIwW) l M ə˥O_KyՄ(jSj ,\)Y_#$~IJ񽞲Rw ƅ%SݪZo I3R J9"pwէuICQ.SVP\}"Qa>ܱcV`Pd p(ٲe"^ x=LˇŤDYJEy.HrtM6Ynĺ >bk)$]G/L4/"{QOy(` \rȻwmߛ. p?iqJuM.#3_'7P'}BQ/pu{SWR*ƄB'@DP[~nD'&b01V_-:tp0I$3MO.ܽEK:U'髖4e_܎J Ie$+o' |Euvj)i{E} ÿNVɳR}XI,:+m:G /Ӻu /=#VדN;&rX{@7VCjhb}{4);ow;0H~ޟ I$oLv?fO7Ue ,qC4EF$LV"fm!Ija< *D¥#kx2XBXB$hT.ӲxKV0mG;k,ӗlM4gHn.=*^PqS{H4$Rp_ceq_oKb5)ŰhšMV%KGtqahvس7m$)& &$@$U눶 'Úwb(&iϞeP+fOXiWTrpv+Suw{(F A K͚5ǭVlBl/6O.I  O$3I+xۦ(O(yk*^+sHi_f׶io߾+}rA+3upPz ΄sWfr~V7}\yU8]j+π)LUcٍvHB$@QIN 2C0Q`mCPO…CL9ad501ـ XP /Lt'P]]@D t!+AaXx6;$P(yXKۘ %x\.r qNp}P1+=GDr}E}̘1j >uP-ɇpo0++v vQ # !5>ϻ^ȵ+3'pLJ`yO<{z<{,G!xDQI#۫f:Kt붨dvu#qjOx(K7rI?OQm2F+dbޟzV:ᵍ}rcRnN)UW] ߤ7`qF:{sm>/%VЀ qN/l5Gp{,qǸ{ 'ؽ%tÁB$~acK?3{YX\X*KǹkYꞢ_c ]}~ɥ;Uv6z:$@ze7?5Xݹ~C/N_L҉RdW`:wzm0[-rv_$@L>2Ûկ/ۺN"g}mp'X* 2S^y۾,k_,H.$w)$@$&MbŋHPK9 ~߾}2qD aD;`/"ׅ+dڊo A0N!<Ydі=Iv,$9цj2~WA roPaF2=oXM4щm(MDŽ&0!f^EzX17 Ҿt%f"ݴO V]F{{ Bm/3 2g}e5ۅB* x6/O^$ b'yjAHam= >!+BuQ z1Xڡ|B6XnE2;u^jI^}(WO S}B9̪_A6ZF!Ažr{ y?Gw҃Tq]qQep+^⴩%FV1J.KCI2WqA<R#FMɿIk~~9?|CM`,%OzlȒoZ>XM, ?;F6UI$@"KxwNk96C ֗EXoWjwwG=bRxA`zFp;e^v/Xno6! b~am۶z2Hg\\X1abo[5E!cwXr $7.cOo#xVUXk*䧟~/s K!Wٱz Xp%A=L4I7j]z-xppܸq#z>u3wɒ%K",'rd˖Ms;SAAAt>09d~Kږܗk!$$DΟ?m2M${ DJ٥,,ḓ٩Kko΍hd[G4' pCɓҴiSI:ȑCd"?/_.7oKǎoFFi=uɐ!C~I&[/~70an棏>ҥKŋEɊXy~"p%w*^x2e-[V ,hbެY3)WUߵv[nmqn8^fn3cƌ)S&xwom۶I͚5%mڴz!O<2b{믿 ᾄ 6C-`RZj_/WV\7oʧ~*)R\rIʔ)w8YsN}޳g0ۘĽb/X ٳôx8p,$x"(׏SRwu ![aGqCY($@$@'ꫯj+̙3eңGBpBoذA?Ci/j*Ν;BVq?_Nyꩧܹs\X+U$9s۷ѣG[n& ,ZHڷo/@5 oi/mY'ѫW/ٴi={km_Xr Nzĉ2zhqdƌ; >R@0p1Lt֪UKO 5JW7kܹseO( @o7͓O*a?}xaRbС^u_Xr ~{ /= ^s0 6LO)&40I$ǂ ´p# C add!yH26 c*[L.fa#1fvS֋ uH񟾒iRzG![w˅%qԒ} ћ/9rMXy8VmzIv`,^PJS?^'ȭKW’5n5)6HHq [Bjժ. о}\;LgȐA!t˖-RLmɛӧOk{D|4X` \X ( Ɂh s='ߟs-Aە+W.!Y(8%J} wZp ɓ'^_.p?uVF:lnSNn \_%]t&ؐt@Ʉ[~K/V$PZV8ۆ FmPBS`ƍ7ߗ]0WtL+7x^źOᾅ_ri~$y}a509 ^~e{S.?wE"2&L'05j0.XQB$h<,{{K?3w?".kmg])eH xܾzMv|[V7h%WO'GFM Icήة\-KȿlU1e'ޏ<3g$ɜ^ . SȲ/H} %IZ&ھ,jU  x N>vobaCv;VC %xH3gV^ ^` moBAlرCwE<2+B cB(pq!OęBOڵkڭJ,ǏHT11 XC,Y2+^m,thRE0YLyBio3:k/|ӭOwm#v)p68v1WJ]v1f\m +8&q {]ܣn*;wž{AN@&~m ;sRќ@IDAT.5V{:ŠVX!=D[}ȏ?2Ab8={Zy5"ʴ횺$$"&_^b~i=Ia{*`o! F 7>G!xtDڂK |RNZa KJI>qoҎ$~W/(Bh6 =zm&Tpυ^U G@(=ޓe+$mZ:|Jo:GFLp)΍ڡQ(mj|X}X*K_o,,I˙U.ܠɭ~A$@3jLx@|i-^>Y38dQq\իWX6HD2?1I=XD ~ˈP585cz`F`)W<8`ecP3QI?ݿ.&y1{z~I$LO]TV|w/>%O|#d$Szm:)y7R~R&vt [ڸqcmł71vcE\*nwkPxMblmA =2C/ׁM@@. 28I ?Mb m$u&uHNek8b7Hxm#fNI(~g8nϮDcYquWiޗ}{޼yzיh'V]] $drD7DsMIwwއ~=0O^]c !%1ffj+}wF_\A!^AKȽuݝke}-Хſ,w;$@$ ڵKgEṷjDq>,BF2ĸe.x71l!VE{,ec` }UĄ0aWL9|CQEJ4y{z`swS;3<FA'} m&X >NA x8'CëO>I9rk[1gΜ3#y![<񇽺/lnH{X:ys/;\-j/vҥu"VgH7H@*{KJ z *~K|lkYۤ<}.rk\?vRn]*{{Kjn; xݳaÆ:K6CTLaBIǃ~Dh!GVځ%<}k`//v<G}L`l&u >2Cс ˑAy2k׮X0.pzΰɌrs)$$EsV|9a&!1OPK* Ht[}u0|xf͚! L⏕(:t` 﫯9~y{[fBЗ0N{We,(3W3>}e5 ^<(`fX1nݺkB>spOצMw2,jD!L$\#|T)ZH?q"2ꕟl|#9$ΐV-7Tr{DiTQ$@ C'MClԐg/BO8^ﭶ}dQK$@(Pr. YR0 }Jž={öyF[8Ǹڝ ,J?AІv>c-fxWnF`ǒx #.ĈU}̈́Bv ]nI!`.&ΐ ޞS}:+9Vxw^PV!.Pﯕu$CwcGL2h\A-o>ylϹ߯S7~xkA=)X^DpO 3qL{rD%o|z_iOzkH@YN$@qbR,P#*H:w(&60yK8dus#S2@& 6fPz`D,~$Y=s}D ~cP aTm~O]%" 5ޑ^Ko*"*ةDT (? қ{キ$@Hr&βeCnofwy/w3Y__B@b$_/o28{pb={y$@7@DDΒL;* $;P ,.]ZMv5jranmJ }SbX<ʀ"y$ [[HH6#5k֨gDfM`D1۾V,!  /_9w      ۆ@ ?Qoj $3T 7TQa $3T 7TQa $3T 7TQa $3T 7!2yK $҂ IHHHHHH y<'HHHHHH IHHHHHH y<'HHHHHH IHHHHHH y<'HHHHHH IHHHHHH y<'HHHJ… TZXb}H$@$m\qgxr7Ơӕt\HLG2nРA^˙I$ps $HzD<*M;jCcx%&Z ϵװѷuu&}KNR'1'%/M3H͚5ery `?|ݻy%_|q8tPyW|_={H l2JVٰ87mT)6lM6{׼bwwܡgJ߾}k׮Ν+[o%yTO?(Yf'{kɓ'v|'2fرc2n88q א%=4o\J.e+V/7V ~bڴij*]x$HBcL+'wxΡSUʯ97ϙ㫎Ye^ddyW=^bLt:AW~@cTC,In.ɔ)硇{ܹs7 v6y˖-Sq2 %sp1fkʛ0a9r 3M6 yO<ӧOWm?깕e pd%Ĉ诺Vn>ȖݫW/ORlywyĩ`0om,EcS\9{9s:7%V*'˱)sqiM>l*$|!Nk#MI,y% A ~%ƶǦ͓|YFG(u& G6.l)-!3HÅclβ2x}9>s;TjFN/Y-}lcSɑfJKGJH2rYUMv헝SI.qv2Mj=iTב 3lc9b9ly )c̥RjP'f-T Y*-&|+?y[tvzlQUޅM;$9uֈpr5IbڭʭcbR>Ra`Ou2t0G/>WZ,9t,:=q/'x, En|󍄄ZhHE}YSGc ן1cX^!CݻÛl׶p?\:$M4EݺuU"a?~ypG*Q:* \ԩSutrrؾ`MPkժefEGG)+O0q -Aٳ3xb%Ν;Y'$@7@P2YIC)ݰl2{d,U6TKc% )g*^Rr֭CL&ʻc?ifd|Wl d}%ƸqLZO5S$]:}dr_\Wm)BRIms%e4\}$$>yr~+˽LU眵J*%CJ~C`jB>Yh'eO.vĂA 0/ICzr=*1WJM3D_:0f}YBO<<v .^->\9vJN-Y.ܻtl [ӊvk!pC.S_ݓ~we-je+9?^Y' śl[/1*UrLd(\":uX!Fe9[NYA=u/J6\]W߯/t > C?u`r TdPְ/GO*y7Pn\\ظ] <XH)-EUx{\QI.j*0{Ể+7XJA–]NlY7jc:"Ml{  @=ZEG9W_I.]tV#`~bضmjL_spqx-`…^iE{<+`e`>`8x{5nnoSE' E (.RUPs6J=2uLRCr┧˛+N^|3"Op. Wc'yIol?LrnF9D#`2o\PK6 k|R̮ zOJ[[2@{)l>ewZ4Y;U- N¶~_{'V-ڲHHH ~fϞ[nmkJޟWl :^fM}ZEY F$Wd[Q;E;Gfus2 qk,k;9 *WZmU@@sYf%K4imۀ€2gٶk*OP>xTZU{1D$pkӪ?Wi~RM3GJ^UF’xEcIhB=[eM!ĬE. Ict~Ve !@;[VnwշhB_Vsjyۚx=G,|?Ӎ`; D$TrHۘwh\Wp?{}UkihYE*7(x"tu Q2Y|{/>&K[uPT/=Qc@1(xh4sDdd+: jP }?m|c}o~z:pňI){jxR1wjAz埉HHH xG 5J>/ҠAS)w#hXLoJ[#<iџl(؛x]"kN;t蠬p͇ $"h!0j(ਣŻc[J:zek#a| -+7sʽjlEŖ-[nQ_[/`<}U('NT D;ʖ-k"|#H#ǸiLk_y߳{>/?RsRY;gR*Գ;5NN/[;_5[.HW7pϢ̮Ҕr mkLQ3f+3Y?33Ug/{6Ygv̾w{YsX֏V>&Hƥ^U=Ë@qup`SFrߋ p>h}inf]_RߕkY [01\0ord$@6b1޶}ZׯC`ȶ#<<\`ׯȳܗ,oc! "]U T2%cƌ%EH7$S e$@$@$@$@!x SvB=ܱݰ| e PeHHHHHH5Ȟa& @0 P&M"      $"@?[      &*IY$@$@$@$@$@$@$D'xvK$@$@$@$@$@$@$@?4)HHHHHH$nIHHHHHH &e @D- @H aၪHHHHHHH Ђ_'      ` A$@$@$@$@$@$@IL ~HHHHHHA ~0(R $1*I{      *H$@$@$@$@$@$@$'IHHHHHH "e @_'   pISv BIH6!p(1W#%:mՓgl}zȀ/k93In.)C؟3P"h<9 KU?T F ,Z{덒#"H<(5iӦ)QrO;Y*ʭ3H޼yLᅲ;~퓻&?3*zH)˗/({ri5jH…e˖-r!ݻϟ/:u_]j^Uߍ,}ԩS~kafNzر2j(ٳg2ydX~,I&RT)kQsx8@yǘqIÆ ȑ#U]qd c̙f9{rf \ RPwsl,r䄮o諎|[7K~|;l=q7LeKIH ZJ)Q?% M%?~;O촶C>pR1@y(clٲySI9h PP\9T[X&N}PC6#V2Q᧟~uk,8"eɓJ[Y?X:t98믥Cc)pi߾mCoBuV#V X"Ӄs΍#oqA$tgD?"'Ϸb_J*wp,kLVQf"kE ],LT^#|kXdS>2+ouaܭ.PSm XR\bwO G˶wνe]B-9#zض8{-9hm<  `x7'_US6e <gϖ+WJ& 2,y(pwxC9{N6^n:Y*ؠAYh4v+'_թ][Yr?/_VXDVZڢ,X#'f+Ǐv-zRԴiSپ}Zh!~MyHn ! Fhɢ`>/e3JR}*#dqG$SRu0&[ +=+uW]ں[+֑3 k6 `9yoi{l[jVo5${ѨC$@$|vv옵:2O+K.<իWRN>e?W\"E :7n,K.@n0+H $XKyEYRrԫ.; pח %MШ?$etRcO"U*U힊Orvj^;6SH R_I4iT^خ,˩v!ҩŃk$UgxCϥl\[o=;)nOJꭙ,)Ӧ9WkJo~s5{?.HDL$p@хuk?lٲf<9s,ϕ*UKYxqk&ZxƻU~ws&m fb{E>}ta_A|왇SJHG~,7#XSbԂ>OxX|7jZ$@IGݯ=2YJr4GvK*kq6kyVMMe _ܰTT-{6GJrVO!baXZSGF)%ߣ m9 7Bʸ9d|cGwqv ۱O.mޡHHH\3foWw~w\H%g~l s='pɭlDZGp8(iӦ5eZ-:[.ҏqACv,iIPhPܲ|)Wx@)DE$OW^QGiFʕU,^h9!a.:VT][!`Gx C ( >Sdin^5qmWSJ(8O=+f5 ɔQ{ eXtP8[VUqLZd[72oy3IZLw?8a 8>#[ ,ط mJ>I(hHnd1B{=AvD eޙ`MF .XB}kt,z\a֯;r$YdQAJ?|tr )Wˇz1>Ƨ]u>"kVv a̅, =b ڵ˶C# @jT{a/>KwcY͓zyCΜ*ԙc`Ȉ336uy_NX ~$35Bٲ}˾v'   MQV;nsK>u7n?p?sW ٿtMJ5A]`*V%\G}e<6nMemg=5keXh֬`;5ŇU9Xs,jJWY `[uCCkKx/[ݻ+Ǻ[] |AS1GG%D[fo -Zf?5kə[x5,QaJ1 O]"jVV?2؜ӳ+O|E,1yB$@$@$@@"o\h]Hhk1",YRe/uo[bc ^vD.>`/x*UL >\{*Mt+ɔ)S^~k֭[Bbr*pGy"]e˖fsCea$V^-D$pH!RKG?L;)gKs{YH'zt≊mKz_CEnngj|7*BXGNVrq*C$@$@$@P4oqZT)א@WXQp{ݶm[O k8Ƃ *w{iF :<`ǫ:;wl_~*d˖&Ks# p_p)z(nYUիWkKm&CU4?mxz愠zaaarA^xr^LAPR @P$Ȃ%xA_)1SI ^ ȺgޔCkn2od4^:ʸQZI= 7v4ٲHníꩳ2Lc>7eaE-QD佮*(`b͡ᚿ2DCs_~BOa'1Y FFŬ H XD$<h7K."#[dcVma0`H6U#G#>L)ױcG*}C \59 3 ?;w ZPu@A]}Vn`a^QL;v SB/T^]Y}߷7k?nY`s G Veצ}KGv/aO:D>uREe0 s@Sg$UcyloT) "A`FwJ}X*mGxx@WY{K^Wa! 1/Vї7Oz'Hn%wT,$    DN);c.^(p}wZu9$@$p++; $Hj=cb5       dG$@$@$@$@$@$@IA ~RPg$@$@$@$@$@$@$dT HHHHHHH )PO HHHHHHL ~R $*IA} @ P2P#      @UXN$@$@$@$@$@$@$"""@ _<,$      A ~8J      K _<,$      A ~8J      K _<,$      A ~8J      K _<,$      A ~8J      K _<,$   ۓ $&&&Q&eJ$@ F)oyM_'.؊׶B^ sιn=~xݻ 0uĮxŀ]9sF\^B+H%>;wnBŲ= ě~Yf;wƻmdGDDHxx}`q P}I|u+Ww3<^X2־xN$| $HнL-R[ĸxK$.!Qao7oӻdsoŷYLQI"w`6yg9:iybB^A0S ѣGԩSK%s_2|YflRzaօW_}eݬ˗v7h Kh#GʨQ_W.gϖ۷'T4 ?^RHSx8tƎ+ժU2eʘeP;ͼEyٮ];)C 3 ?ؚmܸQҦM#Æ ~?(} u6l(s+%TSMܲr3k?W^7xCRL)9sTRI|.lٲEݳwaҥK ͅ]oʔ)qd  >D$_=uPtʡqSP7 h<9s-2bEy牎狱3 @6m Ü9sСC˫}b )TRQee+PbƽZ1|BBBE)S&YpaeS{>?˗/C+b8p@j֬)'OӧO{ *daqnڴR7l 6mR~`;Pٳgo߾ҵkWbNv|z-x͛~i]EY28ڵkɓ~kafN>3for17nL8%{9i޼.])v (w77n[U_â4m4Yj.V< K $m5P =M <}[/|om<2Ї 9a.7㷽Ē? ${[n%K,W6,$aTYP<(QBYxܺKU|y૭:u$`/["P̘H  (`eVIDATaQGUP4i"5RJ /`ևRE58eO0A)bN-l=#|XaGz{U<`4h o.\ZeĉVҥK?#FK/䷅VndȐ!jQ'TE=[N-~|G ܫݦ"E qǂ7|#]t1~>k,@cf'1XxN$p $XZ_UM+nSV[6}d°^mZW4Xdˢ\ڱWί*49Jמ:˚ʡS$UעÊKo98fDGHֻJQ$Kg9d\>zBԼeR~9ԂUiΒITSRXJ*;?A"ϜWu(ezvR ϩ/L9:esiaH඿K&HHIVrIݺumE͚5]vL͛WeAmRXX:[^V- ӧ+ v;5 /ƅ'-_j<׌{/d׫WO)4zL_~@ɯ\@.˖-s{q۶m/Ա͐!bN↹akʕ+޽ۚs 3fZB-`ꩧ6ŋjUm>.xcP+ho5jիWKڵM^zұcG]-#SneF@Ʌ5[EYr3kG:fKXqzQܽ{w=z[e_l{|AׯMD$pkHtqP(l9TMz3Gڛ3=B3b(u& .^$?.M7P.l){#yח ٕdÛ˾cc<;T)_!}SjOa?6u˗K|_)}/+? [)isfWJ/گ-ZPJtyFr7nYB߲8Kv!ҦŮ*gJYV.9>klzg/OWNYޮq͝;+ 7,`H{=_:wl[&y$@7@| ~ Ž4,޹֔MJo$Kd%UpXe=g*2P-ZRr֭d 'iҨE߈I5%[bdHXXSZ!ML1ܪN|eϷJޯSfJJCa{U\u I=rLV,EuӴ[YJ<| _jHi&!2*V|Z< ϩ˟Mmr5!OƗ ć8!L<#KKa0+XNdɢ_xpbk <,KxŃ&#Πu*y ,΄S%$uv(F)ߘ7,Q$}S n..\R-ξh6YF}Ȓ5SVm  @ 4,VNг*@ R(Z.Z+{<ՊkX,jMڍW*r"-X@P ' ܣc/Ə= 4{fV7Xg9IFQpN';mj]%`x ڲe˔2(:HX¢!toș:V<-ylGx8ضm;wJ5\]GHC?PE"#_K-tb /Rɒ%U4}D= [:p_3jޘkq?e"u[s1^(si%K[w\ k}Z^>vR=G ŖMbp&G6̄kX'(3nQc~kCӋW,dm<6k\l5TF3)eE2X1_]WUGp߆ݹZx#aѥh %lx To۹c #4?{fP zxna8p!Oh<}.+ƫRgښ+zTyԟ;Fr;ri>G5Wu qu'S  pR(_[-[+ xmKm﮵q$blmu漢^r bO+|;$%#""Y"~8 ]# x֭m͡*;B/MĚ&X>v1[8p/5nք>ȡ[:a u1u#Kt~m z;]|XU>k,`;ӤIl9?;_/[;x  x(1H i f*MO(RTdŋKh:d"e!| eeb 3௞<#5nT+vKHWq}T#@B[}1$+e52] 鍸(3HvW7{VP7a!6{D5_.x5a I !^ =Vk#ƈaQc^P~9bX"]|Bi'fe k/kX:MH6ÕAVLas{@[ӭ[7mr+95/x!![-+7spƽm"V.5;\/k#x80 zRt9NxB+VӰJ7^wNֽ,ju<-[([~^W3Jt q= F|Xws?|a2tR__㧪}m%CP½SmvݱQkf &w eT|{nu}}HMp7E x Exr+9^1Kb ġ`'42{aч1a=X(uXڠ`4.X(qn;F\'t~l{m(:%^'ߵVu#hXL'`-D@K8 {ӱO}/ vک#^k3\{k2A KF\حYm[D{#y3~nYU. Wx,`7?z}U(X"<p v[F4`˦< H~꬙ZS{2Mj5Jԫ;GY֦aK;ʼ߈iE%d 3Sie|7+|jXtך)mї32|HjN!/xk=Yv7^߇S!` 6`dMhoevGGνkU*r?: Oޖ]U{WL1s l⣣RJ~GX *)j+#9]<j:| m$ڽyxPPqmS<,5M6U >"y[]_xذa&Pv1yX5RH tCj m gx!2v˗7G+>ނƹVbkJ"0lM$@7Ù d{oe˖dUzpo!SE$@$@$@$7Ђ$$@$@$@$@$@$@$ 5~2! mI mrR$@$@$@$@$@$@5Tk8K$@$@$@$@$@$p[[~ 7 ܖߖ_+'E$@$@$@$@$@$_#@s$@$@$@$@$@$@%*I pxxx*,'      Hb'IHHHHHH "e @_'      `? Ѹ\ IENDB`graphql-ruby-1.11.10/guides/operation_store/overview.md000066400000000000000000000120621414121453000231500ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - OperationStore title: Overview desc: Learn how persisted queries work and how OperationStore implements them. index: 0 pro: true --- `GraphQL::Pro::OperationStore` uses `Rack` and a storage backend ({% internal_link "ActiveRecord", "/operation_store/active_record_backend" %} or {% internal_link "Redis", "/operation_store/active_record_backend" %}) to maintain a normalized, deduplicated database of _persisted queries_ for your GraphQL system. In this guide, you'll find: - [Description of persisted queries](#what-are-persisted-queries) - [The rationale](#why-persisted-queries) behind them - [How `OperationStore` works](#how-it-works), in brief In other guides, you can read more about: - {% internal_link "Getting Started","/operation_store/getting_started" %} installing `OperationStore` in your app - {% internal_link "Workflow","/operation_store/client_workflow" %} and usage for client apps - {% internal_link "Authentication","/operation_store/access_control" %} for the sync API - {% internal_link "Server Management","/operation_store/server_management" %} after your system is running Also, you can find a [demo app on GitHub](https://github.com/rmosolgo/graphql-pro-operation-store-example). ### What are Persisted Queries? _Persisted queries_ are GraphQL queries (`query`, `mutation`, or `subscription`) that are saved on the server and invoked by clients by _reference_. In this arrangement, clients don't send GraphQL queries over the network. Instead, clients send: - __Client name__, to identify the client who is making the request - __Query alias__, to specify which stored operation to run - __Query variables__, to provide values for the stored operation Then, the server uses the identifier to fetch the full GraphQL document from the database. Without persisted queries, clients send the whole document: ```ruby # Before, without persisted queries query_string = "query GetUserDetails($userId: ID!) { ... }" MyGraphQLEndpoint.post({ query: query_string, operationName: "GetUserDetails", variables: { userId: "100" }, }) ``` But with persisted queries, the full document isn't sent because the server already has a copy of it: ```ruby # After, with persisted queries: MyGraphQLEndpoint.post({ operationId: { "relay-app-v1/fc84dbba3623383fdc", # client name / query alias (eg, @relayHash) variables: { userId: "100" }, }) ``` ### Why Persisted Queries? Using persisted queries improves the _security_, _efficiency_ and _visibility_ of your GraphQL system. #### Security Persisted queries improve security because you can reject arbitrary GraphQL queries, removing an attack vector from your system. The query database serves a whitelist, so you can be sure that no unexpected queries will hit your system. For example, after all clients have migrated to persisted queries, you can reject arbitrary GraphQL in production: ```ruby # app/controllers/graphql_controller.rb if Rails.env.production? && params[:query].present? # Reject arbitrary GraphQL in production: render json: { errors: [{ message: "Raw GraphQL is not accepted" }]} else # ... end ``` #### Efficiency Persisted queries improve the _efficiency_ of your system by reducing HTTP traffic. Instead of repeatedly sending GraphQL over the wire, queries are fetched from the database, so your requests require less bandwidth. For example, _before_ using persisted queries, the entire query is sent to the server: {{ "/operation_store/request_before.png" | link_to_img:"GraphQL request without persisted queries" }} But _after_ using persisted queries, only the query identification info is sent to the server: {{ "/operation_store/request_after.png" | link_to_img:"GraphQL request with persisted queries" }} #### Visibility Persisted queries improve _visibility_ because you can track GraphQL usage from a single location. `OperationStore` maintains an index of type, field and argument usage so that you can analyze your traffic. {{ "/operation_store/operation_index.png" | link_to_img:"Index of GraphQL usage with persisted queries" }} ### How it Works `OperationStore` uses tables in your database to store normalized, deduplicated GraphQL strings. The database is immutable: new operations may be added, but operations are never modified or removed. When clients {% internal_link "sync their operations","/operation_store/client_workflow" %}, requests are {% internal_link "authenticated","/operation_store/access_control" %}, then the incoming GraphQL is validated, normalized, and added to the database if needed. Also, the incoming client name is associated with all operations in the payload. Then, at runtime, clients send an _operation ID_ to run a persisted query. It looks like this in `params`: ```ruby params[:operationId] # => "relay-app-v1/810c97f6631001..." ``` `OperationStore` uses this to fetch the matching operation from the database. From there, the query is evaluated normally. ### Getting Started See the {% internal_link "getting started guide","/operation_store/getting_started" %} to add `OperationStore` to your app. graphql-ruby-1.11.10/guides/operation_store/redis_backend.md000066400000000000000000000011101414121453000240470ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro - OperationStore title: Redis Backend desc: Storing persisted queries with Redis index: 3 pro: true --- `OperationStore` can use Redis to store persisted queries. Pass a `redis:` option when adding the plugin: ```ruby class MySchema < GraphQL::Schema use GraphQL::Pro::OperationStore, redis: Redis.new end ``` (You can initialize `Redis` with any options you need.) __Note:__ Be sure that this Redis instance is configured as a _persistent database_, not as a cache. You don't want it to throw away old keys! graphql-ruby-1.11.10/guides/operation_store/request_after.png000066400000000000000000000702361414121453000243460ustar00rootroot00000000000000PNG  IHDRb$|iCCPICC Profile(c``*I,(aa``+) rwRR` ` \\À|/JyӦ|6rV%:wJjq2#R d:E%@ [>dd! vV dKIa[)@.ERבI90;@œ r0x00(030X228V:Teg(8C6U9?$HG3/YOGg?Mg;_`܃K})<~km gB_fla810$@$wikiTXtXML:com.adobe.xmp 98 951 1 ç9@IDATx@GG(`ĊbkbCD{E{cbXbWL,`GR`#w^L7o~SܷvO&P' p' p' pGL#֝ p' p' p' q' p' p' pGOߴ ddL9 SS])pF|8+^G#Z7vΥR^лq\ q}h?-mXHyW0"+VU%at3 O<`d^^' p' p' G/35ȀKK0j6k׮úǑGm0^Ƈҡ@A )]<0ZQ0o3sӖ۬8N8N8N l;/F```L,GΑVSP0RWL˸ _Ͽ-_Ģ`8}hnc{ ދK~ Ea ie1pTyM0שh "zaƮ|6~)Բ^x/+NO'0{fE'҉84n;l92 *~:,EDU|\LƩ>VbV֨u,cQݡ ~eٶA_G]@70@զ1o#1C-XSһ T\EnRT(.]eUp4\H?!MXy( UIi<'lFl b3Ԉ ӔyxY%3c0AۤGؽdΆQ ?C5='Vm0(6jEtfu#O7m0Zuw0W =|n&dhyIؽ z bL$' p' p'ȶsóE8f(dD'x$ٌ;a=?ne{4+YpߗfhݶprJp}QZ(Biģx?H ~Jzޡ*o Gє]*($x}]/}ߒKF>Z^Zιu1*߽-)cL&B^hlWMDܕ1ƱLAPsx#BZ1O-9X =Qrԫm?._CPEh'Yo%D8@=' %@06mx<_fU)cHSǜ' p' p@ qҹ3^/E-,,ĸt5)dҟ1@=w.:H@AGKS8~`BE p]x~y&h]\唌r5UVS-QeOz'zZvDhҬ4Μ ]Mw}]5FV{#3\\3( Ѱ+1r#U{?Q*=!UI1<`\0V p`3j*DnumyM2?8N8N8D qkie;b.i'K;|u]1Kk&ѡhL{bWnYӖcZ2nߺZ:SIO%#UqʠgiM/,4ȰK@9KЦWq;"K'A#?vn;V$dRUZnr,q,K9ul\<$÷l+wx5nU/zBE:%Yq*(qq_P O"~ 1z-J4FS2UF/jzP|+>Cڱ-a%݇;(M:mRHHwGM4%3K1baz4Im1{ű4% aCt|:`)H;`m (Ow:s4^HS{*Qt"XYFest_֬5Qb(UdݼwasRZ ˖;(_Xdޤ)kDƊޠmz2=+ަd]߻ [v>n)Xm*ڢZN#Ӱr*Z4RՀ%=G,S=UuB*$ͿP>{**.}By*F;Ԃ,86 tѰwD$^ >|Ǚp*`2 íZ7-޽&/3+׃5.duX,'–#7n5Qܐek8t2Mٛf^W,k֜DBV:#atC["zK#Vc+:f8N8N85n u&{T.*an FU:cw,ދFT.߭I/Vڍ;NhQz+m3?T u= Đر,s 60bSV_2gH5[SE% uڢUzt.^X;^f2kC+_PZזdG7tC=41oL+id˞ǜdRcd1[BR2 )d1{hJ2l@ϼ j 0bghPlC1M,VbI.I#I{YAlߦQSp#z y]íkux]N@=k\+-g'a+p^+YXms>2apW~'ʨg:YRcoihG' p' p'  B8$#?̢2+Mt,"`Ӱq-]OU?ӓ<BQ[Xe}d%ɔAشd2l`Y$):GkŅ:evޒ}VbrOTji!SOi fqњ#^̥jk.~E|xgvM [{PAԧ;yԑi>9hgαMz 7Gy~ p' p' p;Z7-b ;{tkT PlWR+e,(P\T_| ϔ0>#im}2lY՚#ќ39AѮu7tAnI{zBuQ]AnAyVq7?8N8N8wNeKL&Ƕ;v>B '<' p' p' pʸq9N8N8N><9.4q' p' p' pm>x2' p' p' pO~q 9N8N8N!|dN8N8N87n?>r' p' p' Cɜ' p' p' p>׸P@~*8kޝ^x8_?N*q' |>H6xHʍV"V ( ) 3#}|V_"Z3|r(j{$G#?R#AOޠ/JC:FʦpX-*^eN̫VO7Woyonm7hM9mm)O?"3NZ[V4raΐϳp' p@A|ƭvcI^ ۂu\Dp)R]vVmKgO_O ~2v|c~w1Q4d f伈 otj\=1Y%ʰ 13Gw,;BV鎉r_4Zztnl4w]q;8D/z & lZv.Ի:`ۑe!Xqry2Ӌۤ^RPͩ/Kr' p@[[l  qqBzQd?7\p(3Nx,Ƥ =J Kg¥XyD,=TU6AYK" ףUiIB<.\LT%\$Dz~'gPJV9T{gBYBh0j&FuJ RAFQ Bmj. q"-.s|y qÅ ̖B'Ÿg¥;RziQm}bRJܲRd J}& r(+Y|^K%ag"E:g  >pF ńSΫ2K1i x *J8땹&=n\ bn܉(kg v%X([Z|Hv]3+2"palUc eO7T?.4@V0![}8_|~莕T/{K6өqTgA*8ך``Ne] 49(kΐT| lboY׵"?CkD^cǫP !GcdYZʲuoem]z ;;yuz^ԬLD^:j+h % AgJdQXBs<5#U1jnW W lV(-:_h6Y#Ս @=Ptnl GP4M a{'=ۚ3 $ݺE뷣 et3ް}.:~w:s=+R>j$}$USz!dc+܅~j.ߋ+ 6{&9IFɚ?Ǣ}.lkǼV趕iR2GYx!֩'} O%*gTY@35ˤW[4Y+nj 9-sQH1Pmf/GJX:-<KVˆuc6P6^/4T@^? /FN8 G4҅JMDM>Anq GWs?|(6EI#ynyF J׬窼f%_a8;\ |:2`fmc; VG' p' ĩ;J*%<":ːms7Fq70lJEv ao mp'hG@=2ğB&_ iwH࿏RBd/_![ٶ樦h n=xpeZڡc`ؐ~6$3.\C$C lpJ(e?n5GɩA3@dUtgX &䚕qd-Ns~~dG~uUJ#1tetjeT0/U |#~zegyѕ#r!Fu*'x~v%^ِh8l#WaEdOAN˱O|;W_kJ [sv dz8W)mqNJ7=/9PmW ymoY;VG!_9j6oAߜ&ݜbʨKXnW{u\r WkSt'PFr=2 %Ʋ~)"Y;VvsukTϿʨش5dݾ@waK/D2CAڤG8r1yHcOQ,z.̩ v 1͈$ Ck%|._I[ +|q /'CϰI:qXpDžFlmE@ft`?#g>gkӻ._߇ :bBAg8N  u&VS5H c)Zr}뗚1ַ^_G !QGTCte/R (.Dz8w}=yR=YM/h#9㻆R6U/ %Yrr&.hr3XLQ;3OU>oƢC] LPn!dwljlg Pt }nER(Ky1dp`zrj8xjʌ6_7DjK˕1UQ/tK?$(3x|-cFzq VӁ Y*}y.WH/h𵬾V-{31K[JBVW{t k3K_u8(c!\l)Yofz 4VN,茶*|r؏jʹ)9v,,DYa[sz̹^ڢvIb.;,⛧MrUvF5r[3F5,SD(\TfEONm#bj]aogI!nZrXpmUT& iu~ukjTᵒƿYo|׏nTjLEr]}hFuyMT+Cj쏝hrOt3(Ბ?ZMA=ct3\' pGF@ƞ9t7I{᚟vYDw ]A!,]:LNDe+]_S]6&BJf~L5lXƈm+ӛv4ⓩ~:J "CB&Ezijez:~7CvՁWP6ζ:i<Ɗ:YMj}W`/MʀUB7:7UVꭴ{s5KL9j-kC%%ݛ*7Y mb;lkƿ6/ynk}XQ||R`mV7ݧurΧ p' p!AQ>FQ 3=m]ə蹽 ZCiӻf|Rߜ' p'qx N (cNk J?U8N8N(n p' p' p'(ૅ?p8N8N8Nq' p' p' p?n~}[ p' p' p7nw|5 A~S5.IXer;_|Z+pu_ȃ7Irę5%>AOCx' p' pEo?/c$%QK12 ,/_MfM0!$ibA |ձ=~O6}ǖ2dg2UmG9AT3>t )J$E:O7axk!6yYOh_@/O3(Ql9ЮV#8N8N8nܾN@TpIIw|]i珅I8b!Pۊ5,>S w5q+뾎Hئj\WP6cP:,JFlYo6"7BWX{ Fg`,8N8N8C&s2^jsx~Zi!ѡ5G1]UP /0t()66m_$_C ˸ JVd=D f n(7>F11۹`ܺ/Pr&’^!0`3**&>vO QNޙ k"/3cA9 b66*tEeoB){:Nr|-wZ-HWWw <vv|,3F.@ QF?y="}f*  >d*7pb֮4lF+!W~'D>4l`d#aS-GYPt iD3Q^6`C՛QDe9~(q8j' p' p'$*I nd G!". ߆2r` WPsDi#Ȏ!K( dǞ4,&fLfH4FE1(L)aiL*#{ܐMnڐ"/-JS9ŕ'Ɉc[fdR9֨.Ȁs, WɕnGikQE70nMZUA^0o? щ04%NeUG90miTg$;>S'"`t-CM~rTW[-k,AG`8MqAQc(7}Db(u-`p+gF9۽5Bs^JCDRmLlh4~s!Xx@9ep)hO3e6hW v(6" Owoy ?2L <}d2Ft/?9ϜvFl&(մ3_qVYdQTer&m3rd| J=j׳NW@m*q' p' pEqKeB^ L0s/C!5 ۜIc;O"H%t)L XF:.po8֭83E:Sɐ4׉(CJIof*a-a"ˀ"R)S \ q~XR̭Y}vzZG2#v)17+ib_ d9K!C<хML%d2lhǚUpcg: Nz޵?P%y0BľΈ>Jn] A: `Ag,HҧPFz>N Ӝv*-óۑI!W4FgV\?KHZ BEc,ȒMEf:X&Jg 3N8N8N |n(N•#~bC}Pўn|'~D*VI9tY R|I[[dDG^:EH}a#;yb(B7e3Z@v(^FI.ZQz{KѠdu?DM},Nj]'2%ڢ,XA]G[$3y >sE5[Ć!>gi^Ea4R (l7Z ~+;¹3m {w#Rt~4ڭ5-x-R"#j|tFKi p$2U/"mrRKԾŶU j.D r SYJm;xZWnV ̥gpYdfh٫*,0k/Ъܪ J; !G!߬?Њ+^'Ro M` {iSKLPȄIPw XWgDzB)+4>U [mL6X3VN%'\Gh8Qb!NH>fE$wdk2_7dmFT%@6h;3;qyLҴZvmO/Q2jFru##h9jvք)QjJvn~IU}OiSB^pd"-U7ɫSFѧ;Dg_BERCGxϢ Ul%}TA {O cɽ7ǪBOdV+M?VO=Fk/=]$5+nKh0*|+ʙSrBzANdQY jϓ%Q{4Gqԧ(j9Y9~( vJ?'yL]mSLڝ{t{I^.L>ѹll9 ' p' p'&z/,p-7Θ,<8N8N8Nqg~.oP:78N8N8N@?OV[y,' p' p' p)|_Dn,N8N8N8#ܸ?LN8N8Nq_ ^ p' |x8Nxq&; >FO qg>Ԑ`$'wDΨh%!>ާ}3!FCؘ DPgPO[]m =b2T7ߔl]9z9$G#?R&?-Fozs41@F5y5aZWi ֥W1z5/[Ή}x3:[OrU[ˡCx p3;RXhRzeNNڿUP I~).P]b%ap%dTM{)^N՟vcSukR% pf:~8/MO7Em)[Z(6Ǐ I)*D]B[SMS|{A$eS\$8/Nv,拘>RH.>:ZO jM=M& SG#s4e5j kSzU}޿Ӕ)bw$>Ej~l  6rBPzSBm4.!yQMbad+QB@6٩zWhJnq<8N-vn1m4HIַR[ -ICױ}f}cg!?腆r"ֱly [Y.X{ &}pm1ߘ۸nwv,f`Q7_@n]cZM#12d=]r7G-0NM$|}i7_EʕrvƌgPtSh5֘[NX=8 >~Y:Va?v}}.D{ M{j=GS_zwp/F-Kj7x~!߉qfR1mc#q.M/v21۹pi7 !IMj|g,Ge uF qUx qw}wÝul-5I^x#w^ݣS[oȊqO- ]S0V傋7g1gY9s!J 9sܖ{M'Zl<6FvE>PJZ+gj֥WszK?&9UWǸprJJ#F#O4Yy=Zﭝ Iq' qdee8yxM+X SM Ŵ?Tf.0_SfXq,]I?pWr <&nȃS'?89R3EA !M`VabR;B6?T.p:L\9r ^׺-'FOm =9aZ:Q2Zc6 n,IDATLr!yBc&շp2 S;ޚxuJ8zW6Hi%vz1dS&0 ^ۦ&1Ui+WNk7`wep^p,Je82yɋqhS˚m6:isk1d7F]]kY6VrXWY&哛!y((1:14ڰ ^` };ej XhגfTWiTv6 M nHZ Cg-:/; EĘyCaG8zb!9]ggaՀP)PQcTPU}05¬ͣ!ꔫ iP@H~惼oĝW5ئT$<7B֍KӪ5`ħy 'P|Zɬ+A/ZGcڷ 4=d1&V CH,c{w{H լz/.v60k1pJ)af,X|dَèF%{DzR$ף4QM۩3oilV#ܓU{-ԯT^⍦!|ԁzb>!x/zr;I#Аyq$&Pރip7V ݏE͍5&ED5"uհ洘L>έƎ.z  \.{k挺(z|ckYԉz!{")@%)ZIY{5bUx=@I3Bq3k'Ѱ+1 m׎=kz_uEj ֎=Iif;x`s)P}=Z|V1?8Nx[n9`!?,ܼy3kKf ǟqjlp'--U}|Bt=8^HLUx.fOyvS%_fy GBK•' !fac/V;r0iGe^ ׄԴXOsX.)Mq\nm\,$=>/̠:73'O _]Tyܙv.'$RR Bvm"!Zx / T[S$N6HtHv WB 3m^uuAAqmKA 5.N$wSڸWUH},$)n_mqenv .݉PǠ;NNat ! T! !\׍10fP\e ׸Jt9>C g䒪v3_h_)r{nv\bveo徝toqڝ|Mp'}+,Q9Hw/Zı&>2kG*A =ZCcCqsV9N:yq ^Z$85 똩%D05 vdD1W3TvKkhBGGvl*2.Q{F;G_E n ԃh=k* |a}ddH[k LI-fܘfKO(P1oȐU fEPh"e$#S}Lm,G5:ƖRQK-Pش4,?beB gJ⤎`ߊ(D_7@Np"P(UOYl(a2&1sB 2f+UEs<1ng02B 'ͭ~Li$BX?~^TDMi*R*ayf%klx)3-e#rw9nrDy`WT9׫=o 'tl큧Spyq B**P~9n"74cT0|mMЯ ^NX0v@'6@T܆Yߍнh.ԧ솅Ca譡H;0Bz)DOON?vD{|/&j}A;P׊\?"pb-C 5fѸvPKx@s7Û`0څ}6T-H_}m'y!7!ef~Cjn'H5a,n>03F(طz(DIoTS>9cÛ: ʣj&έhF b>Fbh,lݗ"ShYIÍFa~+":aqӖf`BL;qZ- ൻOQmdmuJ;1!uua-P-+gNmUjk"Ɔ.; G'}F{ZjY^=}VǒmgVvHַ3?u_eN?OԍfWZ7xt2J^tչ 0&1nײeKDFDawlo;6Xd(ɾ9W:Nk0^~2a}TaQo^<Zv=<67ǼFQ&T$GL| f"OdnutX:#ƷAu/]A==dM6ng2Y.uV()ēwYN{;;oFE!}yJt@8|g|+>xg')>D0ڱhDKlj?ZqT{f|_1BgYo,C-a5/Ӧ˜H{ E0 o%W  [٘"/ס]ԨVۈ u28zoyhG<2Îb*W\ E] Fq-<^ݕy;Oҽ{ t\Ѝ:~|`gFW4;0(&R[AIf}L:*M[I!ҁk# ySnpdvi ]ˤ"=QCΏP|qF@ֈv|dt"‹]ܳPMR]Fb$ʠM m\0`dHs<)"M2 a^Cw%w1L܍M1 O3d2"eGc)oN àX@R?R/k)cWϕ;]{yg,Jt~ƠǛcUZQQl?: ǔ*VH?x }G\Mĵ2ZI.cR|_z7?;Zf)7S<ԯ9ʿ*~!Uꣃ"_x!*`L +*^ {rEb]oպT:*sw32I,**@;ve%$^NG#(᣹V_UZ'־Muҏ9QUIҁFT~U-T"-n*Y)vX #%ͩZf^hcqbv<>U!*y/*VAm nVE't#w !WԎʄr /^Je%(G]1YQx},*Qr1qw-WyYʹV:;0^sB_Oe*S*>bZ9e9_a'O-sr}\6٬C ~fZsĨAaevݵ(CϰCrauZ?Βޒ~\Qov㑢4K"ѧT>le͈rvo? BE}FQ=ƣ$jFCb8@WHy< 0&`L ؤqkzM_SNʼn' ҫ}hpGdc0{dlm\Ywkf6(>`2 n7'J2=0z.2ZUʳIu18py L_46_^(J+Ri{ S'`poV[iE/%y83.)QAl0ftX?`*->$ ?Ż?#⓸t*;1l:4w"?qaBxdvΞ=e?1 ;u_Ӡ2 hcKGI->J;G=x2eMFys;㵸؟Ph\yzV)l?0 ߍ~JbLsжC[q#7X*#+Ju%y'` }s}=Dە<Ōǐz*N%oCv($JD$jIzl޼/*jWWbȳr`L 0&aL/ֶjA/pTN D;)mi_fu bYbBD{I8Uoj\f}eP .莦N?k̨@1 sO5v`k($~OuP!5DG!tFTX(2`KyJݻk}T}x/`5TA \G p(JAøP_osOxK%(W=<{Ó^A0=/  }4rucR.RY.du؝݆I/5NUUTIQQ~ v.\|-Bz5~/Hi#MhW_vnf5 o-i蜡IcR?Z۫ ,,n).T(EY0\C-UD/^sE#xXgsϞԞO;(C;9g(4Bw| uNL 0&`LOۇzaaa DjڐPE~n-{Dw"$2d[kJɪ2I2ʩ:RBiuRJ.Njmjy2GS;\xaL9[v8;ӱ? })Y>.@γQX7LGkWJm6:ߏ"2x<U oZ?Ǘ3b0blS f~mB2Kr͌poQR*nN8j:nW -LwUW;ԆNx~sBR%bi4dͲDMrXTh|UJ,bcGtj\ *7$zѵ*N2֢#2&`L 0$Pb5!wA޽-uD)bbp5/w2eXDM%z?@(# %Qooѻ)` 磨H-}\K SpK0깸879r_ΥB߬+sɋ i$]kYTCW :6|ѯ!鐕 Ϟ4z1> *fR us]2Jh.i~3,3Tv&AxW!ˊƖTY~ٟ=:\2M-GQOE,0Ш,S "!&VfHdQX8HF8zȅ+9zVaJP7b u#?f/.Z ,QOUJVZQc^.dL 0&`er@-[DdD$6V= mfpU*x;z;C3sre `󋐼Ix Lf#0 ybͭӓXH?"^"CA+bwe8YF~)ڤݷ_fȆݍAdQNwW~@FQ1zpbWLMK`Qg.V}t01K^]u;Ώ+t\InJ2в>0MJ .g,%>郻cQBö|;я|ۀ>k7"jE[ >7>55MkiluC,+JŏNñGL7@Tv"WfǺ>l4F 8Y]Z((638>Gb5" ʳ9WaL 0&`6K@d!tNn{`E#M2=]5և;6EMxWG޿r1/ o)ۍϺEg #YB/ybc]Ԕϝ*sW-PGe CwQ?T%.ǨҕN^ Aȫ+FuXf c0O/w`L 0&'q{?1r_L$U!f<.ϔ7ź;)܊ 0&`L 06n`LG#x5pL 0&`LJۿ̱L 0&`L 0&`$`%&`L 0&`V` H\ 0&`L 0&l=?`L 0&`L XA[+ q&`L 0&mlvL 0&`L 0&`6nU`L 0&`L qk1&`L 0&ظWaL 0&`L 0&ƭmk`L 0&`V` H\ 0&`L 0&l=?`L 0&`L XA[+ q&`L 0&mlvL 0&`L 0&`6nU`L 0&`L ? ]IENDB`graphql-ruby-1.11.10/guides/operation_store/request_before.png000066400000000000000000000551461414121453000245120ustar00rootroot00000000000000PNG  IHDR5]HF|iCCPICC Profile(c``*I,(aa``+) rwRR` ` \\À|/JyӦ|6rV%:wJjq2#R d:E%@ [>dd! vV dKIa[)@.ERבI90;@œ r0x00(030X228V:Teg(8C6U9?$HG3/YOGg?Mg;_`܃K})<~km gB_fla810$@$wikiTXtXML:com.adobe.xmp 93 821 1 dC@IDATx\y*(*޸W*iNͲ2#GVL-]4W+E \w hhý=9sss*4d 0&`L 0&0{Af`L 0&`Vj"0&`L 0&B`.> 0&`L 0Vj0&`L 0&B(*C6MŴ]aii%*胉cDiC}cl.kX^,oĻ_Ooᵉkis)؏&[Ȳ]fG/83G0eg.'`L 0&_#Pd+5I]Yc){F "7yg Z~[zF$˝[k %!{(&9#0 J"X. Ukh[i&q3&`L 0\Yp!"Wƍ͊~'(,TrS_\w~ pIqn& yuf/ލ$J=$WGKfT=bJ9~K~̦9X{4)is#DqH~آd%;FLʀP'yѢ$#o^<N'޳x{+6]#ע4mKi 蚰sD~1 Svo$OD~Հ-SEtXbKT^.QQ2Pnⷯ@9{@\ 4w}?h (4°q=~;Z{liY߼;K[gNFdгRvnae8v-, W?w9 sۊ(Y$6Z`֬/Ø<~e Դ ETO0Cd+3)hg>(u  *186IQaL 0&x~L z{: ;fʨo_G;s';=J&&۾o".> ~8 .!<>FNP#C$&B-B*PF|-"}+)HH@ȶips y|D# +Qi \'nG͌#߈!04n-{1bLl#rJ8:wv_@M=tjSqjՏ)瀔DOi'Mj-}݃/fqq%?"CT| 5 _s)1m9$&e/S1T @ObnUǡi>/cߑ\4= A ܙ"!LE?p"&  .BMG}uo1mpp!Oj"R V6˼#)'ꄛ8>DͨGO sv\D졟}Fࣁmo/f`L 0&#+5\Ѯ];?~BFni.n[RFJwG[z f?n<7=Hslu5"7#b%Ց2E8rӪQDĨsoRw/D{o<KJc#xTi鴞RyLUʑvw ǚ=st-Okj8x`w΀A܏"%]Ӗ>erhJCT~\Ke'>= ezXV!}6nFjo$>gBv`sVNGm%:_łFŵ xw [__{t fG24x roΣRoh+GVB5URhD̪z(  uvm輓Ƽ F28cJf w`;n%Ny—ʱ w &`L 0gD _z–-ʋvz1.f` ĆECm? ngpKO•gp3 \^4E]Y9J@OSVhJY}?JbGGM6Ti5d`EJ%?'oa=*{`۾t86vnc~HOCbU$yx7m/ưɈ?mwb]^vSMs3T_IK :Vp4NCAdPQp/?#H&٥XL]8]B{(;<_1&`L <{-d;wK@#ht$6X2]ū6cۖ z0@55k6,v&tзN ]V.d4߿~{mϋWc]H/KdllWj-') m@yX|cB0 Rd'yYᲤI%պ5-V+;aZY ;,FH.sBDXI{n kՔOp`r>(H6umgeV=[ҞSHs\m؃Xw,e낻ۥ ,&++MUѱn']ٺ .=j6CRM(3Xŗ% Gul+nP\+'߭'%QWZ68\yY"N[!jPR[:g=K`L 0&!`H`tۋkZb{{r9cP>WoŦTƻ Ƣ|žjIضe`^%?eJ}Gak/'"o:X!DZy1ܧs/mGD20櫞p"> ^y!pnm=FBgJ#0K850h@}U!Zu&7 kgt_Ҕ}:eWvvm&hBqr6\MΘ;*tF)ӗ/MH}:-"ʕV>e$y|N;ɊZ^Kg`8F0tZB89%R~5Q"zvի~KTǔ}vԴiٗp|6M)HYE[ľ3p3Ly~VǗOoAKv:;`;-7&9o [8 cׂI,9ul|`L 0& (4du㑔nE:|VǮFw1zJ>n9~<4aSëO+&vdG0kؕ1$W$k:)CVJԲncךpzzS!ikieIeJhZ䲗|i_(/ZH(^P6<*S[e|P?JA:54kiGrk" JDtsL 0&`ώsYt,a^ͬHIHHX';'dG=M VJQt@dͣp$_ge<~~ HZ߼rPr;^o3Z*QDҎ䌘|Ұ eޖ5"]̨dL 0&xJMarA2Ihb5)0e~`L 0&@q$PlebL 0&`L}bI1&`L 0&!JM>p؉ 0&`L 0O_F,!`L 0&``&8`L 0&@'JM/# 0&`L 0|RvbL 0&`Lx1 *տ#sp&`L 0& b\2FW"G5T\ yX8 `L 0&`ŏ@1&)#M!x' ſH=Ww|c/ 88(`L 0&`ŇQ+ .DPP0øq㌺ KUB#"Pj 6'0&`L 0&2xf=ffyv͔Q Ze)J|đ0&`L 0&PL2`\Ѯ]< ;ۨpgf=DmgOۺLů7PDI`L 0&(*LzŵkT0pg*EԠJLq߽}5yɳ `L 0&xz~`=xyNɾ׫V֠GpI'w[;^?FƳ3!wĶڑ:YëE}JQ(ƲuspW;0C8Q8;wybe &`L X)MI&buD;N¼0x4/L/ӏ^3Py4WX+}hjOɄ.h4Bz zƎdi ӷQ"l]g#tY@mۂҵ"1i#؛P8[ ֕)WhLg&`L ,^h%WKl[diXU-z̓ )Y-QZo}ҷbp3)l y5;ԟ9 ohQ:OH=jJ(K efԔxU&}xꍎ_{h-,`IWi4ӿ%u[/|һ>B¦^q_ >/TÇ  :潉"B27E > c%[T! PA&B QEh9JW8~<4;R\6F΀:>+ 4db>ԟ Nڐ;F,&"hFlG=aG|g_>Cѝ(n(Bwof? M TwAVy(8ՒY-+9k jjLӮYp/Ka20J簔b~4v"ÍhQd Pnd=Ld S).S.݅;|tKZ2duGK;Ѭ(qlӢT)tɆ 0&`/=d.(!0!u ތ`ZHRʌu-Ix9OIYý灶3@ npƻ_+ }J¨7r+m7?AٙȚc_.*)4"}.IԖrj8R &+NlZx'K@jb\Y$R@q Hhj  hdو3df4TiȚp͛ЪĆVE[k8*%2ћdO&AAkɳQV2cq? m=zI["j/*0}K)HC=Q݆gCEsK 5UV 2Tʖr@D%)-J*."E75RNoT[I 7s8UCc2֙`L 0&_ r(5TR{&{bVVD%&wfPD kXX.m6ʂ.h Nˏ uJ E!.-l F_&-TRY ;X(Pe#26/My}Mź 1d)tX[p@d'◍ZZz$?maI,I 9ہV_] 1aεA *hy2K76Ek"@p.AmmX:>:k0uA?5 (*Gs>he)=w 1iɊϡT 6TGo]c5M ;m`W5g``L 0(c,hxnT>~HXfCtM'Y N4$#ԱLҍ׈ԛ|na0 akr(LmN3i[ O uHCdi@guԫdN13"zJBHKgʴm %l${eN-Aʢ:WlB1Xy2c+bҤm`) H3*%)Y H D G89"zDJ J8.)I[)LA+kdtۇ=X!R:FڅsPW֥;*8|v)x#^)+55hl')R[HlMIHq!{Wl&\mvEl" #QE4#JKXb`L 0& $+5UnH,s?(l3CNyp.\;5t5e6a0ܳwMvsx9{h`;y\OM/)PI:t)A4}rIvBfWKOyy24Y"?o3/!xP=Z.߻!絻eN+/"6\u~]V?Z/%6AZ^&R=QGܧ6ňMɁ~916j3c G(ҊG~?"Qx"{:o֞)u403`/GDy*Ȏ~Qvy=Q}4-(g#0O'#餉IqO -K]rL 0&ox#ete 5Pi&܈bL 0&`L&+5ȸtg`L 0&q/Rc-2_7#zsKSu'˲ q)qL @׫ b6QgW?u;lݧPjy\_n/^ a#@Wo_5Pz̷.c37\{lYUnׯ-;J܂w`Ba!-O N0UdUg@g&>AYGpu%b}iS! 77­m&UYw}T5יL܀[ua~=saMcG. :PuHnpj Tͣ=T㐺d5UQޤhYI?"sל/}8uuO3Rbp6Zߊύ qlP=]r8jP檧|^G1_r͚䵿4; 9郝0TD`+n[cP/7~+St-q}n$' w?䚥 4Q^=?i[([&@ETlnGʼۦPmczs1b.~? ܀aM ۍ"m[[C%RnZK\R+4 /Ĝ~:_H ځi=7ԴM٬1Fn 1zB,H Ch'ft62(=MaD~Eĩ7.Fgv*lT} hw_P&Ǚ#- w%F7+oC׾rYؕ#\kaWZPz9NnM K(S'W'VI@9;\X al-'wS\LI$g2YUX; +mx W# RʏˏhSDjkS\6ҷj Z^Rw>C$gS#2AIS$湟Den'ݻޟ;]}̯,E{w fFx3i(5 R?G#{H~iV.#5 uC2,=x7$>+M =5 |hgсܼw{Wh&ZMڨyN85;~%!l?>2.H<>JH吾0gCm0u>#j'!zn͆"b%9}jIΕS`55#PWUm RۙFYnPVm4$?p^J7wE *TnHy#cQeOAkoye>j ܎wIh@3b.2"iiU)X,(NDW yG)OE܋G ٺYߓF"(OR,_huLOZ;Û;chPTTY_-_^hYF@QN*T]; G*UP2G hM2 c28)-OėHZE[Ԍe([nNVԌTԞ/qh8]LD[ pTYX|VuXn íWWWOZFGzpJ@CH@Vt mafh]Z*^}y3O]Gi2KQ'(9V~ ,%{VK(M{"kC3diw,^f"κE]ڂPk(supw<\Zj^U{%짆!mD@9;S\|S=Pô˱~@L N޺#w3XVVuiǭ R6E+r$[a5})ov?嶲Υ(mk,;b`vT^ie8y>;T7T= 쩙H݅nÒĽ?`)ofK Nv+ n?CZϜ[h- YNyqg, i}i\~@ZJ_c'nf(oz3T3K *V6Mq-ʛ'J7 iFY׼}M\+:@qH,s ڶ5B3.hƤ DIT[nJO@Mއ3I,% (7IR +'5c罟L{t+p v~_tFT@+E 9ޏ >f)i*O[iDd1VɍXvIWy5lř՘ZJ9d#f4#zb4R<#Ve@b}Λ!:zQ]ؖJX>ך{[(EoQ.Ƒu rQn7QtLY4C& Sx0#Havj"W q4o"nX]vL:n8nKayTC"GxPcuRgҩ84f(,>K G >yOk%y;C]lsniV-w7\?VP9T63v܅V lCA)H>ԣO@@12/LhA[d>"1T"$M#nR)6T4x0}6.toQYF񧫄2+!%ʾU̓VXA[ގ,T|tZjb\YD-:<i-DpвA]Jl.][fu|CF<UZ: >RNԕ܍}$WW&|CJʠSfHJJjCR\5]`ѸR (QsoRh Ꝍ^L˺JeoQ3q?rvU2czIH gʧ-o721*1K"~ KdTʮƒkǎ]gP?Bii,Jv6f|%A?=[A3-q!&W1ƕf[k8*K+)gdݫ'GgjcLqu*B<鈉pGUK|g ?) c ]ְ=&ֳ:(bO#Toɲ~Xӽa-Wq3b}zC76FL Q;Ci5zmmU_8dݣi|auzfTͪf T#i2uTmsq O 5SX+2[Aj[Jv0wmߔ||1z$ y˗CfކƳJi#FUjԖ2xD kXT-ʣ4PG Vbݏ[м]o %HctbSB4 'NlBh@h \maҌR([RǢJ C8? 9)}. nV yh$نjk*D8ka`P\0R~gjAdhh21vb׵2nlUޡ9 ^ e | \h0V{7S[W3TFLٲ6:/"s n~ Uh9ز[vw Ώ p&`"Ď2K76Ej-\+Ppql֦hMMCu*GC&?Ym*DL%Ԥkhl|YHkCS&rQTҕa&zut 34@MV4Σ6CXP?8ՓK&hU_ܝN#NCz9Mah7his{PC&^}p(e5-Ƕ\}-n.ja% 4R;r'ŅJ4#ΟģpM[R&)ڪrQ3U=X܂vT &`dla)~ޑɫ&XKQ;`g[#*KaF1"Fk:FAcvkp>%C£mPh5WR~/]H+ 5¬8Dh@̷{,Qn+u`SVLxHѦ͠zi<ʓAKR? |Mԕ RD*40-`lb1cHNSһp@Χ.KqZ8Ef'dJ1EOCޘbD[lQј }|)tPfE9]XԀ-mYhgM g46wc,\vi4PS vR݂O˼WhUvIH*+SY%#e>G$0ŕܑu6R N^AY /[{.'S{"QVM/Q-njRYuST _ hk8 jϫ<3@km1ԮPۢЮtKغ8FR QgvJXREB4s9>SmIϠʛ6OHΆ ؁SݩM.R?/<}~<+ F.9_&rW.ՠ^UR6=} u]P^i|0&W7G @! ,E.;`5JV㊲q4qD}>me8Fsj^TiC\Ee ;WT=xbp@=,jr4 ,\f+;4{]C bn!?&H/Wà:p7Xy1 Ȣ!Zcs+m\JRX/̅$ W@u \~ ,?\^n-~B݌fj!Yah蘈wHrNoՕθ>:Jr7j]QmDth4nD&e"ޖ5mߤG <pvM~9 LߊqF lQ0sc0)ŭu,=Ҿ0ӌ& zMIDATiYb9=w{ɲ[7'WA+ply~RGrHD/h "49Nj hC_$RvR:bA~\典jAJPW4X5+ 9FtiVHWIw;]*SԾ"`T#Vu%-vk.=]K0c Gr2]Wt,bAQV>6,!i8.Dn(D(^lPt8B0m8DԦ&8hRR鍬IF__Y!Ȕv9KUL0 IO;cΊ=>ַ泽AUWߏm^(Ya֞/euGL@5^ _ӏڕ`f+-qqB,{pOR*wo_J=(;<ץBhlRmy 8}+%4)469]ۧ ViM$=f^vӉMr]yLV}-\S!ԮhKVeȑoh1t-,#B4GEre-7GV-dR Wa{ ~U,+NRNg6or&&d! R!ܺNVӃh>_|j=#jfX~g.WY_6ח*Џ1o~P@'}H?M _8}ԝOۂWh&OP}juYil8/ eĽh'D:o_}hQ&4e e7E+\f'nzzێSTOы iew6C;ZeG\OY=F#]rM(I\b(F"{P5vOP|)*ڢK+/ ,))?t!_זyi^/_;(v;!h*6֖*ʶ8OE5| {<*z搚H=e ɜZȳ_MQM\i!r|@SpQ(?h}1q_䐠Š,@B˥蘨a':&MB#ۓ|G8kFS&;S4)$ia0`P lŀ݋mv4BeO BK?݄8gH li3ܚA۰'Nz घ G %"@.,p o# fG)Oy‰ie}+NғbKn ^nDOu/1d^9ئ( J =j}TU泊dܣe#,Y$78(zRRt<,9E|;`FЌV[POj=Y4⇭`LH ܎'u\sҙ7wf*9M%Uj66 0&`L 0B ٷ"`L 0&( Vj C0&`L 0&Pl RSl :}&`/Vj-O5"CqCt %738sgI'2ӶK ]*A[;iVo^BDBFg p#Aӊh# sj0n\_!&|3P֍-cD0ʫw0qzx;20s9YH ;]˖ܕ}LH>(yܚd$%#}~ }?˛RU`(!굥|㳰#$':y!c3 ΔK>+!z k<\6AloAӚ+3pmfJJ3X:5ŪT\vFՄDmQ=Jn1U1(+6』0Wk*{cEXKxkBv76B"Bp|@(lk BƯ!Q%5,x[-Q9U9>s[aWHȘ3?zW!?R[Ʃ-i>J8+Xy])aHI.hܬ1'|؁?`8jC) k,[n_jMHMFf<vF9 á %ދԦثcǎsѺLů7P<3L 0&(F tm|oDUQ;|n,Ij4ijdꂷVRkF%,]aG[r0_y#ҋۣ9в4Q }ff4כ$D1`$v\5,FyJE2qbyry*cS'}HصVsPY8َ9ݾ,W||jӾF廽ޣ;Z;oֽq o (bmO p(?IIBDЬ Q -yoPBUgUh Ty7h}%- ](,"V5vIޥz]tFbfH|AV:~#rg}>Tpp0(D\)롉lP _m;ʢcjVoMt㫨gZTgcQA#'([PXRxte[Il^{“ZPYJL1߽hƆ 0&`/FE^йsg]`ݽNTtE\nȳb{فN98Z7\4('i ƚBSYXb\LVVJh2eO%J+ )Rv7t,>lzWpN ~tpk]zFZC^UPI{${{o'Šn_1O黆BȌț23: B韶`sk#3{%(ګ_"%s@M*+*^k~ÏKA%V||JC" " J(wRoO$ńh`%< m,3 ^^H1?Ɯ\CV&ה"[!E=+ҺG1 YJF8Wq l3e'8Q_&`L  yb!VBС~tըB#o֣UޝQqSRlIin]Bbkm`GbGL?h4Q"i&@+X5F[Є,(,VR]a,vnR,{^>3ǁ9M30Aiэ]"w#j>1u׬^@dfW7>oNyH~8u!s^ k`Y.RTQM-2jtJjutFuu#j䝳aܞ ?& 奎oߊV8Uƍ[o`gW]8 (tR1ˍpotN [J!W`O9µ|v? w.LW .Gx{rE؛1wką}r(v;]šNcX;vR*ቴFgrˆq6-_^?3oY1*0\ Cg#t-Be7O3~ } ';9baL'َ(ïB c`q.@ Q $p3A.< A (kA58 ΀ 7}X}0 $-1EG F$IFR Dy )G Wrt!wy|B1z:uC}p4fh]nBh=zD0EbEa)X:&`XVaM=_Ǻ#N8<_ķx=ކ_{A+F%X<$BaPAK8N8>;" ]wL"%$n#"!E&yHlRtttG@V$A\L '"_#?#+(*x(D)p(VأФpUOaJ1xQ(YŔM:9EEE#EwɊ|E+^TQHUZQSb*>j . F3h)*Z ,]N)TPR^KeeSe_EG*(UTP2JWuPRU]_s5ZWmnjtnLsK{}DusP,2j44fkTjf` 3F(#qqi8qq+Ս6&KYyH'-VVZڸdY۵ixptJuEutctm ۬wVo@_J߀nm7Xopӗlc wv2zhL1v3N7^oj|"o➉^F^l]^LT>>l*,c7K?{?q[Ҁ@-2jBCֆ քk džo a!hN n҃HHAdC Z0<:?ѓ+'?qs!;#v8q- Sj'$'v'Ot%Y;ܘBJIHٛ24%pʆ)}SL5|ikOϙ~r Q*PZhִA?g#]yyҽӟgxeɬ_edz/{$'1P.975@M-h?s̮<뼒| p^"&j,Pǜv'qOwaeY V->jΊ9ϊ~mg8ow-@-h]hp¾ESg/ؾ%MK.ZSO%J%’}Dsҧ c ң}В١%Dv_"~&_@ 446 1150 1 Y@IDATx \Dd(zA%hc$%1fƤj,Wk>IڼIM߾6K㋶&ڪF%.u`(n!l^f޹s/s/333gy<9Ϲgȑw 9ϟϟ7NrD"@ D"@ -OK%D"@ D"@PF ?8Q,"@ D"@ D8diqMF"@ D"@ D@2(D D"@ D"5 L D"@ De"D"@ D"@@#@d$0 D"@ D"@ Ï2N"@ D"@ -~Z\D"@ D"@PF ?8Q," ;ǚ)D"@ D"@@'2 ?~/`ѭ"9joiK'a FT"l;k$6!D"@ D"ФtE'`Bl~\|Ei"RpV46^*pъ<;Cb$oG`BMe%.\g^[>K D"@ D 4{O1{0P_w=&@<$.jXH,|*о=,%A-a"@ D"@ 4^s`bP rv=b;2qVX`+1jf԰/l}cR߿8T D"@ DU?5Bi?3ժ#o5o F vy'ͺ@わM`W D"@ D", 4_ϽHXqx!n.c?pU%p5w)_wwѣSO7O}y\*%yoE D"@h9.~pײ;6gaEށ?v6|Qq?PW5]/a?~휓xޡ :) $&u7;  [VOgf%gp|(&Mfle\_~i~:y6ڠ'7c癩xd c{td!-<{ڈC;Y}ïנ,[>j)wcL0{o=˦v#s) `$3P~/aIYO>Nkr7!ub{Xx+\0 ops= gk45X: c`fgNzSxb2L3鵭Lr,xq09brn '=xO"b+˛c2^Jl=<ºk}1~H'<̟5ͧ2|Jo]0tꁘ^ dm}[ [/v+G}hQ1aiFx.}"@ D&# oh2qmqzd7O}.l'8/Y4FSso3O4 3<=|u=G6esW8m!+B}V@ڙm˝v 0ai0([$dQ.>֕ز9 7`UmacF^盅y@#fMU?(Ő%(2ςyZjt3b1OnϤ1k@3Fļ_`8z/1<1^Eɕԉ}/H _G(}5}poJџ:4E`Ӈ] '3Ji;WY>(9_G_o,>uEc/΂;erť3"@ D"@Ѽ⪋Kٲ!ײBtV^å>}xh6\v0._O: <2Yd؆8}!]Ơ>|rCS;{ 0#\[NFoρh/F.]D<0;-bBt^9I,XE2~HѝC~.r3|G7i\v,l^d~Mxp1O#[ctl˳6M8\ч.qzOZ b"]u5FΫ4Myo̼Wzч}:rznL D"@@0ӏ]0}L{tZ%O 8[qѻ{w>Ts R=|w਋-[8tWYqiߣ {ip5./7ZQ!= ,3vۣ\lf#vR_mroۖj~č\\/d'ˡj>Oxnvʟ{cMǚ=<_<ؾn 3wfݿ}M)NYهS9Κ 榆=*ŪS~.\^Vtx5^^^+ uAZV~8) bG`exq@E"kiꗂq;ˏΆ(i&D"@ D蕦) #Uڇ%{pKgCiRr JyͽCgU;4~V3WY7"V?6bpNEEqe1ɱgT!*fu<7931XO:"4w0R J3vLD?0p'/g0w gK€"Kzq3N{]%RzvDp3+>{$ퟡgh2QSMS30"*<=X7_0ioJÐ!85cIC}~Q2WV7,\`־l7@9IJqhĉ9m{xanQ1mxW}x7|\Y4苈Cu.w$\9n_*G"@ D"@%)DlB3ԗ¾uul@.A#*msxǶЁu%]:.lr/.V}| 1MKPig)P[ťA"zŜDT"ܖ .me&zd'oꈭWzY /;3-1sn|]C2y>|q\pܾ^^@Wįo۝Lx(?9ﶋ`N"V%[r6[6gnS^_&^6 m©B? hFqoa%;b~~ r λ'747^u+YA5b=[Ruvm5JO`Y IH]{TD [UĊi)?KC\$%= `8F#h\.d9lſ>_? W]ȺlσnŃfя[Uy Vu&ңZ֖qٽz1ؔ) `F{t>#Vm97*}qN5pE~U}THQ D"@7J?#8q)qEGlfߋ׳p)m| ΫDNȝ^@Ip#fUVb75t[B6mB[ĹflÝRV *K ݾp_k#F1?@I, n bYG6 ! [;h'WVv [8:{98=re|D \_d+/q}RdFt"@ D$Kz̡L wY߼jTCNa'bpEcKQjA7m]ًťYNf+f|b/g{XWrЗ8^]_c(I=?ĴTRGjKO6L:B}JPR*AuvDZA<>a.̤k\Qq-h|䷘1)E0]f_0]xqz3Xp#o<ryi%Ն5 @!4[?:KΡ2jjkѦ]tꖄF_+J0-'y :X3Is!vNmVg6LJʕ8mY58il|񢒟ŎAB3R_#}Z?uS}lNbWȼ8wŠgb D΂qo񬩩]:óo}Kp5 xm#z྄>8ak/JŞBKhjSlblxWU#0څ=|q>iZJ4D"@ DCft| )s q&цK<^.\ ]ށ9D>@͍3%d۸^sA }SĪ5$)?Dž¬rߘմujC'6 a[y>Kc{q`I|O\㘰MPwS_;˽T U ̈xٌYU[ ?4cGkѧ lyYvﳥUGO]DQ yMv )ױ]N݅/=g~N[X%hKLϏYElμѧt.^^yUL%@eyLbFԩRVq^r./:0 Fʫؾ%e7?ʩ5X)U (=C3f >Wc[l;+MV/ܜo<6}1kM{S=zD3o/^b  D"@ D9gȑw̟??7n#)3||^lYW>20A%P|ɹ[_ge"OޥnsY i:0^&w[ 8C"Ye(/e& IԦ{[E]:jsn-VODgf a3 }Yߙl%tnd|0 x Ӗ?DZX,3$8.6>OgYG0Oeb;zl\*.uۚmEc[D0wb܅MZ|Q/Ae]SF$Kgmt-7e@ D"@"MfSU[/b'qooy=U_[xi1].hd" D"@@I}h%!&?e`}bk>'޲K }iG"@ D ÏRD@)) Abȿ| =x'EG+Y棴$V:"@ D Ïw8S)D-F`DiYi0HZ,[җL$$ D"@h, |S!mJ({KOr61iw2%Sfn_|x݋Fq=Wg3sV)xlxLE;%1咑;%%]PgeʐV-           hQ:@3~ؗ/%0bl$SfJ4- t8Wr +_~'DW7cȮ¥z-Nֿ6 ~̪OOkT֔ D"@ D܅lߘګ8#zvAvPPpvI?f8bzv邲wW#@v]r .r-V 4{!Ĵx x *w0mZҌL 8}WŊ5U2˙#zar4́\YPNi9.\?(WS$.9I _M zӽj9Qu8})_̷8D(s-϶K/0bFujlY2U_~"@ D"@^V),Mٱ0唧e1w;8M[΅Z}PձeӖI-cn|H&՝;sV iIK|d3jL&9Ӗeebrfک\ey~2ɿ 3 dd6M9l΅K׀IKU嬽4~nRW7_QvNA\ !6w_f&] ȗC~wxer-#vYn>˜=Wp:6g:mw`_lsNĄ|;5()cWټJŒ%=WWU*/x@!-3ErJ޾ ;]4ڋ\ey~_lqWJSv? o]y_C8-X5CkT m2$Қ]5\4V8ehct=3o ?mAeش+l)̀t"@ D"@PH@{_AF:ʘ~f)kK.ҟ$.܊w&Eq ~~A0uDdxR|=bn7.;*)h:l#$q ĨiejANo>~QDc³3I$.zͤ:mT@i!Vǫ -wP.Z}'=~%.܌u 3,IǝغT(& D"@ D 1`T/sFQ wOêĦ(Z3*2|qshu3Tݐ‡Kc3iX9-Z~f&9(oGSJB NX<-=&Z۔`d| &!޳i=?T(+cձE0[* l ~*j`Ⱦe꫊p* nZw¸Wq?"@ D"@caN]E,~U2'qH}Ԗ`Hvw W;)~}Nl/B||>3E㕝bԛ;8/bxXZx-,lȴ ~b w +|nYM b%mPeT_ ҟM#/~;tA6#3 Ver8A.d*Ttg>+{ߠ]N.yX:8 ('"@ D"@h} 3`œؒ+DZMa*/Ls:w+:`{ʱ׎~Wv;:) )r[b? PK80DZR.*5oH"-7^QSrp+WۥKS{%e?H|¿:^z6b)0!fX5m,͟ʼnql)كi)F^VĈβ/ @ D"@ qFz<ͯrcv>.*>M^1j{MMxvnܪϦުߚ~&c_Z&ƶe"*wg~ߋ,?Yp9o&c1[~gX.nL=.3(ښzR!Fgդa%mV=fq㦹1yyv<pA$ZD~<$5 D"@ D02nylxmE5_- c$beNJx.4RG;͈ˮ7W Ki9+{}N.aS o8,21 ę@b .p.˕Ч4s_s=i'o?Mws&"Y/Xs@1Ŗ_B6K*pgL1 D"@ D%`6 wN>ͶY=Wٝ/.ܰ[{v!?w]2LRvDklʟot5XUX|gPrzqĸ\3zR#0m}y`=. m6cn7g8!j'dP9MP"ȑB{)#j U'ġKD"@ D"@~o/#wggY_ };νggIxk"|īp) 6rmyȱc~iW [7%^jr% &ѹS?E[ٛ?h0IbT10mcŕٲdxOH[7-;w,[Z(` 򙎆([4OV,'-vZjm{ll&,K?ضޭMM D"@ - ?oGlߧmnվy"xqob`g cSH}犙OKt쇑o@Ue-.^e`GV5Sb\##.'\:*?7iWo8lFJ<R+^->+;rvx1 JKnJoJ<"Z=lk~]6JfhSk:kH}W]sm{ȵN GLzS:n8eDlq/MB_^]#rm»+W)Pn~Yr3hZlX8ߠs"@ D"@ D/m|:y$Ԕ"]&6R "@ D"@ DY ?V "@ D"@ DExͫ~$  D"@ D"@Zdk*N D"@ Dvdi-L#D"@ D"@Zdk*N D"@ Dvdi-L#D"@ D"@Zdk*N D"@ Dvdi-L#D"@ D"@Z3D‹a(] *NZݲ&iij1l*lJχf$PLbTI߂<75$g,ӑ&.2@>3c5Qn^zqI.k?x~R!oɖڏd]p Spo73v!doY|}>~~ѮMӔo|}=!mgFhu8 E$znrnt|PSzY9>B:*PgM;^l&L~(aC'r}3w HCy)=S_KӴ㒶W\sdwόn1oVV֏x+.6MBLemu5Q_] _ 1{KH&x:#CR@0 -NIž0Ψ8{ч{0$P_| ?S[Tj̛ulyXgnOcs[oa AMAF/wj_c Ycyg{71>l9Ehx?"@ D;?ؒ dDBg-QZvD4/~By4\,B k[QStzG_ }q3i4~2 Ռ׺+cZi5:qZ{cF;i""'k(\B{hT@J8?+a8+=WgB^ߗ𔈭F_t1."_rPoJju|=5~˗shI\e.z3S(<}6 M{TJYNUjAm Dİ^VՖJ]/AU_^XXousc=Zsp2Qa^0Clud0j13s鮗mx' LqC0!89k⢶"O\֕_[y;lAq̑?FG#}4IJvgɶ-5.y}5JM`GCBB4Z~y8zƌ FNA;9O䝀ף.] ΈfEfϫs9kzmƔ'oJļyc }sGj /?N-|)%Uova`m(QAh"ERWb6~ 3VJSk:^b8Sv9tve&EEi{۞\flg`g1J PthX&'a,WRoa/8qaWSELZmah}>h +3#{sj{j~k-Uquy-㮵K֨_z+\TTs|DIÄb䞽qD% ^`|UU4ö~X3f=N0UTȏYRY>sf S0}3D<72S$9*idSQpmw0 q$'#'vcFZ\8X?L}hYCѧ`vKO0جn}tsg坐ver4*Se;8| "1| f$bmKCU$`hS_-+7ΞOJۏvqN%SX>86[*0̌IqZ[m >lw@~+3Vs%jR,# ݅YL[ ߁~}QE(؋"Nx_oFZ''Vk:`.`sZwO9KQ=l4 AH~G{ٻG%/OQ"UE'q'6c/XRW/aP8< #ǹh܍rsk4 ip+.sfv C}}lO~)3mtx} ^]x +h Kddoŏ3QB:ru,;l/h. KэJA<|AM;Em;lY? ,51wb^}2?>sFyw7'ˮܹ[v(>|4f~; 8b '6'>XLj3Nk3Ff\^}QE)ï]xm)h?x]xqJc}ps@IDAT =O*yZjM')樵2/-Pt\%kiXX2=3}I OWMO U.5(~]p%ynj?u[Ԍ6Ex06>yp$ě B38 IDŽi1a&"&2Vo45e$tL3\8j KI}E(*C [Re;LCư9}l.>%p7i%I;~E!'PJ2 padwhp9̜j=,8ܖvؚuX`PKq} /&.f[wǫ.8߄z&Z!qxaS~QM]XK&fӚEM5b֪gۏm~8o1~jxJԚ樵4^J ̥hN|KDɿoTy_sy~c{s~ښ^k+3ޗԣ/6.Zq+ .6UV~3$<ҔΘ~+3^ujz6r t=Wp.dzhG nVj.#Kԍ CsN!4#,ž}ysZ] @F` SZV$s7a?j6ޭ ?VrߣRʌW_QCN&[Dۼam'mft\g.̽cXbD#QQ ?}Py;4RH㷵C2ج~vKlG #ˮ^i>M5B^c^ `K|"R …;a4q@d~?T\?zWI56f\z VxUԴ"G|pT0G讟JZHݫ@I%#eQHN߼Ԥ~.8`Sň̟TgƼ/)G_xZqd m6{h$<ҖΈ~t-Ns- =OsE[Ex6e8eW/Vj,?9_3{e!֗Y*K O`$RBt- í˛j9'2m1l[sۨ\^l}SL l1QiG/r7ZBPkUhf5:f_~L$<0bݼX#0hN5苦*7hZ\TךCm";KT{9yU8jwC?iҘz۽Y' o6K֛߉좓O" 5U54A,f}Z:=%"mku'M6y)]K-Ns͖[iDS\ak9wG5_-T-AN~l.+G+z~ -)?:8z^GYExveOJýloG4ˤ*o\tiR7(ʰ(6Gڡ09'ƛ1>Vc>.wΙGI%Md:ژ~ kfQQnn* c18.߄#kP!'7~zr6N̐8|ݧaa̱0"\0w"Vކ:"0練xL0+pnV !Vrȋ99jTaX4l~g"7+kd pI1/ӛ;qCѡȫrf~mF>s4;3J>Vl fy0i.^CzW:װ"߲? cP`!9=W[ckxZjKQP\"76S?<[Mu:㘙gFps Rurp.QwK y>HZ_li? z%cE3OW*SPfۖ^ɩv0ߪQD?w5ExbpS p=_-^{Maa:4ìO{S'`Ygug;fp`.9^OXS331c P^5/H$OF'sSSR6U;eX 1a_v.s`!3uE.D24v"_ FyFa#¯̹CW;|>k~Om9g.Uq#{U`ʆ= C"dnηε_r"8z0Jf'5<̃ 0+%|"SoA08q.kf ct Yeh@}qd9Sou׬3[W#==]n0LwTAu:BUG#FGW$%v,=*;Rsy:C|4j}ˢi3ߗˆ4-7b >`"XYH>md4@a2!}kpw0}PNER{?l߼$SS_z烐`a,O!mfPYCWyI띛\aSdƆ0^ť fIZOM2X}' 5G` 1qqnS|9b-na.ȶ'c}S^7>2CuT9?^(DE-31]+X9ѧو+@ q1J_doN'j񚫻LT[?eo_Uܷ?n_cѧǿuEޑqI9 r:o=#G#gq㤗ՇG'Qzd3 B0Gg?2Oyشڢc̸b̌<vWLsTL^~-Pvq b1 3]+.6^+dFh`$ϙ6X4 5g˺2C#3d'E(`k=ףHiB8thJ^WEd37u_ƥ粲 /mFft /kJaxԎE6*=mP2^u =s.S鹚i%yx<#5簑:&l {s] #dZI Z]F陫%zHϝ4{Ͱ6zĺlXʀqlt?W+.C{W=jfE(1K@8Ee5udh}HM*[q튆\]'ʟDN%$Q67(N#jm>o}81r~QlS_<"~Q.3xMQ^.-jiZ 3iyjZ맕t155Q!X]Q6V.n3usSkyz^f|P/nв18oɿ/{<wQf[P}5@e;hT?rjʪ'o>YG74 ?1l̐_:6tcf28r쳀kD@8ÉwPSب(t }[Z- '{ea/VTN+%@aZD" ?.ÿ'0% A(*.A[64;wa2jۋ=aMtS^ 4jVf/"xgyYA)C^h/N㧱pԚNfL[Ÿ^RpEd eٜEcjN5}V1zƍR"@thkHy@oDݜL])c s|غg0 ÜXIlʰU&cz7eE<[vxsT!-Âs? }gfIKp#@6k,ԚNGłٮ&>21ʢ3:}E:k^@_H-ԬimL:RR"p7h!χ4s jV\`j]U]a>QrQ2LnU!/*SUH Hz;JtPGu`;Zֻy?"kH_< qD=VZimlknt顥Rz__tU#5೶1Gj`GIL=ZaSlO+l D"@ D"@"@35"I"@ D"@ D O3k"@ D"@ F ÏQ$)"@ D"@ D43dif B"@ D"@ D(d1$C D"@ DfF ?ͬAH"@ D"@ DE ?F| D"@ D"53yH"@ D"2D‹O$ؗHߑ2d&) w5h$$uD[??X,UY<DL,nwfeU  e2(h)s0K Sz{l?^;2׌ EbO [F? 3SbWoҷ(Nnw]^~Fo2c}ft@Hcs*-G?G)Fޏ.V9GơerwΝ4N˩eroҼO)G\>Ab^7' Gblwdx Ep~Y1X&}-*J-LK&;9p,:> Kt_):q1uq':c%XWNfa~Ͽs'>~b:G }Eճo*T,F~no<1w#)fmUUwڌ)OO{kx"-&/>Tу0qX/\ $ .׏=lVm}qv:Q6@~$U]l*~7.%8o'1|ۣ#W~1H_|1kG1#^zw t9aq&ijpŗsucۏc$$c8>mma'qᐏČ$xeEWpdFdW?u4M,Q=DIFLQOaLb7`$C쏬YCk٠ '#uP ǞՑ]vу-c[. FN|~DݒUȒIhUL."dmq6~"+EGjl,Ydl<}ތLD7F_"ܬ 6hY8fPDX47ƾ5Rۮvh#CRԩ/`@da_Ew[$`6ރ ?ZөSͣ"AQ]BE[PDP A1c88L9OƛcYܙzN;jʹukj*7299usy1R1/#18EA[D`޻w ^Yg=YpEnOY.dlebЇGݷ|(%ؼkk ;^(or("s`ƂZ~paߧ9,V Sh(IKstd$[=;CuػDy:K"y/\ҘcD[c^aI&H6a+ǏvY" %MN8cK<Ȃ<f4~V*i8 Um }nO—jP8֜mÁa& 6ůtIÕoA.=9 bNH0)w U $ea[yk]Q }C AX d/gWFl(PϊBYkOZq q;~Ϩ6zB7PZ'bEuZۑV>9Q1-=%BP+ xGv1őf0Wl>E"V׉gf\ >OL.==~¨'&3;*&>ӔȄD}WϜАx`A'N: 3vhŖ7B>-AX;k @@nFv`{% l%Q݉#HxXMv0b!~ڶNz1l/ƾt 阎 $ܑV׏~m9Hw/n͒YYVe *onjnR*l  9PZ s/iXPMjV-9$L1ֿ',i=iYt<l.ArN7%㟄d=6lr obljsXwKuƃc- rF2/J[j[3"FY0]uOpFmfWg#ߎj9؎}yp*Fg yszujJ4 _߿! P%dP-c3 ~Q 4ԯVOW jyA|DΒex $ڑ0ě4)($#@y>$pXxEB \q;; Pq(8Ά }bg*ᡜ8y yS2gdSR'A(sc__1?0tOF]Uq"?ga\B4=%1ґ,,7. DWB!7D([S/ڋz7Hh%$J^b?˟vМg}r*4,{vd네5NSuDٰ¤PW;~JX\n8/dW;2>W':SKHZ9 lF,;ϖ|9$͛[dw0y+^ G:O6 5qNN%6"Co<%z2UN7jj&x"g,qAOPI򜓩fڃy큋.iNCjdܒY%~2.>\SgTWU QU[Y)4&yN6IZ#26ϧlMLm0r|aCWL 9 ǿ|P/bƇzz6 뜅/DFDv(KԃΦIp4"o( e)Yw#,́t 'tgȹZµd;4E.}ͤZEXo;t>GAGKQgoQH :gNY0'(щ?Qݎ~>/? A/=$0b P2sԐ尿L7-?b+oDnYw_EAj?\F>y zUȷQ,q SH$GQV[/.)[gѨd<0J* VB eKu [ҍ~qqa^6YL?aHqIxޯH :zKV]"1 [D_i_A^Vh֬w|x,bQPM;=DGgԋKqZZ(5D6CMx3^?9&*PQ Gw%nˀ'bU'd?#`e[E|^:Ҽ~70$ƢEc؏IVƢDy$DIj-HFExQ27c&ld@6΄@y5&=DJ* —?`Й}7 Yleؿ,=OxƣrxFtrCa4j4ȧ[6zI F6 sdDøu$yt*?*H!7^@%Xp?)ս Wb߮T2x2 ]gC৊ w}wSݴ"3+f`%#a1FMߠ$]~# AoFCӫ)P֍ i$ԗ Ə}ܻۃ'#dӒ3E$ܲg^`~WʗJA MG$?>LFށ!)kwGz{የ{k3u,?ǏwB_Զ#GՐ+D&#G~m6VN!~5 "0&\, ,/M~7=R^@Br}7/ lǿ0I J(LG Zb wtth. I9{1~;~tBO EkgiӼ~>2J>N۝!5 lzs>?8 ^_DouVa$d_Uť7IP3G̷N;30ʜ$G~$=x}/L`щiRu$ dEGq'+~bЉƕ=#{{z;̖&4I6xq,}ƍEg7)LJB˥{[Gv`ciŹ4GDZLg .ʄeLTb#Ynݞy~޾{+,(ټ%+R !mNNBrsOJMtEB}6Kez ~ļhcM%#O[mvp)SMBq^k&qutՑ ñTBBo`௥()q T舌#!k6Nj X_S{SŀC a򏡇AiR$(xjRB}8"iBbZ׍&D'$8A(([C ^1T:m؋JNiV?$n\Kf2dLR@UH|7\O l6Q2@ y/cKN( NHB ZЂ؁o8BZ`'9b_]Q&v6tgZi衮db0-@m ~Jo30O{q-6Szʁ˓/!æ=NH6d8!*'7R5qniV?9gw\n@ 7!sDʾ 1 p 5%7ge 9R+M~ يށJE I_Ab*W.#W>HHch> &8% D{?qs, JVowJp$g*@<_ϝ8 =_t8Ҽ-jc^6mp&`FKy6^Bȩz1KBB 6 R> 1m`<ǖ>ò`h\罼 ξ'6NNvZ9zR ^2!)&W+?֜Ĭ$ѣ/7q͡.s&?Cɠeߐ:)U9i {%%Ы-bhN}OFK (G?< /VRjG>') %:Nj8X6;1FQn6E_($vӱϊz)|(D \mq!M$DE(%X+T w@1yG] bpO}WAOs0IKXg]ո4rzOGsL 'R;_Uhy(KvN^FEqF?F\9QӐ'n>ǬZkAo=pGP0*Օkn-NnW; ՐOdו s*L!LĘO1U2a3kJЇ1m8 Qe":8Zڭ>g>B_- ^*cl2)Dp,ߎm4L$uf>\.Y#Ybvc"Y6؆Bx]$TV")`&ȏx?tY]9wAhdž]PzD<IQu'OCoتN[5CxۈY")f$(y>CsG|UFN~(ςl60en$41N4?GvԽ_`5` '>|M]-hvd>A^WEZbHBILӎġCx5?ڀ؎D4)FqE{m;L h3r*j̛}t-s4rϴ 4{8 m#5-L_ݎ=(%m&K=עSzBF4f:OF>^;^ h4{q|Ђ ގi3'z=5OS9qcZ%t??#/V& Ɗ`wAW5i#t:vQTʂl el=_F92AZoBQ=~g- 6{:pa,pŰFE)o]NI4gip6咺:;5Έ )6jHHL DF'ŁTp}?mU dg'B,~Ywn1nwOqѐevCvdf_\i+?iL],&y6 تʐO&"ijujˇ;OBS96HS@qDbO|ʇ%͛Ӈі-d̥ZЧqKǹHW0Lnҍ_>P0i,߄يa-hr%7 BrEKb;ZenD 9LNsYvJn}lF4oMGȀ,IC7}q K`4[k%k`= Gn5Єu)>ݎtf|W3c>TX(6aq+ UvbFvR-Lm(Ub ~NC;JÏ(.f,` U|S7=yoy( 52RuËQCZ̓n^.*LB~R??$~Y2YTq Ɍ&z ՛֣0mX 8) ^I@#BdҜs[_FޖέY{P/gގI%-[b?DF:R(]q"ۃY9PNrs0Gz`*Hj*.f/V;2炞tXOBs9 9WgBHمn^+ģq*S1Ao?蓱h5`cg-:]i2yQxg5G)*8aG '1Hz^*mxj\p-<\Ֆ؅X 7 %pWȱOG`vCݵ~Aȗ8%n8n҂!' O"b:jg ܃s҅<,x;oY̠!=_O@)x&ugτAr:~ PMuRo ㋋!=+ڟ/p s̚7PD6@3s7ZO=wkrMЂ5d)xS%a.a}cA;G.\d| jc_Nb.b H# q7)k =|.KfkVNm(6}4'"֜Eh> #%5MX넬zf$s@IDATEkG M^|ޔ~~'˖,Hϐ"qs ņi+]o]Qxx;uA466Oy³faL_ZUヌ+•o6Pr]ww۷7ףf{Sx۫E :7^ ЇhN!;b-8;v#[ߵHljs\j~M2gsns r[pboX7vDF(]uZ~tvFI]ޕb,յ>?1^QU2S}>/n7n_B8*KP[{S[m7zS|pTc $<`? f0l:A+=< b;뒵+_)N~S!Z5O[훂⩟OTFg-sFTV ?y:Qm7ݗ.h8OkHبɭ8u[H;! mّ7͐w[Qۺ h/ity8I(\3BU55kʳT$x=kQ!7pfȼ.,WW3vrz6 ԳzS"L9r&ԎgU|ތ~~" (ܹrZ5Tj?Ӟˁ3,)M2>kEnuT'Jtgfdʍٞka/6χe\Ɲ$ҳ[ԏ7leWނcm0:dSX+vIVmxyw1͇̐¢*ێ}(Xڂxqȓk&6K(/$j3X@s%^G:ǍEKPƳ:F(Ц׆A+|CrJ4.,,A} c,qf]<~9[ȹ,MܵZxeճxYճ[Μy Ʒx`p3I;&ba2Gqak'ێšv>\xϛ~K?PF!,7]iіh+*PH<yO)=Mmص 6:LK~ 'iUzGd=0hN0ru)fÉ8m/! r%4>xHTA&'M`%nK8v:88D( YZSM}ű]41M_d=U$ {sM%i^(L^ ^\k uf|[Wէ;qG {jiJjG=ghɒI V?uBgvF>]Kj"+ MrtW m BNԥ='IW'mc9dw!'0`y/(}vVJ*Cw>76`EQ!·^DIgO *O~^Y?i^ #=lYN$a*=Nݏzz w>zwvitez9I*2ۑJ _wLa~r@b*ɰ^0D-~VNQ !&Rxc6k5pwz۳"c8h-+; 6DZ_:d1IY]A]ɩ>p+n'6-wOan1Gzmu5_'Lxia:w :W^ZEn|gkuU4+EatTFt">:԰>ab;r6 Gf:>-F ىU#3~QAlx^x!φDIHkaV }x6Fft(x ቍgV;_6E%Ӂ\}ϴsȓQ\*?c폎ytܱ'8bL;a14ar(*ĬVx]STq|Ri }-^ϥhlY(̙x!a<Ou  ੸nkЌܡ;J崗I5UVxk/.E޼ ٚ$*i>bV顤@߽q+#g>/όχgzBߍXhc&qQ ~>yW5`L+lNQOqPU}&s*g"O7RlGqc%0 -`fbV'N7ICŊ!9| 62rǟnM8hHD_2%g iz+n Cwıf^_2GM ͢N08I9Lbal$b @ˑ.kĞ |ItRKWNr@ `<0Z̗͓xb0ɞ}5L/#tbJz&͡c{0w h8%1acah+n¼!<&-XKꐫ ]f`0<8x" {6ѥ9ab{G-W>DZVY5勐1`L9g4b(+ê4o52Xe0@fU-/B6ݺh˱R>2B~s8:>S?$oUzΜ_Bt>2hm z'C! ΰoJgEdX9&i3ٌ3lC!PBQB@=b*cA^a9לa!<+;]S؟yqa:ujϟKjX܌ GF!Uq#9f9`0"4~LFMo[Y~ ]ICSMl*]puB0 ?Td0 py8!`0 C!`0 C@&Qy3 C!`0 C!0t/A?C!`0 C!`0 G`0 C!`0 C`#?ӽ C!`0 C!`0``7C!`0 C!`0 Ld3 C!`0 C!P@ ~wqExER ޟbLZ6Q `0 @a9x-v*@p>m<W,m؋dWmDG쓹l|XEU"|r0撟xj3uxʑ<ڋO|y5rj>x0s I>7>E+?[)6Ydzkhnԛڷ>hvnD,$S4Nu~8;m4yQQȘ6_ #n^n-߬v;=9L.,uC!`A ~&džm! 3!&KJC6z":1}r:IJ\8LDz Ϙg{h7e񦥞MD&f"H718#!)^ka2-;JJHHom7ŷKNsq6U?"HQg o?q$3IlyE.YӢoȯv; -w6 h!`0&1k#/[8Kč_\ ?s}LcR)!3WP|.nCXML=a*kPfKx5S?f^F3\6~͒vEyv\DbUlVpӍ3p)PR ͻ&i=Ow͘nB=\q!bAFz6r.ĜEqQ$9u> %~(\׾µ{"hF<a[s/NnڱGC!`LǔDiWϜPEق#\pu8:eo|̄jg&$HRcgM!IGGOFH6V՝8iGlڪ [BGC4\t5*I#D%<hXqMlěEشM9uKP&%X!`0fل2ɡ8KDŽo'MrW*?{u)y(.7>bn͇q %Do;HGj0o\ L@UOƼ^*?B #:6G: &^. wѱ`x-{y9?ޑ!:!,7@{ԗ4gRj6{1P;jGt|s2yփ@,b.>6{萯A&I .!D)XGZhsYy'Y-V!|a ~P(Ώ)Nx͊z jꙐWiyփb R4I{/)[gxBAI?F+?fgx(m:{0L+ŔVR-4'%QeBF,cNc} Ag{k.Hp—eQA_q w}wE[K*221Z҄\BIE)փnަp?/Fv]-5^,OJM=B)mSXleؿ,r_ PWhmVl Dƽw?DvJX!EY~65]xJ02 wG+ R2mzIMm8&Յ$YQˏ+i(:}<8 khuozn}sQOsً݉x,ŭhGz;唻YM4=>;?x8|:"rbbW#;X$, Iii4zx!jvq#Q37H[lXV@D#r2k8BƎḢ-U@1y=L}]Hq?C`t ׀V?'*Ҁ⳦1! k8p9A]=ہr'F0>ԍߓ'F oMx1,qahVN=E. RB3p1f4)F[]Gfn8x0j t"ᩒYU, AAE0Չ/#tS xq"rj; yob%t-D) ~=QQ?fp4ԵuPW_]ncî}(~GN]ԏlk)w44I7Nm} OS}_`D`"xe4 C!>Vc^]SիZ_ ]lm6jpti=x<%Px?<*.LFn\-7ʧ 6wҸ"_ωC焴Bݽ2`G@4wn8(pn=dT֗""!SS rܱoxꮟ9j D]$&IXQ &7&фg1Q{~^1’;9=LʻxCH0_;qc.Kʻ֎@.)a,$)| ~ <~_ r7wz\LjrIL1/}r@7/^Y C! `\w`jGG(^8t+f,Wj [k$_z%Z/+&n.(ݰR;㏕ Is8lB-ߎĵs A_xp_'Z^dD\WI4oGahL+\}=p Dž| A=F ψ^p'&\:N9*Ry r6/95څw'JjD4-mlK:#5b6,lWK/.R4t&gܚi@:tW k3ZLG MORJ1'ꋡGNTԞ >j(*i>Rxt3yp䎺r.YϜGgt=:u1JUDSE9 i^e!`0SZPbXMNɰ>AȡaVKlzve ?d5zV2qK$1> j@_E.Obqe ~FP'8ƗT',zzTf6Vc3X%[b7kmNSʟ޴3a%jID,uxP_` k":4j0Nw'=(L\1 C!0]`Ao?n2> XtTi[aVݵ~[a$rbuu•Ɨ5Ǔj=oP^¾̣tJ`$4PԮ],G{q-'3ғoCӤn&pWFWE^*Gz4q#Ưz_$9#aS9I9E7x/.~t_>!5~a,_d{sPCXrDŖG˻0&y1$ODoʋ?rIȟzNMQSЀXΟ <.wu)6;ڼt-C!`00.[vFc>ƅR8y.J'gFepuyYVFwzF y*ż a2z9e&6kx-y)9K baLK*$WZo Φbuصw A}WO님I@?>mQ(Gh[l%xE<<҄-2嫱HrG$|IF}s1$ۜl6Au-f$!9o]!_81丫OyJ+Gu%R۳O85+hK%GYl@}#J'_'b^i .HX#&"|:*|DX3+חz`PZڑ!';|l\wc` ȰGM ^!3*e%(_2QxHcO:g@E!t}tQWYMfwldXg uf(G[tpi{Q\K6\!DFv ŒD߀]AY^Z =yVɢa/*阊{Q[4i/f)Ǐ."OFq1xj0+ni٠]W!D޵PZUY3I;n x]j{*[n$(>ZhH)ߝ >}B#{q) =%$%cc*Ӳ宔+A~ 3;٪+ʃ-s&^ w;\!v< {Z]cich[bvbx^!`0 LO(pOôMrNTujK4N(dѬzhY0']Nzx?/f੅Yy\P֬޷6.ntȪEϳz4hHD?]WuZqW2P\t;Y0}2?k-w}Ei!`0 C7l(oiMXw[ ˶Yn߉muFbkts/Ba0 `!e C!Ə*AMod#?U寯~;[8z|=zkۄvTb=6 7N(K G9 0%{][9ķ`V1'p'8%/:aFw {a.uPNZwwP9# ~~I]Oj!mP:w>%˰4 ї2 C!`0 C`B$ . 0lͣxz[xpZqiH4Q/xc^?+>PZ#LܵC<ۑ%İ%@>F|'% BU>{FxWsC ; Nl'}a_ZW[aiWC!`0 C!0QLgKs3򰶡OA\*-& x5~/NO*b)w#`[ I[/ cuT"g>?mT3{A!`0 C!`L0wBn{l̯X5± #`0d}r&WlLxl?^4߆ }$T#C!`0 C!kl]A{t g/wKf"sX2fXNOqQtI0O/N[_G}y>xpF<(t&+¶9b`,hlh~BkulEk֠/i!&qÿ#Ϟ<`1g΀㻻m=_5a뢙D׾ąkx!߭<.ZAHE0;!=HEts4f̞L,]5٧?V*/rcp^ ]1?oq sn]BVp޾'$H}|X ?];; cøsq dG&e0`<-O=H>wE ~ֿ +_"Գh傮SA̘v$Fd^Y7?6z| cbnÅ$th/Z>Ã#xa C!`0 C`20U굗P[w~0o/$ƪ%3~MK bWa\w.~y:jmg}x-Q}}V8%>: q_#W!e,cZ[9$m i4,䆺}COH͠z)r.(5c2b2b}҇\h宻3yHLǪ}Pjvyb>wd4%kyp!oZ  6TϜ_H1?[R-07DHC!`0 C!+ ~^X=| }_Ҝ3xkۺ@,Q0|Qᰎ¿>2t7b Kfd%[?O.gЇOԯpfwVԯ[*KLMlB,J_^//Ȟ:,UZW 4H&'<ʁQ`}o'd1/2|RZl\ XkN!t~ʗ'Kwp6JHsIt]b 47.]u,P* mGZQ ?c.yFMLcx}w.UTO"  بSF !RS =}#UlPMJ( cR M@p)@~ٗ{Nf_r03kYߵְzVPȴ>}P>ƑK Iy&P Kd$ qX C/IWݕ>kj4(R:lSQgGp !]K-* w:ĺC;qY5&^mw[{@G~⏯Om8X~!q[KN]j(SǔgP*M11jh癫&      xaxU@STaފd5)>B\4^lLq,rwHpWuM*DvZ/kήNCQxS[ 4tmܢK.~HtCxYVchEsx*+`殘0<#JE={SbD~ѝ碛펗<^|ͱRnhʠ]8YJ=x$:jK7SYeç< d YG-skh;eE3h8Se?Dl]W86S7[G;8?]f[OT)X`pbTeيK~K*  ijݲ%EU]W{( yVj-޸ѣA$@$@$@$@$@O2cRHYX8Bpm ܓ,6 ez:tFg5VFZ.ζ s6tzr4CMeFZ/-c5ĈQC>OWuKjxjF >ҎfgvHۉϳb|kGw43:I$@$@$@$@$@-D >Gd_{/58vUM 3ȾuSַ$Tiȟ( OJF3yycSW3bӲ{Khسm^-eC:.Hv;+~=EڧϋstO a1-_2<nG]! zݲp텕ۦ*zd$uu*=Z!3ys҆f4v%+(@A{x]>_g^l-D4ԘG$@$@$@$@$@'O<9U=3/6|YE~blwߍX0MŠ SRʓ֗om/yP3YYi`ZOĐ f9{ *Ҿz"KPIc 0Yv;RvgsOKNjTTZ{dhylx#7}Yq=f,Gʄ +~au("q$6KZ+Vyi*QЪ}_4]FFuX{RbqUȱԥ3B:wE0E✷kt6JwE 5,Gdu'1o6^,@.NBfV{h4 `G6Ogf]~0ÅQqcƛ%3peːlߣ̣ D LD:i]5ʬ_}nϓ&.8W5떸xQdlB*Epa#e^74)x/QV{{f!U˵Oe]iѳx>'<+i'uN`YA@G\Z}.u7unnsB CmyFg7ULߖ] އبEyNUt爾h mFcFX!E Y*[ho ˶&JC$@$@$@$@$@$p%{וm!=ɯ.ENf>|B˪mZ*}HHHHHHM6 A"Х{-)Q^V8-Yn;(W҂װ@ rHHHHHHHTY?8e.зNXb ^\Lk= T$m@I~6:6B;t3'b䯣O2C$@$@$@$@$@$B $7e䑃HHHHHH:wno=FyIHHHHHHH$*~Lb2       hoio=FyIHHHHHHH$*~Lb2       hoio=FyIHHHHHHH$*~Lb2       hoio=FyIHHHHHHH$*~LrNԴ4;G6HHHHHZ@3(~1yNV+r -]|eJ/Aԩxp Z #@|r2M 4+q|66PrBl[pVڕ(;yukpt $%ߛ[b^/c[-ͦ[k8>=x}o?N9 pM܉Mb78spiTق3=שi|I2hqc%{-<|fi^mg ӑ H 8mJH0BCo+q۾"л{&rX/ĉ"}]L1qfwT|$흷f!7{5g?by_w%:y_# trȰ !^[o=|%V~1?Xs-ww~g55(="A~.QKno㳥̸NM :v {ߏ/@Ing?.;;vw b/t#sϠvպ5j?3\HP#F菏?آ\ [XȱaRKfF%>>`IM`ĒdVJeY2B H-6X6l0>6mdYyBc66siS,ؕ7>+x>s-D gK,bu`+ryۦXxGVn| Kb9;tmMSrn_gS~ˌtKɩs:K-x'-NeTN=yL[aqX O>ilM?\Z}QF%y@ƀ^飜,XI{Q]EtN@,TGBu>e y63/ mi˶Sܔ&|M,L/㳥Nj5Ut z(t.Xe#16⹉Z:_Scnl_k>VXr p 1 ? ye}1ڐOpҖ$@@v X+wʜ3W:SFrBa[8w"wݪƟ`R>{wů$vkGEQIH @GkϾibUIv%%M;ɉDS+iyS3[z4vA TlBMCZTwcߩagJД3>CDpND1!m"9c2 Ż3EXg8$b^{EًLxdL~*u֢ts2Peԇ HNNGi:4)>!gHz"@1v%Mݷ e{/upRp Lg=BooNśYe @bb_ =kv9GvlۓД&}n.px7|jj$ME_sF0|GF% :zYT,_<۽qV HIaȭ=P}ٲk>xɟ7tc*g8=]Vߢnr|%}\C#pKd82Kʾقuk>@^cP|k)~Ѥ XwY6,`}9 s$G {/рtݸ3_vHƧ|1ѷ8{ H>o퍐Zڷ ƛ|L0o烒.J/ľ=ѳy<ɫqOz>INƓ G PKrV|ZئL~,K'Ы%d^j,ӂ4K"-LP ƦZr׸Q)Gǒ5v_2iYڽm69~Wgf2rbWҊBm=KwP YK{6^ ϰ/W/c5jYf%(mK,s7|$L#m8ji~ə ,7XgS$3c+r1?fK%!Y+>+?vaWoAX.춤̵Tyڲjne;{ RN{hea/lR=Үs-gf^푉dY_F'5Z]Sw,j.%]trI_Rx,.4djm.<3>q8ͲWTS,[ڻr`|~G1-)-u7`Bc9ϔ\Y)UO2f>h /Ʃ?c[ly5R4cTOOy'\Ň|}~j<ɉccZU[͚"1~JJkbuPX^+c dK1ǨJՖ JLEGV,ž]PY]I|SS?YX< Cåo߈CG}O9EȞC5?:UUU*Tשk2})gBf.gE\#ɯ\}.W&e0K.%X0+ʹ{|,JETX+=彇{*E OGQgI/Y/X=^e{yyB)! ǿ9ִH#7rm#6?@D\Km=9T,)12lPk;J+q']`'IeGj۵vn %@lC 16N[jS>t u|>//yfI%~d}Q!*EUM:ut!˥<}⋘y.wBNI,s^BÅsu/`DLv:kQox~<ˬ!sx؝G>ɪM0XR[ *X9~(8dž߅lH}z+YMe5•sźGۿ9fq/NS> mTm\G>3 YV #׶KbV|m@|I[⛋[pq3Ty;_lڀΆDd?{O] <% @`,~4M:YЬ[`In'yb%ǓՏI ش’f#>RV,FdYi_,EAaH%jmiŐ\grZĤ]YUH;N6K4$#JGeic+X&'7ne.E Ga79EaɛliYcYBVdə츏9ڸ7솕";\"gxqzև[6XV,wd!6#>V/6PՕBKE1k%vm(N{ٞƂo߄\87Rmc >r1-KAnkheӬc?R`rhħX]n44a˦ī,N[_}'.tewβ6;]s;D vK[""7KCR">>_gXʔvXéXMMm+/U։݆lBt`N&Z&as<`Z]ˑo)鳷:Lϧw.s9wȩm1@P^3E1_4ڧU6KzIp,Bv˖UӜ-dQqمFdr8TodUuxXl2|~e?xz^i.:v.c?矴/Nu-k~&b ,鰞St!+#'+o Ge`{[kcc"]">P~v OL4L"wꝅw1T<;O_tqʕ 1bmTaY`)㢣c#3łGpr+Ku.uZ.c]%,[nRprz鳗)wAo薹dtZypٸpDa琹HGEڣ ,4Li?zҳb}V\߃1S(FeѫY^Ԕg繍%+ƔǓ02) Ir|O_[zَ$a"~˶w?x=n]ɇ֥yH<ҴpS\{ܫǧ/CyŚnv~k%=*sB:!((OqJz\!7 =řuA0:+OSX=Q>.==3#2_Ÿ {$ֻ'3;$=+xFik>Ec_BI)l_N.ymIif2ˣG;{M,CsgQ NVK{@J786pʩm(04 rq-%y=>u괰 ௰sAo݀j|-Mp zHM}kY}S\ 5OͣXb{#S`}J}M PwÀ1/kjʅc`>qQQ\?)矈|oYd_f𹑰}}>^.\9`g ]mi T[{J+P LMk"-֝Aak .yg+gۗa{%qFG5{>‼`Tc啜iQJ?ez~A6ǷdgN¤IS)>1&qR ~1˓^t5$JM][]g-gϵzC0ȠΊW8w VH,λUL>w5NaÖ9FE!^,p>_UJ;Xe85Xj=ޛ&8˞jL?x5E/P LD\LNDHf %/8B>bE:Gui42;c^y~ܗNQ/QvēȘiC}5;2&aaHiWf'/37?55t+)mEQ֔]loբX&hs.G?[=`t3REkp5רGs/a^m5]p,+9%.U=֥pT-ۉr([dK3;;`"d7yq߻7V|ĩj|惯3sAD ;oN{*Cs.CȞ[5`./I??M 2% +@?b̛2fbWbI!qJljKR4Tn,wb1clbzȞ/ 7Sr?PH; $b!gx6wG(gU!KѝIU@{xN~Zeԕm F!^$fBLkwO.NBZ|N1Eɲ>f &鬀EMWWE֫ei#/ǵu~5*Hz  WH6P <pGhyBCyDpK&:pp q̶`c%2Gg;D|ݱ_uBaGvs`Oqkenԧ\ 7O9ů)DD!j|뾀qqBb|)<m*n]YO5.OHH*$L+eHKTBh?Gd\wT_"Â4ۍ<2L Y-<84~%5Q\R蛻iu*ΫxŖ-VG!=o-z{Fb9ԓz\pqj8C6e] @*.γkj䨮Xͽ:}L.ˆS-^k%Vi H I+B`7pSx^l_˽ o:$8\,9}8XڽC%!y6\4ߥg.1f@8Rg/_c. JV Zwg_nzoj}fBusi78e)5=YI}w@惯3/C j,Λ}yP|)%`w_7 i\1cIH м+ÜIb}vR7r?t+6qSԖbR.k}9ejRu]L}^cU{T̠웺;l[ nuec=}g2b"fe4cp#hmD6'ʜ%i+9oEb˓0g L{(C7~/^nSgNTzc8pDž!7Y='q>%-O|9wCYSp)筽~Zrayֲ\,?}N+Ś#1~fL=6ve)N>=?M΄$@$pEhŏB+5;,)Û۩s^Wn+Y+Ћe;p}ϐQ^dl餚R WLLLjQ:.J{/"EK~wy.মkQf9v'ܫ̨n]iV\zP[6ro HwJw"`t~\7g?o@Yq>^aRk?ੜ 4e&Xa?6{mǿ*4|'0ytL3W-Y> p:M v%Q)n09n?f[J! j%-˰􃋁tW ⥿}='t?06B:4 ŢTfg gV:-ٛAR(G-aQy֒\T?G^ۙwo7~;[p#X2[Q(_b@zY cބݬ*-[`+12J8· ([ N [uN!7ls;JOƒ"g)*jv0%XY9e  Oݏbw{UFʧY觾3wӢcSbrVg4Vhݗ-n9ԈFRq+0.Y˰xXm]RKK2_/Mlk\L6H`&lWB aQ<;UR&bfLkB>|gSxs>{LXQ0RCdxzܞ78&d7iawI1G]ƅ@?\o7^3KtXtC DĘ!}m ;;r8;ƾoB0Μv c\?-sbE惣4)zeXoyԮ~7^[w}y~z(1!i.$=$un|NH'O|2'78#~2RɾJ>`S+wiB#"&%V%(UA:u̟ E!6&g[۝c>_\Y:Q^FJT:=5jd䮞u19&~QF"̊n2ssM>^$k]y+bw\hc+bHr3OȘ%NW :gsŧʥTJccxAИWn!5#͍["e~KK0!ٓU*Gi ƪRB=7-ΥQ,{°cbTO&Θrk>䰮+vc1NI"/3gb~r\(g;ġ+}@Z${&hPʉsbv7{r0F/Ɗ.7 s3r?CN1{\:q8㷈 V.wiDv{"3i͝%%eqvMwJq߭; 󸖒̎i lȝ_Vi3Ub7ѡ(a3O~\P{ᯛ>^ |]֘}zU9̬¥y[!Oü76\{vZc 8Zd\l¤a|߃]s-]Ɛ ԯgs9?McnjGŁPZVCpc"HuNNw,YƼw:l+v :<=b"7(|G|S#dQ.Zh`͚iCQ 2%`xѧPUy@/Z;)v{\*>ԥf)׳5mqL|E MIX=EʒEƞlQ!5wX= wI{+0{繤J 3~ OMFա#8z8^EqW Dvy -d5x4 &^2M(T㹂xlmC{A7^vVxϮP$‹pq]:x,eC9ѣYb+ywvEG>3vǷFg3*6רW6&y:Ui>z?|?>{I ):i|_?'PZq E>q>8U̩.Kc{d矂a /b?? ֽ11B>Zɽju+D݃2]]2t>yI$@>֧\ireaȮ$qX4 ƋdFt˄,P%KN<:.KT˚hCtt\Q>Tt*ѕ"<<\=Bh[.9gy+WMSwf#M2Ed\ըV,pD!$fVOCmֿ%+ 5%,+xIYoꔄŮgsiA @tL bU"ւ"<ʙr "c04110.Z>8Y ウCnPII GiHUPSNŔezfɃϤb<\)C1O Ej (}Pkx~h"C+ZZu ~$ v)J=țe:DI\4+ R CmGUsF}kS\w?3pkk aw+}XD؀ qH\qB9-6j%d^ĻY-^x8ZUt-;]n%7BxKqjSbL8t{wٲfQ?+}ZYBBLl;bKJ)}$̕PmYM-}cU_%{mp'Q߄;Xܑϱ`hH_ǵbf|Jg+w tb݇~GE9^]o\b>QFM'b]ww֭l)"9kbhx䉢!3MU+˺gS% _;n*{sߏO@B\7٫@R;}puczGkBtޝhmұ /7䟐cdRd?0cMWXW썦 M*<˰]ܛ̶09mS%XVXYGؿXmwݺHd]:Ғ(=oy$R3@<9zs6WlRqT%(xު^QNu'Wm2|7|=c_bًczd)sc!2v.olAk>}'N'*>·7//"A&,h n#GOoO6ɉV7]5h&l$p~ EZ؉uJ[SڽMOGDLs$%܅>?@XN\ayz%ϑLkwɏ;.70͛/PH{K7ذ/Xydo=5_ěa6鉋^&X FY;n[ Y|uR,&̚RrLzWQrtFo 9G|-~O^bٲeeKa%o6Vg5TV[|$m|lXjkܫݭśrǀXB%s KѱG޷[a[|e:;U'foT%qϼ?FcZlA$>O([C!hK1ե,{B"VQ/kV@/k    cE%^]80ܵ^ܔUa1lAt{b-7 H bÓ5: h`q*c_x ʩt!S%e N︳?4(-x LrY7vY/"Y  ]{ ]W~9L~G],O[bsYZyW'L]D㺆zb#.df   8>_9ch}u(߸#O VD\db Q=:qي%xqUf#`!IJ@BM; ' UCo NOǀx'sͿ\SS/9hɫ<%Vm?De;p _ H~t$@$@@ Ν?>uF+NewQz1Z7"~Zبgĉl9j4Q#n:gPR[) ]d8*9md$@Fh7ih?^/f_J^[0i3LןM'֣ K+o2       ?W`IHHHHHH\T\}˖ \O$@$@$@$@$@$@$pnM ~rB+l{'2/sW$@$@$@$@$@$@$@>h(,[k ʼnJ ڕ3QaPF xA*~GQ6UE yi0a E#mۇƤ$@$@$@$@$@$@$@$`@;U\^k% m/0Y?\HHHHHHH4Թ%cw6jl$       @;ibdR1YaX|u,3;)NpVkI~ vw@XT)_'`]$m+u >g`!      h)W(˼$\%ay d7քyp=#;GO\^Yo~/Zʲ '\ HHHHHHH+Ol;~b"5o6(QX]K2Q,Ջ+#evQBXޔՍFSEJPJ$_-Y_+ &a`X$Eʖ?bqEk}ΥHHHHHHH?=D"iH@IDAT %|V-~L.kr47])}ͿtNGQa`3Aq<=E2IU?ʬ/|KU*g?׳꧷,#@z>֧$@$@$@$@$@$@$@mMg^E10gg V2OD R.WoBhQIqU(}|w=jU"9eAQATv;_       6HPDjK%4)> SS{<Qܨ(vdcRO>r3W?z鬌|O+IHHHHHH(C;6*Xi,^L9%کí~}'ӫMo@k%lB(Ȫ |ϠF \#5 P?e,jźZl7Y+-OKZav4Ƚ92*a$      h+ڱG^kg(|KiU4Fl86|6#M|ZHHHHHHH>;Q 9O^xgSDRm)H\nieOR1|24Fք*`G)ske&DLB$@$@$@$@$@$@$zVPM#Y&j2 ZUGV"˿[w>YLql7O$zɺLMol3/;ҭPb&,HHHHHHHZ@TYɈoYF(_,irw:B~\~|OV[%- <v$fDS>J V-۵ho2WӕS       6GAis5*`)[WF`[@,R6(LV)[K7(z3Q,_C"H\jqzJV:,wtsE~7DSQ. >rXEI]"a곕O       6G}Z8FyD4'<7DQԈBc[butz϶]M'O@aQ>HKjk>)OH$_)EJ;(VA)~<ԧg       hs1bUc6}tdԨQmN6!Y7߈d(̆G懢9( |ͧ Jt(^~QPnn3,$      h~.kv_ YKX-[|ͧ% ʌ$@$@$@$@$@$@$5M@ƣ +z+B%u#(},.5#fh K0l?z6P9KYIϢ0Y2#E!%|:%>|0ŧHHHHHH$N? XF6ItLMc $wm;&>9 oC_w5)ULH:(G a iMM@l WR6dEN]pc>5״DS4|ILI$@$@$@$@$@$p0|m; ܳmץyM11ȟUym9HIH ACōW(bgoai SS4T!oXdJ{}MIHHHHHHS:<ΝII c S ۚ1/Irk&.)Ƴ^`|ڞ9&>Z}MP$@$@$@$@$@$@W +rW~0*H6$xܬ.ŗ[ڳJd{HHHHHH&N-~ﳛzt4Ojn( JJJ <8^>ksД?ɸ/7:Ha8Ӳ:ڶ3%К4g/M (|r, ?틍OƐ;#&3Z?ox͙a2k)#c򃸥GWZnU\\HHHHHHoMV[PY敐7SZWQMHw餇H̍^lj^գ s?*)u m\XmAx }Y&D]&?>YK0;&R⽈l?MĬ_>KmYH?X]:ۓթtٞMU9 Py3De+(! ]";X-[Rimȑ3i.CڜUOuu-Cs2fQ4T.4Aa,[hiuz>>oBf."YwEٷ8Q#t߀=(S Epr+k?S I%H2.6NJO-V* 9w=5]0姚ҪzNJuPL"Ezʮ WyM$@$@$@$@$@$ph׻z sM"      +G1ت-mfh5_C llYZ4ڟi^q6z311Q󏤏n/ť{!7^3_\`      B?񸭗ew=-.++"Cca#C40Qlq3.b}o9Y⤆&2<n(cSaOO0-4 &$     vODVFG_!TcB3+Y$dJ?xA~hHd495#ͭ\5C }:&L@^l CJG|Sgv:M &+?إ1$۵݄YxuC1p.i-K!     hLiik}RSbSU(۳{jv?~Do y-IWf xh%oۍcTT` s12J{RE"Uc[/ ^ix(rc f H AXTxNԅ 2BSUy`y?s˽Bc5?Q[a"c`>{`p ; gAYvnn7E!}trԨQ6x \gOaߎϰ(U|cе:uEB+$E;,T[Ufi %      6O*~?TQ( x[lذ'z qIX008MYlOج]8WsOA@ xReD Д֨Yf;3V4:8orIn^&I-L-HQoڗ>Wp]g={l}vnx;UsGI&`L 0&`Lv"L ?}ԯFCt*'Ad ġGWZfBqgp;QGFIOގ o'2&`L 0&` EIHK=Fh;df[VIK=z_\[V'EqIO`L 0&`L 0&",=~Yty2 ǰ. 1Z"֔bh*jPоuue5R8Z@֦Mʘ^vy7NtjœQp6: w@| .xdg`L 0&`L hPt s^|gCіFy9‡z Ş"SU :tbeuyxkvIQxb|M-QOBF}+æ6w9cDK[ E~gL 0&`L 0&e+DSi/j_6cDi3jC.FzQdDR^uy}6ƯzwJUR *owСڀ O2xݠ\ٶ-|=[ F=FQ0(1 2ĩѾD.wቧ=֚d1toJ%WC_[e׵ۜL 0&`L 0&exTWˤı ȁ~!mZRKw2M5C ||$S]U-h2-[֠^-] QSv6mf53<9 46)_Gibk#eO ~|~k[8<76>BlR]y'>5W~uY8)`x"& Tp 0&`L 0&pg/"*\.25"q0:kOչC:`]!gw+vIFI7;  Ř{{UjKUCz1-eTM:~qLGl6ξ?fe'}Lqo|/WaL 0&`L 0&p{pzMi`< ^37b߆f"tT{ƫYWFcsP} 7I߮h+jBN:\!fDqG;bNdfw&`L 0&`Li4?2H*/v]㩡żsr`#R~誖r7jߐNb'trhPGy'z=d!YKy9<[(20T ~8lV3`L 0&`L 0&& ?*hNoֈ!zܸ36,Pl&][YB|P,X|FA车R/ _TS&|`L 0&`L"ĦX hK(aQ#8nSAW!Zӈtkh'\,6E5!C[3[ՋTvi[>:p.`L 0&`L 07 4Δfhpa՟1[JY^t?y۟/z5GMtwN\Rb0۳zII}u1QW1Ws 0&`L 0&p fhtQs44L!ꮜGiŸV%Y|:ve| {+[_t᣸ BC5w^]-/i`Z}ȋ̮r`L 0&`L 0&~~,7X7p6eܑyU.c`~thY+WiݚpPHRơzofa33x-8{7hqZ;_ay ޻;=GcҠ@˥B:BE+W@}6\ҳ%.mGlՐ25Ϗm?C |x.t_(A-<۾;t 1Hws ><% AY' rl'^@i> 9`L 0&`L @މUU 7~_zbz V}Z ܣk+bx>7ן=h]W'yʹlE;5p􂯯AkkH xz;?|=v 5v٧]:hG|͗ҧd+/`h!ׂ˨e>aL 0&`L 0&%bȑ:u#sOǎξ+tl7P]Z_~؉ֶj8]Qq4=ܰN)wEѮ_.)TlC!/K.`c㐍j| 0&`L 0&hDtH1lBc{s4}4sH1kcNonn^s`L 0&`L 0whKA`L 0&`L 0&@3&fX+^"6etg ~e^ND6oX9/M˿ؓv3k4v FhD?aI*1xJOV˙_A!< 7/u;xz{Ã^eO7eQOqNSmqL_RC?]7ަj) SEh'ȶ*J{@zCYN9I } mB4y\e\M D.ϱ,IG\9l鉴ٮiBok*];/ .ּpC!x0>,p>r:ɤx{X7 ߶~TaǹtvƧ"9䙜}{G ^faM;blJGH;3fna"v{kyDG뢣ct gꖮLjK] kgvNHѭX[<-$& t9tF_$uhNQ}Ҷ+4tx\C@6cQINNZ>M1[?_t7*O { Z~k+#èN3';.%`mqYKO1W_l1KX:B߼b]S;?VJ,sk+Ǯtcϭ`Ԝy*9ou17>ֳgܘ=XE1PY}X?-}dafUhCDs&  mxӜG{Eo:|:Omqu%O#KW5A¯E1w&1yhPl*!UhZ+{ 1z@o^il0$-1CA_E݊LtcƠOnƵ8]\.Z.TCQԾ$I m[<Fc0h|Kh|.Gb3+**Prei,v=s<3QFE099.`}*ưit_0~z{x䙸?;@3r HN QhaEQcaT=QcthGgSYl|wthڒ 8q$s8w"/~Q@ܹȖ2;}<:};yofF m0ֳ1 I]Uڸ%fbcыmcv2e˝>4s{dF'Y<8Z[ щIbP.B J.O{vOߑdo몹)ƽ}û5X)4Ȫň'LZTl=p/tZ N䟦Z#<} imZVJsU6.F=1}z].̶0“s%ϐR*:(3ݐ|MJ.WC_X~r1Z_ _nԲ&-ҝ=uyݐh.䱐*= c|RnM|.+XQ':Tdd8gB}Ҡg|GM_;xB,m4fGxe{HGoSI1~r -:]yn.1:?/^נкs,ʏTטM9S5Ԭ<]QȾd,E}5(X5ud%5j]^I5A5맠kO˧~5Kp/jFV ?8\Vp^C,\kL!ո|V鿔ې4<ڨGё]C|qXqq;pS_rLƵѯ/KȡC(*؇> U3*ˑbx?K8^T*gҍH}&~DGa#8^X q x(V= .A&(yk{F }W_Ο?EQR-5.w/ee"Fn7+lqC8^Py `.1)l} Rt&l?R@}i'Gyڭg̓Q8ʐ 8s"hZJdrѓbuJ /)DvQoOō޵Q!Sc^܀ok06H(r I6-BQiVTd-WM 2.%GJ%k;8 ^cҚ4/Lԟ`Z{S OU/GWy.!on޽G pZzxZ ))gnTT;;y2!=]u_0lzRUJL˨0(5b Ŧ}_/E.T_]spѿKk]-J.ǥWQUge̎ų[_i>HM; ;>Wqq7ԙ^ʒN|r_y()^o7'y^a'7p1 ȇ $o*M_ij߾,zw%*bD+r\\{O>75Z;3 xfN!,jˡ 2Hߟ5}ʲ۫gIÕG'yrK愾;>#Źyw/<8.,tK ?w]x}$Fhܨ@)0u.Rܩ cH~?fG{$6 8rxQL1|?4vQ-?A/NǰG1e,Q?i1&xISg`֌8S+U^/EEi)J7 \2WUHz"wcQMŃxmK\.Ҍᢑ`=iX" sN t,␱`,qFϤg;CssngNԓ}zftFţms֢/vAvHĨIpِ[-K'ϾC-?טUԁZK1;^bF|h$7;b&gsYwIT9!jыEҴS' hhC:'~CrqwXti Ahds:V= yugw =HuC^4Ϋ&%?iR[aGKv~pY~QhC[ Ӿ})r 5u7p  Sk7P[s W7QG+ʏ}I-бK cVf='5ਾ(}S#$E/ZOFd2(cإ>"Cp14gkG[ի:l<ųQ %@>qtV¸/q}V-/X ~cѸ7FT|$XՠRYbIAv aҀi2c],eغ!ӌsI=k\/ZI^gJgspHO}Ϳ͛/5UMQ:MX0?Ex7U)Sxd:z뙾gޝw:3+]?vYwvd$aͼJ hF_}dOiQE_\Ǹ ʧjEO(e'zq2zO{(?AP%qg/]',"Ĺ Р+Ē0Lzs E4ʑY}՟*?UJ mUnC⫙JB| py%?lڣOK-_)!|u5Tq}<q_7a?]nh1H FwK|`^s\gv̓j[i:be^1v OE~lPɬNkw0mcܭgjH`%E3{:vQ9`gS]){_\ʖ}EuXé>h=ˣ^7w`ec1pן/Gݝ{Fg(=-VJ xL QrJ}@jSUGߧ {_/<3x]m5T}1Jen ׵YR2y—b(34m-3B;`v;?nZJyrΟݛ KK5Ld4 ճ ^Cav8u$Qy_AoVԗ_Y>}$U)Y?UU3fC XCO.F\Xsu2pPM˱炦{5P,Hu~F $m@cLl6 5!mk<ޛʬQctb^E:> -fTn3;y&4'Íԇ:gv΃ׂpן߇׍ܭ$-pw>B:#%,_\!nx?u+ܯgk8yo זp=w"wUf(%h_T_Ō?F zruR6NT7K0; i1}|A#2$=Apy8E})gxyG#;ncQ<%IDAT澳ζ N/pb7+ņL KlV5Vi׳9ÕyEՏxa}QFlOQ%CX)%&\Ÿ]mg> ~w$ye5s$ƽQ?.P*z$Z^~u02*F}11Nmz͖;9Cqad@UTՒYxz!,y/zt{Qny}[9N/#äamZ)&B4SKҎ=\cQ};>#wo䮘Q%KF( # cP|{6jꉳ ?E/חkVxN!wtl[>[.@nL!FQ^o TsegypkjevE6ۇa\n!|]LD|kEݤg j8'6E]e.'R3 :}i6x).V|0tרT$*1=Jd"S!0K7|{is78Zy+n_LZkj_9žN ѳKr!va)Ϭeh霾Ƚ:ʳ,X \b625߉ ~GdOIȶvSFh,o noc9 ?B8!'7)‡"ѯ[ZS3ZZUJ1e/O ©5Me򞊋a͡G6q7J׵ܪg^[{6tZ D|KjL=sw̻Ȟb+Y[5VuvKi#;~|J;ЦiY.yX=paG;z@]VUL"gZ2I,~O30?u8W!u|rF _4ݙBrC⨘&гZ7Ңd3?1/557t/5?;//K^FT۝wS‚1vp@调g;oU᪃MhK=5q4TQ^'.tQI2;;Nqy=Ғ;KwM>>GlGJ_(}qӁZ2ϽR6iov=4ܹ@%?ٌO"L#ۜ㧤e=<46aW1W}iM\q/.W 7(b%D'czlƑ'e=9huqK3 h͕J o)pT O\A=6,y q.x-0&ɉ U ?GJs09RmPT,wR,~V ( P*0ȘΟuٕ󐗵 m<}//Ϥ(6ϓ/ w Ťm}XECzf&ky<zRĵbܣya$_6R9hh\B QN6h43qã'ũg-"3g2{)z?̴tާfYtk:nceɝ簞ee{߇F3@vM›#M7$1K3܂c*(_ 2M[gJ|&}e|M/H\iӲRy _w75LT=8EbuMsƬV=3M/ؔ#4 &bOg>O$]>|:S8ٲOG9, L} _GOأȱrBW'.E&QXo31f(8SZK_ d~r9&C>oK瘞75RwulUGD%uwT7PQ/oRP~|s -U&3MA Ɛ'CǹKո>rW_bb?b&S=/lVfwqh\*3qg) c;twccKXa`1PH|*QrQp:!pb &O}Ɨ_> fzz{'c Ӟh:˜v"6~aTnҗmzX9mbkw3q'V>CAN,-'imڣ3A_NXgI#.(? fQ=+<ݩgj]/\Ă/x8޾ =D#bJJ?ճϐ{1 A,ǦQ'Q!$ȋ^gnѼ[їwFrZOuNg^]21\~2C7;3bD½(2(fUUmڳ"v1>~rn:P+nӚYf׉\[K.2* bvy4FHpoqWBɥK(.z/ VCǧ4d zҽ8"*=9(EUm =!1L29LVᛷ&cT1Ia;E-H6ɽoZl|dzU$Ay8IKa=[9s+xص)Q7 -8s1jN;/F:ӨN/0:Rx曷sBQo^w*N">kZG"62ҳ;ҧd ;yfӯoO o8}:tBΝкYZpڨ+{O]h}dK>e,+ۙ< aBQu?t =^i7߁v SW\8{ǏÙ+g ~[ݘ-=˔ y|$zŸKX)f_E F7o*y>Gb]w rRϢ83>[6|;y7noܸs}Byz]<::0٬<]AQ.5&XɬnC02C[Z0mƃHv -Cb`p !O 6[!K:ןdhV*3H 7S_Z/ƬUh_S)yxX(SO6_X $}Qt o[.; 4lsWPZ >ؕf" mzirȤTt&  NBw5?_okq qfg&`}14f1%f۸/Te3_E\ʻ8.0(6F b9f֯f=S1u(>XϣPSq_/mPg֥yzmKλT9j9myE 9ZXfwF}ꯟ8,BJWmTUZ~*FUdE=E ђ?;Pn 琉Ѧgr\HyKZf8~O+JG0HxKOt #qwSv;,4F+f{ggG"lמR1Wde,{S-ZFiM?hY2b>{z#W8FZk9gkJѧ>]: )ߓC+OŨGy5bD5TpQuiK?dӽz!x01SMt(.yp8gyKз$|%I?r0=4`E@4ȑ#ujΝ+;Vm:z fa]K-*_An:[k. cѫ[G NcVX8 wŕXQMFc#{ѯ5r !٘titdĄwDYXaRzmH;6J-JD`G[F\ҙ\u&OOCESҙn\(DΪ Λ5=SRZꧥkywo)i"<.aL,YiI$~y.2iL;pW|/(ʳ(#W'_phg=ϓ9&ܭ:ZGͰ(=\,6`gEc1^jOœ5;|F˺<9DMY{Cq }%]UxrHӳ.ݻɲ!Uz;}3A\έxeY(R++/S)%}kgElo=j䱖gZ&gj@.Y#ӏ.//FP;>Ea'1WWϟ= wys6rkغq$_ҙ_Sq?~e?b7lޭ^.A^',6 S~"a˦m>+vGʉ9!y0]8uN2ϖsA-v>6hz jFÏe!8aܭ۳gnÂ1N$Gxǎk kV*C8yjƘ kmLbMFmQj};&If䩖z^zp6Բt?g>̄܎:/޻v|>fiɉV<-m  hf | 0&pRQouM[>sqؼrE?.g$/ zL 0&p[VO y`Gm054{AAq,rC`0'b3 {'/@3%`q8ZOCӆ"|t9t%Qx T+@2W/0H)p8&k("ҠN]A @0q %[ք71&xqv\ 0 [: ?6 }; KRh Xq5^]>j.fL@n,lBg+m8ZJsZԿ#ze\Ge{7|VM/ws؆ ]2n-Sd_pS%k^Jv9ؼF2&D7Qܹ69rXp҃I5 J7le7& "hw^ Ν;l3s[<M3=Nqp:N]CA;x\g ;nH:G*_~o,˸|nFLӦ!h^) L D& 7†7@.`L 0&`L 0&8sSP>`L 0&`L 0&lqd 0&`L 0&`MA ?MAdL 0&`L 0& &`L 0&`L 464u 0&`L 0&`n 7@.`L 0&`L 0&ԹO&`L 0&`L ~`L 0&`L 0&@SlN xI3:p$8 z{Hv!0&`L 0&`My~ e Щj_W"x2v ǩ22Ki 0&`L 0&`64XvP3oG kŵ@ ^/|iq1f)`L 0&`L 0&Xo,m,3}HgY@9<摇L26`zLvMoj6ҳL 0&`L 0&@pspgQ}[8{DZ%:Ɛ]ka Z|0&`L 0&`L@zO>zO_(VkDtr3׿ 5`$N9H5ZOI(kAhѪVPi@V26lHVjcYLm~!jh| 0&`L 0&p;~^tUJiӍ8vݖ M4au#g隟xH/cH} %d5dyR%h9aj}M^bdZJoH&c#RUwF*#/Nzdz1j-`L 0&`L 0ۖ<~%<[:n9?iw2ɦ޳6d#/dyZiCKp7c^"_Dc2xgs ]'xPϩ`Lc5"!SҮEkt}to L Aީ#2&`L 0&`L (δźlx9pxQO!#CI1D/VuTFNaG8ڪo)?vSL H26Y;V]dQٔ|&`L 0&`L V @KKJL&;(@ )֎OeÔzBǒ*_p/29 Y0|`bsB6"& 0&`L 0&`hܥ^da_i;PZ:%꒺%hc6 ?Ų!F‰"/V B֌W.䡶^hmok9g1&`L 0&`8X ϞɰR,Ķ*/]vd#,o)"r)A_ + ArӵRjζ-ǙL 0&`L 0&0p'b,X6AA]><}lt޸C-# /HX̡w! giI2 S*$L 0&`L 0&NyO[~C[=A xB a'8&ˆAAEsfA [}> 񓼭a@ r8 1#`L 0&`L 0&ppR/ v|Aӛ .eooi4h'1q u{ۈ~vkza`:<2y7.;&`L 0&`L ?$a ;ZՐܼ͟0 &8[_ *!J!u<"=wH6A1he$#~-V+I6LIL 0&`L 0&J^BӟqCma!#_z{4~!c y 42 0ɘ )U#Po-/ Do F&u~A;Qۏ'02#ل#vBwS?qZIgL 0&`L 0&B5|j/dpDUK]%=_>D;h' 0$(f'x9z6N/.g޴m3/d>Em~UK*􁊔8`L 0&`L 0Z9ReUΝ+;v 63VdBnf*fj*BcvFh? '͇4'%x;?!}fXET?~S0&,˲ѧ(;SX"gk&V*Y`L 0&`L 0&(Zgx)fi2sWZ@=\-<dr)7]j!ϜlRO@GeW,ml1HG9ǔ+^ڃ -z.fdq,`L 0&`L 0\je=-EK_i?OD]y?ʭWjLl) h` fëfК1&q hZr?/Z"i.`L 0&` ~nB4SCpߎ@ C+6R0 ?ށm(,!K~—z0vmDL!wR[]I KDߦ_7CX⸇&qZ Z nd Lj:6!<6$OyE]Ws\Mr0.Ĉ$\0>2"}zHo6^ UtGDP0ZN@흙{*C`1KBpN( ȇf&#g0&`L 0&0%(qT [ۢE Zl!ݲEKt½}bڄp<؟".dL!ɟ^ ]r jQUNgeK09{NhW/F'G2;I ח"}8SF܏$lBAULZ \Oۍc26`a%q4'|#AmHGёo9 @aV1a#ʯm(z1Zwr|6FSA 3ӥ+SGTĕ#OXۏUO79!ӊMHP;Qs?C1;`rl$:Ǻ |ɨژ0o;SWѦMtzC;/ `L 0&`L 0k\X#h;[O7?DQ{`G)rRW]`!ZxVࡤx`&ҙ*Wa2(G.\V- &,~B"kdj箛''!A6KAwg>?U*hK ʑYEg%>12[_GNa%=1zRU /{́vIE(ʼnHɫAgJ|z{|p:RW)уp~ݻW IABljrll$OEXVH^m#nF <3ڄA1q6s"McJAbmc:[ Gvb\ 0&`L 0&nec {@:OݎwM˾k#7 /:`bޑ߲h;w{'C)Ws_"wo,r!JaGbV: ?GS,)ߐ] yCRعmZ1Kߙ`L 0&`L@#~qA}rATPco/,ȿ(~#6.쮳B֣䊴@2PY;r qŚIu9Zg)_1Yk+PZJTZjÝy^~m#ai2@W_i9 R5N2]2Fd:üldk+rOJNA*s1?))8~^ b'b=lbUu9`L 0&`L[zkpDJss'*Uc ~7P]11LݡCQ skqS ;CMa\(}+hKxidW(t硪\\=rGvMTϳ.kERY Iqb HjL 0&`L 0&ĝCtDkZނ~p LOTb/͍$u3*])Wv oHߣ@^ڕ㧤%D=0TSRmn W*q#/+WF@}g]x6j| ]5~,JBܰIx7xw1Qq߈J;<(Wl4$'ZlP,©h疒k7hr?@:0 !~jx"s=_?͌(@ P(@ PRI9dx `tJl cuL. GžOa" ; ٛ|F.Z'#25!`F>MŰY=#܍twg2&uy$ln(;ӥB\R+zq]?sc`Yf=&7Zˆ%#7o;"o!}7sK2Ϗ<>D P(@ PK@~ c|n6x.%|o*<:1_WI76V`mGXD8>*, 0Jy>{',zz D4$C+,Co] j߅}`1S^_]&=c w:Y3q>C510VU4ϸg +rҺ7cNVhU^Ю}`4bH=g+Ŏ_fv~a:oELR#LW?OP?v,3 P(@ PnXݿ/X=TZqE8{gn୏ӧayU5)e x KbKS[a"j,,Yk؏f!^ !(3~!6lGAVC+Σ5pXyg (ΙYa,6=hHf|=Eb9|Zj"`9X{`զ۸Z;hA~õbs L*(C |ijQ:KW\>GD?)R(`y9 [ JN$< (@ P(@ +$i ,p@~ cl<hooAS% Cy]i-:Gw@2oH 2-Mu8:\ǥihtޘEଭUԵ Cpp ð"1TGMo|b<(@ P(@~'+/Wԅ9}ۏ[5Zڤd:(@ P(@ P-Dzu?t~/OJ P(@ P(nUP(@ P(@ A~.Q(@ P(@w0EA P(@ P?}Ka(@ P(@ P;q"(@ P(@ P}P? M_@9IENDB`graphql-ruby-1.11.10/guides/pagination/000077500000000000000000000000001414121453000176745ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/pagination/connection_concepts.md000066400000000000000000000073471414121453000242660ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Pagination title: Connection Concepts desc: Introduction to Connections index: 1 --- __Connections__ are a pagination solution which started with [Relay JS](https://facebook.github.io/relay), but now it's used for almost any GraphQL API. Connections are composed of a few kinds of objects: - `Connection` types are generics that expose pagination-related metadata and access to the items - `Edge` types are also generics. They represent the relationship between the parent and child (eg, a `PostEdge` represents the link from `Blog` to `Post`). - _nodes_ are actual list items. In a `PostsConnection`, each node is a `Post`. Connections have some advantages over offset-based pagination: - First-class support for relationship metadata - Cursor implementations can support efficient, stable pagination ## Connections, Edges and Nodes Connection pagination has three core objects: connections, edges, and nodes. ### Nodes Nodes are items in a list. A `node` is usually an object in your schema. For example, a `node` in a `posts` connection is a `Post`: ```ruby { posts(first: 5) { edges { node { # This is a `Post` object: title body publishedAt } } } } ``` ### Connections Connections are objects that _represent_ a one-to-many relation. They contain _metadata_ about the list of items and _access to the items_. Connections are often generated from object types. Their list items, called _nodes_, are members of that object type. Connections can also be generated from union types and interface types. ##### Connection metadata Connections can tell you about the list in general. For example, if you {% internal_link "add a total count field", "type_definitions/extensions#customizing-connections" %}, they can tell you the count: ```ruby { posts { # This is a PostsConnection totalCount } } ``` ##### Connection items The list items in a connection are called _nodes_. They can generally be accessed two ways: - via edges: `posts { edges { node { ... } } }` - via nodes: `posts { nodes { ... } }` The differences is that `edges { node { ... } }` has more room for relationship metadata. For example, when listing the members of a team, you might include _when_ someone joined the team as edge metadata: ```ruby team { members(first: 10) { edges { # when did this person join the team? joinedAt # information about the person: node { name } } } } ``` Alternatively, `nodes` provides easier access to the items, but can't expose relationship metadata: ```ruby team { members(first: 10) { nodes { # shows the team members' names name } } } ``` There's no way to show `joinedAt` above without using `edges { ... }`. ### Edges Edges are _like_ join tables in that they can expose relationship metadata between a parent object and its children. For example, let's say that someone may be the member of _several_ teams. You would make a join table in the database (eg, `team_memberships`) which connect people to each of the teams they're on. This join table could also include information about _how_ that person is related to the team: when they joined, what role they have, etc. Edges can reveal this information about the relationship, for example: ```ruby team { # this is the team name name members(first: 10) { edges { # this is a team membership joinedAt role node { # this is the person on the team name } } } } ``` So, edges really help when the _relationship_ between two objects has special data associated with it. If you use a join table, that's a clue that you might use a custom edge to model the relationship. graphql-ruby-1.11.10/guides/pagination/custom_connections.md000066400000000000000000000147701414121453000241430ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Pagination title: Custom Connections desc: Building & using cursor-based connections in GraphQL-Ruby index: 3 --- GraphQL-Ruby ships with built-in connection support for ActiveRecord, Sequel, Mongoid, and Ruby Arrays. You can read more in the {% internal_link "Using Connections", "/pagination/using_connections" %} guide. When you want to serve a connection based on your _own_ data object, you can create a custom connection. The implementation will have several components: - The __application object__ -- the list of items that you want to paginate in GraphQL - The __connection wrapper__ which wraps the application object and implements methods used by GraphQL - The __connection type__, a GraphQL object type which implements the connection contract For this example, we'll imagine that your application communicates with an external search engine, and expresses all search results with a `SearchEngine::Result` class. (There isn't _really_ any class like this; it's an arbitrary example of an application-specific collection of items.) ## Application Object In Ruby, _everything_ is an object, and that includes _lists of objects_. For example, we think of an Array as a _list_ of objects, but Arrays are also objects in their own right. Some list objects have very fancy implementations. Think of an `ActiveRecord::Relation`: it gathers up the parts of a SQL query, and at the right moment, it dispatches a call to your database to fetch the objects in the list. An `ActiveRecord::Relation` is also a list object. Your application probably has other list objects that you want to paginate via GraphQL connections. For example, you might show a user some search results, or a list of files from a fileserver. Those lists are modeled with _list objects_, and those list objects can be wrapped with connection wrappers. ## Connection Wrapper A connection wrapper is an adapter between a plain-Ruby list object (like an Array, Relation, or something application-specific, like `SearchEngine::Result`) and a GraphQL connection type. The connection wrapper implements methods which the GraphQL connection type requires, and it implements those methods based on the underlying list object. You can extend {{ "GraphQL::Pagination::Connection" | api_doc }} to get started on a custom connection wrapper, for example: ```ruby # app/graphql/connections/search_results_connection.rb class Connections::SearchResultsConnection < GraphQL::Pagination::Connection # implementation here ... end ``` The methods you must implement are: - `#nodes`, which returns a paginated slice of `@items` based on the given arguments - `#has_next_page`, which returns `true` if there are items after the ones in `#nodes` - `#has_previous_page`, which returns `true` if there are items before the ones in `#nodes` - `#cursor_for(item)`, which returns a String to serve as the cursor for `item` How to implement these methods (efficiently!) depends on your backend and how you communicate with it. For inspiration, you can see the built-in connections: - {{ "GraphQL::Pagination::ArrayConnection" | api_doc }} - {{ "GraphQL::Pagination::ActiveRecordRelationConnection" | api_doc }} - {{ "GraphQL::Pagination::SequelDatasetConnection" | api_doc }} - {{ "GraphQL::Pagination::MongoidRelationConnection" | api_doc }} ### Using a Custom Connection To integrate your custom connection wrapper with GraphQL, you have two options: - Map the wrapper to a list object at the _schema level_, so that those list objects are _always_ automatically wrapped by your wrapper; OR - Use the wrapper manually in resolve methods, to override any automatic mapping The first case is very convenient, and the second case makes it possible to customize connections for specific situations. To __map the wrapper to a class of objects__, add it to your schema: ```ruby class MySchema < GraphQL::Schema # Add the connection plugin use GraphQL::Pagination::Connections # Hook up a custom wrapper connections.add(SearchEngine::Result, Connections::SearchResultsConnection) end ``` Now, any time a field returns an instance of `SearchEngine::Result`, it will be wrapped with `Connections::SearchResultsConnection` Alternatively, you can apply a connection wrapper on a case-by-case basis by applying it during the resolver (method or {{ "GraphQL::Schema::Resolver" | api_doc }}): ```ruby field :search, Types::SearchResult.connection_type, null: false do argument :query, String, required: true end def search(query:) search = SearchEngine::Search.new(query: query, viewer: context[:current_user]) results = search.results # Apply the connection wrapper and return it Connections::SearchResultsConnection.new(results) end ``` GraphQL-Ruby will use the provided connection wrapper in that case. You can use this fine-grained approach to handle special cases or implement performance optimizations. ## Connection Type Connection types are GraphQL object types which comply to the [Relay connection specification](https://relay.dev/graphql/connections.htm). GraphQL-Ruby ships with some tools to help you create those object types: - {{ "GraphQL::Types::Relay::BaseConnection" | api_doc }} and {{ "GraphQL::Types::Relay::BaseEdge" | api_doc }} are example implementations of the spec. They don't inherit from your application's base object class though, so you might not be able to use them out of the box. - Type classes respond to `.connection_type` which returns a generated connection type based on that class. By default, it inherits from the provided `GraphQL::Types::Relay::BaseConnection`, but you can override that by setting `connection_type_class(Types::MyBaseConnectionObject)` in your base classes. For example, you could implement a base connection class: ```ruby class Types::BaseConnectionObject < Types::BaseObject # implement based on `GraphQL::Types::Relay::BaseConnection`, etc end ``` Then hook it up to your base classes: ```ruby class Types::BaseObject < GraphQL::Schema::Object # ... connection_type_class(Types::BaseConnectionObject) end class Types::BaseUnion < GraphQL::Schema::Union connection_type_class(Types::BaseConnectionObject) end module Types::BaseInterface include GraphQL::Schema::Interface connection_type_class(Types::BaseConnectionObject) end ``` Then, when defining fields, you could use `.connection_type` to use your connection class hierarchy: ```ruby field :posts, Types::Post.connection_type, null: false ``` (Those fields get `connection: true` by default, because the generated connection type's name ends in `*Connection`.) graphql-ruby-1.11.10/guides/pagination/overview.md000066400000000000000000000013541414121453000220670ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Pagination title: Overview desc: Introduction to pagination in GraphQL index: 0 --- GraphQL-Ruby ships with several implementations of Relay's "connection"-style pagination. You can familiarize yourself with connections in {% internal_link "Connection Concepts", "/pagination/connection_concepts" %} and see how to use them in {% internal_link "Using Connections", "/pagination/using_connections" %}. It also supports custom connection implementations and type definitions, which you can explore in the {% internal_link "Custom Connections", "/pagination/custom_connections" %} guide. GraphQL has its own [great pagination docs](https://graphql.org/learn/pagination/) for further reading. graphql-ruby-1.11.10/guides/pagination/stable_relation_connections.md000066400000000000000000000120471414121453000257730ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Pagination title: Stable Relation Connections desc: Advanced pagination for ActiveRecord index: 4 pro: true --- `GraphQL::Pro` includes a mechanism for serving _stable_ connections for `ActiveRecord::Relation`s based on column values. If objects are created or destroyed during pagination, the list of items won't be disrupted. These connection implementations are database-specific so that they can build proper queries with regard to `NULL` handling. (Postgres treats nulls as _larger_ than other values while MySQL and SQLite treat them as _smaller_ than other values.) __Note:__ In GraphQL-Pro 1.12.x, the {% internal_link "previous stable connection implementation", "/pro/cursors" %} is still enabled by default. See [Opting Out](/pro/cursors#opting-out) to disable that feature, then enable this new one. ## What's the difference? The default {{ "GraphQL::Pagination::ActiveRecordRelationConnection" | api_doc }} (which turns an `ActiveRecord::Relation` into a GraphQL-ready connection) uses _offset_ as a cursor. This naive approach is sufficient for many cases, but it's subject to a specific set of bugs. Let's say you're looking at the second page of 10 items (`LIMIT 10 OFFSET 10`). During that time, one of the items on page 1 is deleted. When you navigate to page 3 (`LIMIT 10 OFFSET 20`), you'll actually _miss_ one item. The entire list shifted "up" one position when a previous item was deleted. To solve this bug, we should use a _value_ to page through items (instead of _offset_). For example, if items are ordered by `id`, use the `id` for pagination: ```sql LIMIT 10 -- page 1 WHERE id > :last_id LIMIT 10 -- page 2 ``` This way, even when items are added or removed, pagination will continue without interruption. For more information about this issue, see ["Pagination: You're (Probably) Doing It Wrong"](https://coderwall.com/p/lkcaag/pagination-you-re-probably-doing-it-wrong). ## Installation You can use a stable connection for _all_ `ActiveRecord::Relation`s by installing it at the schema level: ```ruby class MyAppSchema < GraphQL::Schema # Add the connection plugin use GraphQL::Pagination::Connections # Hook up the stable connection that matches your database connections.add(ActiveRecord::Relation, GraphQL::Pro::PostgresStableRelationConnection) # Or... # connections.add(ActiveRecord::Relation, GraphQL::Pro::MySQLStableRelationConnection) # connections.add(ActiveRecord::Relation, GraphQL::Pro::SqliteStableRelationConnection) end ``` Alternatively, you can apply the stable connection wrapper on a _field-by-field_ basis. For example: ```ruby field :items, Types::ItemType.connection_type, null: false def items # Build an ActiveRecord::Relation relation = Item.all # And wrap it with a connection implementation, then return the connection GraphQL::Pro::MySQLStableRelationConnection.new(relation) end ``` That way, you can adopt stable cursors bit-by-bit. (See below for [backwards compatibility](#backwards-compatibility) notes.) Similarly, if you enable stable connections for the whole schema, you can wrap _specific_ relations with `GraphQL::Pagination::ActiveRecordRelationConnection` when you want to use index-based cursors. (This is handy for relations whose ordering is too complicated for cursor generation.) ## Implementation Notes Keep these points in mind when using value-based cursors: - For a given `ActiveRecord::Relation`, only columns of that specific model can be used in pagination. (This is because column names are turned into `WHERE` conditions.) - The connection may add an additional `primary_key` ordering to ensure that the cursor value is unique. This behavior is inspired by `Relation#reverse_order` which also assumes that `primary_key` is the default sort. - The connection will add fields to the relation's `SELECT` clause, so that cursors can be reliably constructed from the database results. ## Grouped Relations When using a grouped `ActiveRecord::Relation`, include a unique ID in your sort to ensure that each row in the result has a unique cursor. For example: ```ruby # Bad: If two results have the same `max(price)`, # they will be identical from a pagination perspective: Products.select("max(price) as price").group("category_id").order("price") # Good: `category_id` is used to disambiguate any results with the same price: Products.select("max(price) as price").group("category_id").order("price, category_id") ``` For ungrouped relations, this issue is handled automatically by adding the model's `primary_key` to the order values. If you provide an unordered, grouped relation, `GraphQL::Pro::RelationConnection::InvalidRelationError` will be raised because an unordered relation _cannot_ be paginated in a stable way. ## Backwards Compatibility `GraphQL::Pro`'s stable relation connection is backwards-compatible. If it receives an offset-based cursor, it uses that cursor for the next resolution, then returns value-based cursors in the next result. ## ActiveRecord Versions Stable relation connections support ActiveRecord `>= 4.1.0`. graphql-ruby-1.11.10/guides/pagination/using_connections.md000066400000000000000000000070241414121453000237500ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Pagination title: Using Connections desc: Pagination with GraphQL-Ruby's built-in connections index: 2 --- GraphQL-Ruby ships with a few implementations of the {% internal_link "connection pattern", "pagination/connection_concepts" %} that you can use out of the box. They support Ruby Arrays, Mongoid, Sequel, and ActiveRecord. Additionally, connections allow you to limit the number of items returned with [`max_page_size`](#max-page-size). ## Adding the Plugin GraphQL-Ruby 1.10.0 includes a new plugin for connections. It's more flexible and easier to customize. If it's not already added to your schema with `use ...`, add it: ```ruby class MySchema < GraphQL::Schema # ... # Add the plugin for connection pagination use GraphQL::Pagination::Connections ``` ## Make Connection Fields Use `.connection_type` to generate a connection type for paginating over objects of a given type: ```ruby field :items, Types::ItemType.connection_type, null: false ``` The generated return type will be called `ItemConnection`. Since it ends in `*Connection`, the `field(...)` will automatically be configured with `connection: true`. If the connection type's name doesn't end in `Connection`, you have to add that configuration yourself: ```ruby # here's a custom type whose name doesn't end in "Connection", so `connection: true` is required: field :items, Types::ItemConnectionPage, null: false, connection: true ``` The field will be given some arguments by default: `first`, `last`, `after`, and `before`. ## Return Collections With connection fields, you can return collection objects from fields or resolvers: ```ruby def items object.items # => eg, returns an ActiveRecord Relation end ``` The collection object (Array, Mongoid relation, Sequel dataset, ActiveRecord relation) will be automatically paginated with the provided arguments. Cursors will be generated based on the offset of nodes in the collection. ## Make Custom Connections If you want to paginate something that _isn't_ supported out-of-the-box, you can implement your own pagination wrapper and hook it up to GraphQL-Ruby. Read more in {% internal_link "Custom Connections", "/pagination/custom_connections" %}. ## Special Cases Sometimes, you have _one collection_ that needs special handling, unlike other instances of its class. For cases like this, you can manually apply the connection wrapper in the resolver. For example: ```ruby def items # Get the ActiveRecord relation to paginate relation = object.items # Apply a custom wrapper Connections::ItemsConnection.new(relation) end ``` This way, you can handle this _particular_ `relation` with custom code. ## Max Page Size You can apply `max_page_size` to limit the number of items returned, regardless of what the client requests. - __For the whole schema__, you can add it to your schema definition: ```ruby class MyAppSchema < GraphQL::Schema default_max_page_size 50 end ``` At runtime, that value will be applied to _every_ connection, unless an override is provided as described below. - __For a given field__, add it to the field definition with a keyword: ```ruby field :items, Item.connection_type, null: false, max_page_size: 25 ``` - __Dynamically__, you can add `max_page_size:` when you apply custom connection wrappers: ```ruby def items relation = object.items Connections::ItemsConnection.new(relation, max_page_size: 10) end ``` To _remove_ a `max_page_size` setting, you can pass `nil`. That will allow unbounded collections to be returned to clients. graphql-ruby-1.11.10/guides/pro/000077500000000000000000000000001414121453000163435ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/pro/checksums/000077500000000000000000000000001414121453000203305ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.0.0.txt000066400000000000000000000002011414121453000237500ustar00rootroot000000000000004de5bbda7a3e9ed29eb3cdb25a0cea1a91f57dd34bb73827628f49a605cec244583cf0b9b6d9dfab932573f99c2ada4c98f4ef569fc82fa32b046e0ffb3e976c graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.0.1.txt000066400000000000000000000002011414121453000237510ustar00rootroot000000000000004951e730ead0e493c21ad337af84eb99551a1e4c66b35c8ebfabc000b1bc59d13c4c45c3081fa68719f56522ab1f1bc4c4258e634c0da3414cb82041ed2e122f graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.0.2.txt000066400000000000000000000002011414121453000237520ustar00rootroot00000000000000bc165db6aaf32d51e710626eb2baa36851e0d9ca4ff88676d0fdd9120264e599e326dd1602713197308d010392a46f7f3472e805b5a1677af18887e3e7b0bdaa graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.0.3.txt000066400000000000000000000002011414121453000237530ustar00rootroot00000000000000077630f850706f87104890dd7ccaca7fc01e44f73c2f5bf5750bfcb5b15a4b58e2028aec04ed3066abbeecf7863af399bfbb4ad1bbc7aea9c034faf949118b5e graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.0.4.txt000066400000000000000000000002011414121453000237540ustar00rootroot00000000000000e9be770ff2117a4692a11f5b5c8e5e9f5095bd7aa7ca24a9e085dd278a548caed28396d100302ec52ab295aafd7fab849c83420a49311034779ec30eb3b4a232 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.1.0.txt000066400000000000000000000002011414121453000237510ustar00rootroot00000000000000c934c912626c926921a4e5260edb3e1d108d3bd3b680137a3077a0baa6e802966399fcbb52c3b2d60333db9d8a5e508a4c8e3344ff2ef05555aeb76967537be7 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.1.1.txt000066400000000000000000000002011414121453000237520ustar00rootroot00000000000000deca840ea6676077e39608a63b3d33cbc032f69ab147d2a1c679da8852e087204b3c6b643cde3bde411d7d1bb7cf79bbc21b504fd08940ea5b6cd7cd868c3e12 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.10.0.txt000066400000000000000000000002011414121453000240310ustar00rootroot0000000000000070b8ebe4f3b0c99a16a544aa26bf0d9c8605c4e1aed1fd9374c93f7818bc08860690f60b3f28f48a6cfa8c5864312a2ea94b7c8caead40f6f609f07739d55ec7 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.10.1.txt000066400000000000000000000002011414121453000240320ustar00rootroot00000000000000d061d329ed1518519382f182ffc48ccbaa9cf7a2abea701a563477bfd3dd39418accc67449d6891afff6482c0c14f05a7a459ac42e801dac6fd1c91e6a0de974 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.10.2.txt000066400000000000000000000002011414121453000240330ustar00rootroot000000000000006f8c9be62438f95aa146e302c7234d55ea592fda72d483873b20140412cc912b3e1dba8789451448f600c15c38f40989bc34c42aa18ceb1f796cab50c774afb5 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.10.3.txt000066400000000000000000000000001414121453000240310ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.10.4.txt000066400000000000000000000002011414121453000240350ustar00rootroot000000000000006e900c64b27dbeee3180e5e624dd8eff409305bddd95bb651e8105a00a699bbcf9dc321df87c815372221e02b332ffbb9512800a60e0daf4ed323b181f4e681a graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.10.5.txt000066400000000000000000000002011414121453000240360ustar00rootroot00000000000000aff26d02fb3eb8751f763b12e4085c24e7efca383f1117b3b5ad119c328334f7f3abfdb294f9fc3f33a466c031f0ed856bf459d979eac93e7eff4f0f76610774 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.10.6.txt000066400000000000000000000002011414121453000240370ustar00rootroot00000000000000e304f7f23f6e940159d34f0d871054070a8235d657c141157072655d00787e9182629280509012160c757dcd1afd12982875dc0197e381393051d0798293cdb2 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.10.7.txt000066400000000000000000000002011414121453000240400ustar00rootroot0000000000000008d220a1b2580d9e763238d553838b692f26262be563b685e9ff69e02a6c03fe125647bd6b193256a4dab58aadeb45921ad986b51ad0d019e69b77e8ecc61650 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.10.8.txt000066400000000000000000000002011414121453000240410ustar00rootroot00000000000000de7618f67acf9016e74bde682a3fbcac61a422f6e61d06f85eebcf0aabb10111a55d2e05054bd68841909e0a53c73104c369c3e919447329ad6e1aaa2e8e7f26 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.11.0.txt000066400000000000000000000002001414121453000240310ustar00rootroot000000000000008f856d904af40ac0424743a1a40bad96408908202c031d7f0d67a74a085c000bb5db17e9e1c97dfbc79095b4006f82d18ca6f67fe830a655895ebda0e614ce71graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.12.0.txt000066400000000000000000000002011414121453000240330ustar00rootroot0000000000000075208ae66c220aa3d275a3765a7e15e9f589efb4fa7135fee57b33ecd13347f12ce8b4f5207b8457693a9b8f68eab5f11db746afbf2d0eba436396faf5502421 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.12.1.txt000066400000000000000000000002011414121453000240340ustar00rootroot000000000000000da30804e51647104a2b0be0bc9c5ca3ed43d8c64db33ae9afd7f7b73923a9243824e2d44a37cbb47372ec6a5c6b5aa54765d481dc51b885267ba7ab4672484d graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.12.2.txt000066400000000000000000000002011414121453000240350ustar00rootroot000000000000002a12902ed9321419fedf166021e50fdbfd695ca7b6245bf10a4354f95f38ac60de7bc4465ec873d6ed1af7adf0f631308739de799243e88a893f7527b8df9df9 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.13.0.txt000066400000000000000000000002011414121453000240340ustar00rootroot000000000000005446303fdfad4e2cb2bb47535d60bdf11a3e039c7b1edf8b02f9e8218ea1b87367f59726e6ad227e32d74a51f39f776b36d5f0fe59de1e72475a38376509cfa6 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.13.1.txt000066400000000000000000000002011414121453000240350ustar00rootroot0000000000000079f699934f0e9da8f328256b5c67144046341011e0629234d0391f206a807a00f457c71e00eb03b8fd4f0779156d189b54b56f6d7525904acb5ea19d061fceaf graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.13.2.txt000066400000000000000000000002011414121453000240360ustar00rootroot00000000000000e1aa0bc146fa154e3fb3655fd8e96e4fa7adc27b165ebf2c56880f64ac1938e3ba8ce8e7c7c02e921d54255ddfe9f7e44ecc3fad8675eaa0b1055813e989c2ef graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.13.3.txt000066400000000000000000000002011414121453000240370ustar00rootroot00000000000000a1f2ed08b503cf48d8fe7e0ac7f09d194e5f215de1ddce9acad8a0a6e9abc0865d62583b84af9e2a18a8ede3caa47d8d0b148caca986813bd4d03ff4f5c9ff5e graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.13.4.txt000066400000000000000000000002011414121453000240400ustar00rootroot000000000000003e1684ac0a06712bb565d0c4eff852f8240cee55824b75899d044db2815ae60c20fd367686a69e0b4f3bdd9764da18691d8af736561a13303d8be5cb9648b56a graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.13.5.txt000066400000000000000000000002011414121453000240410ustar00rootroot00000000000000d481fb6595d6b0cf5a5cf0fe2c29367dd4687c6fa8f7a12373b896dca6e0b4464ccaa6a46d8f8dcabc247ce0b1818161091b18f39ff14b8d2bbbc72769a0df23 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.13.6.txt000066400000000000000000000002011414121453000240420ustar00rootroot00000000000000e380c037088472eac1d71a64b419dc917eadbcd5e91d0273cf97d394d9833cbd93070d9ae3f8227e401a953a6982571713a3672df9b7d8ca37f1f216b9d3011a graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.14.0.txt000066400000000000000000000002011414121453000240350ustar00rootroot0000000000000022543434eb5bbb551f8a47fa2fa7f41f9210128b34e3bb8d88e47faf02fed606c9269d493fd3702cd8ae9861998ad967fff7095775a0fb8af8aa012b9dda06f2 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.14.1.txt000066400000000000000000000002011414121453000240360ustar00rootroot000000000000002c41c982e963f91f777e9b7c31dffc62d30e10cc741affdc168d94233016e3b07ae8ebe3c9631ea57b7a2433fd353f2087557dcfe2394d64e379485a20723893 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.15.0.txt000066400000000000000000000002011414121453000240360ustar00rootroot0000000000000006757f5a7d878584a29864cc3790e9687d119aaed5fe3c66afdf2ff498ceaba63c0bf9bc4fe81fdaad2b5e933acd7a889c33cec825abb673f6f624c16c5a110d graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.15.2.txt000066400000000000000000000002011414121453000240400ustar00rootroot00000000000000ec02be5c1b0e7defc6686d42d3033397a144de006ef7ae9ce0168d3aee459529edab8ca357a95f204010bfa7cbd14b7a0c7049892cad968ff2ede267c5cc5224 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.15.3.txt000066400000000000000000000002011414121453000240410ustar00rootroot00000000000000ba4c18f634b340b6064fbb919f4b33445d0c0c16e81f462d929c9437498c1349571f055d73e2bc0388a7e34a54316592a9f606cff12095b9e70483d962349a71 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.15.4.txt000066400000000000000000000002011414121453000240420ustar00rootroot00000000000000bca1561e9657893e6143404525b0857be42dbbf2148b64004e09207c5dce5b5c98bb431381989ac73b846d58e68d5eae12240ff01abf89660d0c7c66ec2f5b07 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.15.5.txt000066400000000000000000000002011414121453000240430ustar00rootroot00000000000000dad57401f7f2029b45dbd3b3c307025320b999e9d22396888cbfcec41ebac21838d4fb7ab63925ca3fc4fed67323842cd6788ccd20c8e6f315dd54f76d848e3b graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.15.6.txt000066400000000000000000000002011414121453000240440ustar00rootroot00000000000000476db12210ef3f92de3ec5743d06fa371495932773161f7e7dc574db0b8f45b5beef99d677394b3c4e5371a1b92ffb7c923e02c27f39ec3c570812b5480acaa7 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.15.7.txt000066400000000000000000000002011414121453000240450ustar00rootroot00000000000000fc69c5d1a8335ce8eba380e2d4aaf61e0f704e9dfd293fd435b485d3f84a191bb32b3eec91c8f718d307306abaea3b23f941e2e68cb8623c220d4009aa94b0fc graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.16.0.txt000066400000000000000000000002001414121453000240360ustar00rootroot0000000000000028dde609677a6303795e12452bad9e35b828bdc3cbfdaff5c40a8d30b6f1f53df6158fcb42659a5d55b89a696c9c0ec484c218c3df8c68abee4b02a141c5d284graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.16.1.txt000066400000000000000000000002011414121453000240400ustar00rootroot00000000000000d0d27966bcb3df9bb4f7b4188db4715321ce98beef0d57f759b003f79edf1d03dc6691380b0f4eef9f1124d5bc6932878e262af0ce98dcdd23a421b22c5232c8 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.16.2.txt000066400000000000000000000002011414121453000240410ustar00rootroot00000000000000b181b6fc70695498b5faa3216664b9849a6b8e32ae36517e3cd82ea6373e61821e4b0e193339ce2e92ee5528d9c694794bfeb27a3f2cd822c9576168e37e6f77 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.2.0.txt000066400000000000000000000002011414121453000237520ustar00rootroot00000000000000ec288ac8a27c79d493613abf9ae867a6ba3af28a029166d100c1153cad4c4039e8ba94beadf633d2889fe2af5c3bf91998c4ea67f00ac08150e9209b52c49938 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.2.1.txt000066400000000000000000000002011414121453000237530ustar00rootroot000000000000006362be7dc25124d8cda2f4ecace1b54088c8af7137f0a8789a80df63ace77ea25b5b9f09c72abb6e73deb204224d38f6f8de6a314fe255acca8fb8de3cc379d1 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.2.2.txt000066400000000000000000000002011414121453000237540ustar00rootroot00000000000000804dc71671ca8ed64f2fcf5fdfc7913758109c59b78a724dc6d6926829044aa08b354a862dcbc1bdb403def1f24e1e34eef2115778b671f87855cbfba62fa2b3 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.2.3.txt000066400000000000000000000002011414121453000237550ustar00rootroot00000000000000513828ef6e829ff917a08095443e51224f8a7dd5cbd2379e350e0505ad412d36fa9e151dc7ec034c65c4b564ff41ec4e40c9bbeb14501a245dd4bbfd61a64c78 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.3.0.txt000066400000000000000000000002011414121453000237530ustar00rootroot00000000000000cf7d74176481decbc4b8eea28a1133076fd184856bbee95f583f9c0818771028ec65d55f7f711df439faf5c1c85a47f22e7f4165665261922aa8b6e0d0d950d4 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.4.0.txt000066400000000000000000000002011414121453000237540ustar00rootroot00000000000000b51cb1d642400b5a3099d0b9194b82050d812d4f1ba782c707f7f3090931439baf241e4eae745452399f48c60de64082689069cbf3fb1b371c943f2fd493dfe7 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.4.1.txt000066400000000000000000000002011414121453000237550ustar00rootroot00000000000000e565badaef42bdcf0c9dd679947d46bbb9149dfab40ed481f5195df6e24df4844df2c7fc333df52b7fac05d1008353d99d7930591bcaace4055d41bdd1358e08 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.4.2.txt000066400000000000000000000002011414121453000237560ustar00rootroot0000000000000026805a034902ede828fbf023b88aa7a9c03f0ee6dfc8e8dafb1404fdc1c64959a5b952d794d356ed08f05ce1e1bcd2d1be48054578003885ba91ad002d94c470 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.4.3.txt000066400000000000000000000002011414121453000237570ustar00rootroot000000000000001922b4218dc1549263354398c358fd79146de111b8e01d32408d0bcac382f4b90e27106e6338e36e9e36175761e8c0a55a2c86b01239cd64ff7a2ea9c2cba5ee graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.4.4.txt000066400000000000000000000002011414121453000237600ustar00rootroot0000000000000065231e030a03fb97fe4bb9d57d3c6586f8a243cc164dcf9e9119046a1ae96ec527d498639a1e434654747e236f412130219a3513152e9190974d2e0a8f00af2b graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.4.5.txt000066400000000000000000000002011414121453000237610ustar00rootroot000000000000002934e5e305b16beca9e37e6b55f4969e24877c86e68c2825ed8b8fae1e84c3ee97ecf3e9450c5d34d1bc1410a32c045f60ca65d544a9c960ab1d3ef5fb5ec18d graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.4.6.txt000066400000000000000000000002011414121453000237620ustar00rootroot000000000000002956ec0ff31daf1aa6c6b7078ca56807ebab9f24cf8be2cca2bb2e0da164ecb0efbe6f8a37751af0179ffd84c90243e136363a47db18bfa0bcc7acb1a264d903 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.4.7.txt000066400000000000000000000002011414121453000237630ustar00rootroot00000000000000f4d1816ae87e21d2d1f8d7ab1ba75606f4714259ef8190e8d24fe9dfa708c15311fddad571c6815b8bb4e324377a0634983f262f3d4bd6068625d71401f0d850 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.4.8.txt000066400000000000000000000000001414121453000237610ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.5.0.txt000066400000000000000000000002011414121453000237550ustar00rootroot00000000000000a2b3e35485b4cbd0656aa0b6a6583809e8db9364a913ef94415c40d26fb8b55201f77d91cc8c411f45e4ebf949530262478a99e4655a4573f3cf709f82a43ab7 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.5.1.txt000066400000000000000000000002011414121453000237560ustar00rootroot00000000000000a7a844b3e17989a6a4b7638627bc8045b0ce68330e683c03af4960470aaf53c381490bf16e1fb216d79c1c7db528e634d1c9559a1cc9635a39cf3bda84cf4502 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.5.2.txt000066400000000000000000000002011414121453000237570ustar00rootroot000000000000006578f9ddf46edd80fe156ac11799f1e979c581affbddca2ecadb4246934722666677a88e648e3d4799008f9c482f5ff4477e8cb62b4838196d07bf6665f64074 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.5.3.txt000066400000000000000000000002011414121453000237600ustar00rootroot0000000000000026d1e4db3e66dc9469053d424c29cd5a02f9d91d16d5db2eede9fe6c924640dedf27e304a99b38841550eaa1d0a1343b3f17cea0f40b6277d0f94a9df5257130 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.5.4.txt000066400000000000000000000002011414121453000237610ustar00rootroot0000000000000060c95c06fdf6b106683cd87ce993f2b8f6903f85050826d26bd5d579144c4ab71068166c7e9d6c47577c38cb93b2f4095f87b60a69e8ff391338d4c935c0e5e1 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.5.5.txt000066400000000000000000000002011414121453000237620ustar00rootroot000000000000008657b73725fe9390deeaf37f5d6a902cbd2939f8039a9ef0e96d3a7000ab5f835023aa323dad399ef8a00c8686dca5397295b8aadaa9367105976720c9dc9e61 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.5.6.txt000066400000000000000000000002011414121453000237630ustar00rootroot00000000000000928afc8c4526b1869a9e88b2e62115a97d9a062e5bc36a3393a85d887a038da293a08e9baa828d6121a1d0a03974aff7b91bed7cb38d30ebe5dbb5e3e8f96cf7 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.5.7.txt000066400000000000000000000002011414121453000237640ustar00rootroot000000000000008b59d45fd5bb4debf257f4215fcc0e304ac31bcf814cb2684fa49fd1696fbad35d88f6e578c502f927819bffd1efce4dd0909e59a51ba6a4af53618e5ff20165 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.5.8.txt000066400000000000000000000002011414121453000237650ustar00rootroot000000000000000119e593bdb84bc500b61bc4f4712d5f15b33f5fa5e6860f9e0ecdc0215ef7e61dd53472f5b03fee5ff149649712e9cf1ffc25141875c9cc4eab0cb2c9ec626b graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.5.9.txt000066400000000000000000000002011414121453000237660ustar00rootroot00000000000000d82db8271dd1bae9d6b49b0e3c9c9446dc95cdaa1405b99af6bf65c551dc8994c445137ec98e11c8b61363b9490ea2fc22ea98f966dbf645f0dcbef2419f3220 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.6.0.txt000066400000000000000000000002011414121453000237560ustar00rootroot000000000000000fa5c3a16722351ac3b57f54b2cab3dfced63a5cdf1b90d12824e6b69424135d4ae0187089b6b7cc27e5a47f1bc89e2ee111001142789eb1bdab062e048d466a graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.6.1.txt000066400000000000000000000002011414121453000237570ustar00rootroot000000000000007da501aaf5560330a34d52d8df2170b2dc1ad307b922abe2d6a2da117c3dad8281385bcd8a092a4ed6642e7bb91e114d552c859511523e8f719fca5ee50e8171 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.6.2.txt000066400000000000000000000002011414121453000237600ustar00rootroot00000000000000804a6575faed960eb1081ba5988fcf53d89c55f68b251397aa5a59fed1e9e0e4272bf18a27dfa03ea15d72271893be93e40423b54a8f2eece9a314ede8a5d517 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.6.3.txt000066400000000000000000000002011414121453000237610ustar00rootroot000000000000005e8d1483abc2f5e65e0cd7dddcf826b0fe2dda0166ae2e13e107bf10c0268c0267b21b851e4f9671877fb5a0cf9a71081e2536eaa76d3f5ee2adf9e899b6d751 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.6.4.txt000066400000000000000000000002011414121453000237620ustar00rootroot00000000000000c4cbd897270ecd150eb59784d7a568c61e9d9f7c9ad9a99cd7547be857cfde0ca268f1d6d0ce726d4f6003882253ab6a63d334e406a1fae4cb55b07fc9f61b38 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.6.5.txt000066400000000000000000000002011414121453000237630ustar00rootroot00000000000000ad004946d3f432fe8c3694e8a8410d6ce6f29a7fbed0d785d05e488e6ecb8ccf523d2d73514fcee8481ceb66053d9eb88297f4fab25d031938c08ee2dcc58968 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.7.0.txt000066400000000000000000000002011414121453000237570ustar00rootroot00000000000000377a41d7a5831d0d20176bd7ebc7ad4ba24cdf7bdbc2879eb35844ece7e48390ca598061b58b6fe60b965af7f9097b353dc30b9ce89180ac03bc54a4fe3ee59d graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.7.1.txt000066400000000000000000000002011414121453000237600ustar00rootroot000000000000000c22a1050619fc84f024a8928831c8b1dcc733b05742b249c2928d4d97f7bc4106839a1038e92671b42ac8bf2c0647dcdc09a6c7cd8ef240a67f841bf7ed3ffc graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.7.10.txt000066400000000000000000000002011414121453000240400ustar00rootroot000000000000009cc3900fa9b04a667146595141586592e1c7f72d4fb88d6af8e95b74d50cb8c096e5abd00d35e295a66dbdc7774b0afd311a20747e6051d786da36564282aeca graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.7.11.txt000066400000000000000000000002011414121453000240410ustar00rootroot00000000000000983b45f0aa5a7905d98cbe6571a4028fcbc115eff42deaa378edf5591b546a6b5e5cddfbbb39b5d363ffc16147dc72e056bd66c8f3e8adba97e58064f2c12695 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.7.12.txt000066400000000000000000000002011414121453000240420ustar00rootroot00000000000000739bd0c352ef4d183426504ddd41456d05191fcf3230214e9e84d7011a84c65263ec3299d9ef42a829e49d370ece69d15cd3f3d913bf63043fd95f55b1f6ac48 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.7.13.txt000066400000000000000000000002011414121453000240430ustar00rootroot000000000000007d9aae99f424473e4e07aa6e119645027d8da99f062c162cc7f67929fddd886192745383b70e053e2df6ae2176208c5781b92203f8a7194c1390ec13dce36a12 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.7.2.txt000066400000000000000000000002011414121453000237610ustar00rootroot000000000000005c8539eca13d99875f50d8a77c27327ab0c14eea88e31e0ffb78a81990f1e7bcdb34b2cc2ba0ec647cbc36ce74c4d990f6483381388166d4e6a9191c77c69d4d graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.7.3.txt000066400000000000000000000002011414121453000237620ustar00rootroot000000000000007c1076a9065fbb7fe69796e8bf33454e1ed59aac0fb967b08e7756193d3a9aa41ba4f92e36ddb9e7d50c73cce42d516c0d0d2492ba1ec3f1af005597513c6f1b graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.7.4.txt000066400000000000000000000002011414121453000237630ustar00rootroot000000000000003ab8ffacb8cd9ee0b22695b63e8ba4a4602007f54928cf4745b2d6a8866bc8291806ab492526e4ebb2c297ec27a7bb605afe674d440ee3027d4c192dc06978a9 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.7.5.txt000066400000000000000000000002011414121453000237640ustar00rootroot000000000000003ec4d622f1a1d2efc910872db03f79ca43c35294fa7a93adf5283c68faf21cb7b2a9116d47a8b0e49f174276b84751d8e9e16a1a51bc20ab5ea8a40df2b41183 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.7.6.txt000066400000000000000000000002011414121453000237650ustar00rootroot000000000000002f7fd5c57b8564bb9af06d82449a46957c6adf723dad61092d6e2fa91e79966fa15f6dbe49df90e99f56d51c9808c1c5e6f25c80855728923a69bd77646d894a graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.7.7.txt000066400000000000000000000002011414121453000237660ustar00rootroot00000000000000a3625b161363afb04bbf497ae1af1cc3c580753c95a2d281c1788f162fb146c92f4b73b7faf053cc73df6db61e90b4eb37d3cbe7247b9bc6edc92f217f6e691c graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.7.8.txt000066400000000000000000000002011414121453000237670ustar00rootroot000000000000003aea6af5e7ed2a5ebab879d3ea65fea6b60be60d7b3f3663d03831797b3970beb896a82a035b8782c693b0f9e36c9f3d3b937ea09b165ee1478f393881f5ba18 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.7.9.txt000066400000000000000000000002011414121453000237700ustar00rootroot00000000000000a9098604f33ebd469909ee7be84dbe8636adce7e91199c442d95ddc35da4773fe7117057cf1a0449611625c902de03a26d015f82b8917c97b8bbe5e4f1915d4e graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.8.0.txt000066400000000000000000000002011414121453000237600ustar00rootroot0000000000000066ee5869510364c01668107c6343085e9f2755a07fcd4fb65d251e57773fa317c24bb2a3da091f796523a30bad6f6d310bb50d0c57d3a29a3ff4bc65215e5c2d graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.8.1.txt000066400000000000000000000002011414121453000237610ustar00rootroot00000000000000924658f0f8cbd15afa5210d46c2e2c13a768ad0bb49e3bef00096b1d8836600a5e00d42ed7b3f04b49870c4042f97916e13f1a51d3ea367fdc5945fd4402df64 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.8.2.txt000066400000000000000000000002011414121453000237620ustar00rootroot0000000000000078623f5ef2359e170dc04eaaf1b5397b74082f962b35bda766e2574218ad55c2b490884f1f76e7bd66acd091f68539b6ecf5f8eedc63190c26aeedb976278562 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.9.0.txt000066400000000000000000000002011414121453000237610ustar00rootroot00000000000000cee3e8dfe8a1c7016ef9578edab3c81dc0ad7f87df1e97a860ea2ef455fb2ba88ecf8120529b0feea683ce623ce2b7e6172e97bf2850f73ba32b858410661676 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.9.1.txt000066400000000000000000000002011414121453000237620ustar00rootroot00000000000000ecde6cf51e0eb328ee6185334acc9f16b1ccae2568a8ea7f45d501a14a66ea2af76b41e240dd255e4d05e8fd5b8f3d6866c02daa7f30a6e0b1053ced95bee1e4 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.9.10.txt000066400000000000000000000002011414121453000240420ustar00rootroot000000000000008a7526d55d9dd3e4ba042b4e950105a9b99b09373fdecf8017ffb002bcb1aacbef6ebdd213588ec96558e49c2f23e9136f3167c928156a815045176ff2161b70 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.9.11.txt000066400000000000000000000002011414121453000240430ustar00rootroot000000000000006ff8342db34dd6f7a7a7e11b2925df9205810ca4ce15ba2ec8a2ec79eafebdd0c1d7c706f82d6b5fe536d7b19c4ab4eda2c0129f1d0db9d6f597a2c92f5b38d3 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.9.12.txt000066400000000000000000000002011414121453000240440ustar00rootroot00000000000000ce8e9583918491e341bc47eb6a08712ea310d91361f7324e9e10574e60cde062deecf58306204267a08bb8a8ffdbb85fe73fa8382f91fb51cbc6f3ea7919df9f graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.9.13.txt000066400000000000000000000002011414121453000240450ustar00rootroot00000000000000074391c2bdc294a1ec027d043a6bf172f70bf5ff75f0062821cabd5ce7469c88a3b60a0ed644e8bae178fdd9db26acef20b0721fcc708c1825da256ec5f2b4fb graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.9.2.txt000066400000000000000000000002011414121453000237630ustar00rootroot00000000000000a2370a8984ef9acb0664cc982bfc22fa2fc33df631b9e5b80fa8a2a9e8ef68d0c296f1f297e405c44b123445de67edc83dfcd6960b53063b0add7906202e5fb8 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.9.3.txt000066400000000000000000000002011414121453000237640ustar00rootroot00000000000000f70be733046923edab517e4bfbada4dcbc9f9234d4706bce59a8d9f23100545d994e03c49ebe525ad4435d33b5abfe25191c690fd20d8e16ea0aa0979438f21e graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.9.4.txt000066400000000000000000000002011414121453000237650ustar00rootroot00000000000000b9b8771b0dea2b4fe2d426ae0c84a522884c2b36cedd1095ded49fef9bc90c0373d70ff871635223f07f12ac9a067978d91d4b42292070e44f2a5f223e5f4d88 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.9.5.txt000066400000000000000000000002011414121453000237660ustar00rootroot00000000000000de80808d62c6f49cc391ad2f0badc6040ac174271656649f0837aa11bb9188a5faa7b33c6a7640129f4b2df4cb1a00c823efcceff4f96aeac77655be4c51bd20 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.9.6.txt000066400000000000000000000002011414121453000237670ustar00rootroot000000000000003e14feb6dd6a14d26c76f3acce640845f786961d4718730d4509e3b72990cc2a184db8e6d17f612fdcb1cd5f5e85a02f72a8bc69124b7173f0019664baa5cdf2 graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.9.7.txt000066400000000000000000000002011414121453000237700ustar00rootroot00000000000000aac0fdf50112fb896616ce4dffe1e427dce076f7d13cfa6b6f62ef97c26eee56c3ff4e8e2e088ecf3dee263d841c294651dc65514159b3f4cab0cd6e53f1e0de graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.9.8.txt000066400000000000000000000002011414121453000237710ustar00rootroot000000000000004da4d21bec267d782224732143535a3d9bd210c1b5ed9df56f9b21ffc9415ac04687b2ff01c4c6d8dd0c82613ce323db08e96b727e95a62855c47112c49d07ab graphql-ruby-1.11.10/guides/pro/checksums/graphql-pro-1.9.9.txt000066400000000000000000000002011414121453000237720ustar00rootroot00000000000000055f07c55bf22cda1ac6ce58b8acb74812ae150c3235ba7769b47933f827b4956fe9c036a40d48a4051554d161d9274c21dac57694daff1a0a8ee2558b838f2f graphql-ruby-1.11.10/guides/pro/cursors.md000066400000000000000000000114241414121453000203670ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro title: Stable Cursors for ActiveRecord desc: Value-based cursors for stable pagination over ActiveRecord::Relations index: 5 pro: true --- __Note:__ See the new {% internal_link "stable relation connection", "/pagination/stable_relation_connections" %} guide for a more robust and flexible implementation of this feature. ----- `GraphQL::Pro` includes a mechanism for serving _stable_ cursors for `ActiveRecord::Relation`s based on column values. If objects are created or destroyed during pagination, the list of items won't be disrupted. A new `RelationConnection` is applied by default. It is backwards-compatible with existing offset-based cursors. See ["Opting Out"](#opting-out) below if you wish to continue using offset-based pagination. To enforce the opacity of your cursors, consider an {% internal_link "encrypted encoder","/pro/encoders" %}. ## What's the difference? The default `RelationConnection` (which turns an `ActiveRecord::Relation` into a Relay-compatible connection) uses _offset_ as a cursor. This naive approach is sufficient for many cases, but it's subject to a specific set of bugs. Let's say you're looking at the second page of 10 items (`LIMIT 10 OFFSET 10`). During that time, one of the items on page 1 is deleted. When you navigate to page 3 (`LIMIT 10 OFFSET 20`), you'll actually _miss_ one item. The entire list shifted "up" one position when a previous item was deleted. To solve this bug, we should use a _value_ to page through items (instead of _offset_). For example, if items are ordered by `id`, use the `id` for pagination: ```sql LIMIT 10 -- page 1 WHERE id > :last_id LIMIT 10 -- page 2 ``` This way, even when items are added or removed, pagination will continue without interruption. For more information about this issue, see ["Pagination: You're (Probably) Doing It Wrong"](https://coderwall.com/p/lkcaag/pagination-you-re-probably-doing-it-wrong). ## Implementation Notes Keep these points in mind when using value-based cursors: - For a given `ActiveRecord::Relation`, only columns of that specific model can be used in pagination. (This is because column names are turned into `WHERE` conditions.) - `RelationConnection` may add an additional `primary_key` ordering to ensure that the cursor value is unique. This behavior is inspired by `Relation#reverse_order` which also assumes that `primary_key` is the default sort. ## Grouped Relations When using a grouped `ActiveRecord::Relation`, include a unique ID in your sort to ensure that each row in the result has a unique cursor. For example: ```ruby # Bad: If two results have the same `max(price)`, # they will be identical from a pagination perspective: Products.select("max(price) as price").group("category_id").order("price") # Good: `category_id` is used to disambiguate any results with the same price: Products.select("max(price) as price").group("category_id").order("price, category_id") ``` For ungrouped relations, this issue is handled automatically by adding the model's `primary_key` to the order values. If you provide an unordered, grouped relation, `GraphQL::Pro::RelationConnection::InvalidRelationError` will be raised because an unordered relation _cannot_ be paginated in a stable way. ## Backwards Compatibility `GraphQL::Pro`'s `RelationConnection` is backwards-compatible. If it receives an offset-based cursor, it uses that cursor for the next resolution, then returns value-based cursors in the next result. If you're also switching to {% internal_link "encrypted cursors","/pro/encoders" %}, you'll need a {% internal_link "versioned encoder","/pro/encoders#versioning" %}, too. This way, _both_ unencrypted _and_ encrypted cursors will be accepted! For example: ```ruby # Define an encrypted encoder for use with cursors: EncryptedCursorEncoder = MyEncoder = GraphQL::Pro::Encoder.define do key("f411f30...") end # Make a versioned encoder combining new & old VersionedCursorEncoder = GraphQL::Pro::Encoder.versioned( # New encrypted encoder: EncryptedCursorEncoder # Old plaintext encoder (this is the default): GraphQL::Schema::Base64Encoder ) MySchema = GraphQL::Schema.define do # Apply the versioned encoder: cursor_encoder(VersionedCursorEncoder) end ``` Now, _both_ unencrypted and encrypted cursors will be accepted. ## Opting Out If you don't want `GraphQL::Pro`'s new cursor behavior, re-register the offset-based `RelationConnection`: ```ruby MySchema = GraphQL::Schema.define { ... } # Always use the offset-based connection, override `GraphQL::Pro::RelationConnection` GraphQL::Relay::BaseConnection.register_connection_implementation( ActiveRecord::Relation, GraphQL::Relay::RelationConnection ) ``` ## ActiveRecord Versions `GraphQL::Pro::RelationConnection` supports ActiveRecord `>= 4.1.0`. graphql-ruby-1.11.10/guides/pro/dashboard.md000066400000000000000000000036471414121453000206260ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro title: Dashboard desc: Installing GraphQL-Pro's Dashboard index: 4 pro: true --- [GraphQL-Pro](https://graphql.pro) includes a web dashboard for monitoring {% internal_link "Operation Store", "/operation_store/overview" %} and {% internal_link "subscriptions", "/subscriptions/pusher_implementation" %}. ## Installation To hook up the Dashboard, add it to `routes.rb` ```ruby # config/routes.rb # Include GraphQL::Pro's routing extensions: using GraphQL::Pro::Routes Rails.application.routes.draw do # ... # Add the GraphQL::Pro Dashboard # TODO: authorize, see below mount MySchema.dashboard, at: "/graphql/dashboard" end ``` With this configuration, it will be available at `/graphql/dashboard`. The dashboard is a Rack app, so you can mount it in Sinatra or any other Rack app. ## Authorizing the Dashboard You should only allow admin users to see `/graphql/dashboard` because it allows viewers to delete stored operations. ### Rails Routing Constraints Use [Rails routing constraints](https://api.rubyonrails.org/v5.1/classes/ActionDispatch/Routing/Mapper/Scoping.html#method-i-constraints) to restrict access to authorized users, for example: ```ruby # Check the secure session for a staff flag: STAFF_ONLY = ->(request) { request.session["staff"] == true } # Only serve the GraphQL Dashboard to staff users: constraints(STAFF_ONLY) do mount MySchema.dashboard, at: "/graphql/dashboard" end ``` ### Rack Basic Authentication Insert the `Rack::Auth::Basic` middleware, before the web view. This prompts for a username and password when visiting the dashboard. ```ruby graphql_dashboard = Rack::Builder.new do use(Rack::Auth::Basic) do |username, password| username == ENV.fetch("GRAPHQL_USERNAME") && password == ENV.fetch("GRAPHQL_PASSWORD") end run MySchema.dashboard end mount graphql_dashboard, at: "/graphql/dashboard" ``` graphql-ruby-1.11.10/guides/pro/encoders.md000066400000000000000000000103011414121453000204620ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro title: Encrypted, Versioned Cursors and IDs desc: Increased opacity and configurability for Relay identifiers index: 6 pro: true --- `GraphQL::Pro` includes a mechanism for serving encrypted, versioned cursors and IDs. This provides some benefits: - Users can't reverse-engineer node IDs or connection cursors, removing a possible attack vector. - You can gradually transition between cursor strategies, adding encrypting while supporting any "stale" encoders which clients already have. `GraphQL::Pro`'s encrypted encoders provide a few security features: - Key-based encryption by `aes-128-gcm` by default - Authentication - Nonces for cursors (but not IDs, that would be silly) ## Defining an Encoder Encoders can be created with `Encoder.define { ... }`: ```ruby MyEncoder = GraphQL::Pro::Encoder.define do key("f411f30...") # optional: tag("81ce51c307") end ``` - `key` is the encryption key for this encoder. You can generate one with: `require "securerandom"; SecureRandom.bytes(16)` - `tag`, if provided, is used as authentication data or for disambiguating versioned encoders ## Encrypting Cursors Encrypt cursors by attaching an encrypted encoder to `Schema#cursor_encoder`: ```ruby MySchema = GraphQL::Schema.define do cursor_encoder(MyCursorEncoder) end ``` Now, built-in connection implementations will use that encoder for cursors. If you implement your own connections, you can access the encoder's encryption methods via {{ "GraphQL::Relay::BaseConnection#encode" | api_doc }} and {{ "GraphQL::Relay::BaseConnection#decode" | api_doc }}. ## Encrypting IDs Encrypt IDs by using encoders in `Schema#id_from_object` and `Schema#object_from_id`: ```ruby MySchema = GraphQL::Schema.define do id_from_object ->(object, type, ctx) { id_data = "#{object.class.name}/#{object.id}" MyIDEncoder.encode(id_data) } object_from_id ->(id, ctx) { id_data = MyIDEncoder.decode(id) class_name, id = id_data.split("/") class_name.constantize.find(id) } end ``` Note that IDs are _not_ encrypted with nonces. This means that if someone can _guess_ how IDs are constructed, they can determine the encryption key (a kind of [known-plaintext attack](https://en.wikipedia.org/wiki/Known-plaintext_attack)). To reduce this risk, make your plaintext IDs unpredictable, for example, by appending a salt or obfuscating their content. ## Versioning You can combine several encoders into a single chain of versioned encoders. Pass them to `.versioned`, newest-to-oldest: ```ruby # Define some encoders ... NewSecureEncoder = GraphQL::Pro::Encoder.define { ... } OldSecureEncoder = GraphQL::Pro::Encoder.define { ... } LegacyInsecureEncoder = GraphQL::Pro::Encoder.define { ... } # Then order them by priority: VersionedEncoder = GraphQL::Pro::Encoder.versioned( # Newest: NewSecureEncoder, OldSecureEncoder, # Oldest: LegacyInsecureEncoder ) ``` When receiving an ID or cursor, a versioned encoders tries each encoder in sequence. When creating a new ID or cursor, the encoder always uses the first encoder. This way, clients will receiving _new_ encoders, but the server will still accept _old_ encoders (until the old one is removed from the list). `VersionedEncoder#decode_versioned` returns two values: the decoded data _and_ the encoder which successfully decoded it. You can use this to determine how to process decoded data. For example, you can switch on the encoder: ```ruby data, encoder = VersionedEncoder.decode_versioned(id) case encoder when UUIDEncoder find_by_uuid(data) when SQLPrimaryKeyEncoder find_by_pk(data) when nil # `id` could not be decoded nil end ``` ## Encoding By default, encrypted bytes is stringified as base-64. You can specific a custom encoder with the `Encoder#encoder` definition. For example, you could define an encode which uses URL-safe base-64 functions: ```ruby module URLSafeEncoder def self.encode(str) Base64.urlsafe_encode64(str) end def self.decode(str) Base64.urlsafe_decode64(str) end end ``` Then attach it to your encoder: ```ruby MyURLSafeEncoder = GraphQL::Pro::Encoder.define do encoder URLSafeEncoder end ``` Now, these node IDs and cursors will be URL-safe! graphql-ruby-1.11.10/guides/pro/home.md000066400000000000000000000002611414121453000176140ustar00rootroot00000000000000--- layout: guide doc_stub: false outbound_url: https://graphql.pro title: GraphQL::Pro Home section: GraphQL Pro desc: Overview of GraphQL::Pro features index: 0 pro: true --- graphql-ruby-1.11.10/guides/pro/installation.md000066400000000000000000000044111414121453000213660ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: GraphQL Pro title: Installation desc: Get started with GraphQL::Pro index: 1 pro: true --- `GraphQL::Pro` is distributed as a Ruby gem. When you buy `GraphQL::Pro`, you'll receive credentials, which you can register with bundler: ```sh bundle config gems.graphql.pro #{YOUR_CREDENTIALS} ``` Then, you can add `graphql-pro` to your Gemfile, which a custom `source`: ```ruby source "https://gems.graphql.pro" do gem "graphql-pro" end ``` Then, install the gem with Bundler: ```sh bundle install ``` Then, check out some of `GraphQL::Pro`'s features! ## Updates To update `GraphQL::Pro`, use Bundler: ```sh bundle update graphql-pro ``` Be sure to check the [changelog](https://github.com/rmosolgo/graphql-ruby/blob/master/CHANGELOG-pro.md) between versions! ## Dependencies `graphql-pro 1.0.0` requires `graphql ~>1.4`. The latest version requires `graphql =>1.7.6`. ## Verifying Integrity You can verify the integrity of `graphql-pro` by getting its checksum and comparing it to the [published checksums](https://github.com/rmosolgo/graphql-ruby/blob/master/guides/pro/checksums). Include the `graphql:pro:validate` task in your `Rakefile`: ```ruby # Rakefile require "graphql/rake_task/validate" ``` Then invoke it with a version: ``` $ bundle exec rake graphql:pro:validate[1.0.0] Validating graphql-pro v1.0.0 - Checking for graphql-pro credentials... ✓ found - Fetching the gem... ✓ fetched - Validating digest... ✓ validated from GitHub ✓ validated from graphql-ruby.org ✔ graphql-pro 1.0.0 validated successfully! ``` In case of a failure, please {% open_an_issue "GraphQL Pro installation failure" %}: ``` Validating graphql-pro v1.4.800 - Checking for graphql-pro credentials... ✓ found - Fetching the gem... ✓ fetched - Validating digest... ✘ SHA mismatch: Downloaded: c9cab2619aa6540605ce7922784fc84dbba3623383fdce6b17fde01d8da0aff49d666810c97f66310013c030e3ab7712094ee2d8f1ea9ce79aaf65c1684d992a GitHub: 404: Not Found graphql-ruby.org: 404: Not Found This download of graphql-pro is invalid, please open an issue: https://github.com/rmosolgo/graphql-ruby/issues/new?title=graphql-pro%20digest%20mismatch%20(1.4.800) ``` graphql-ruby-1.11.10/guides/queries/000077500000000000000000000000001414121453000172205ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/queries/appoptics_example.png000066400000000000000000007616441414121453000234650ustar00rootroot00000000000000PNG  IHDR B\DiCCPICC Profile(c``I,(aa``+) rwRR` L J>@% 0|/̲Jy[oZtQWJjq2iE% )@ryIdd! v}&$Ⱦd $g$`|d$!#Bi JR+J@s~AeQfzF#0R 2332 1602 iDOT!(!!Ov@IDATx\e?77BB'J((U,(EbEEQQ^^UWQbAb(*"D@J %iB; {d7as;3=}7gfxB @ @ @ @&5 @ @ @ @' @ @ @ @M 5M  @ @ @ @G @ @ @ @4- p4 @ @ @ @y @ @ @ @ дQT  @ @ @ @ p9@ @ @ @ @@GMS)H @ @ @ @ @ @ @ @M 5M  @ @ @ @GK,F @ @ @ @@TbR @ @ @ @@6xhʔ)T; @ @ @ @- p]w8jVE @ @ @ @=8ꩠ  @ @ @ @Q 5 @ @ @ @詀QOmO @ @ @ @ZU @ @ @ @@Oz*h{ @ @ @ @-$ pB @ @ @ @z* pSA @ @ @ @h!jlU%@ @ @ @ S ڞ @ @ @ @@ Pc* @ @ @ @ T @ @ @ @ZH@ਅ[U  @ @ @ @T@ਧ'@ @ @ @ BG-تJ @ @ @ @G== @ @ @ @8jVU @ @ @ @=8ꩠ  @ @ @ @Q 5 @ @ @ @詀QOmO @ @ @ @ZU @ @ @ @@Oz*h{ @ @ @ @-$ pB @ @ @ @z* pSA @ @ @ @h!jlU%@ @ @ @ S ڞ @ @ @ @@ Pc* @ @ @ @ T @ @ @ @ZH@ਅ[U  @ @ @ @T@ਧ'@ @ @ @ BG-تJ @ @ @ @G== @ @ @ @8jVU @ @ @ @=8ꩠ  @ @ @ @Q 5 @ @ @ @詀QOmO@XbE˻<}7m]-0}tm1cƤ>D{7͝;7mxF?~| @ gN?az+w_y4hРۦ:( :w]) {f;#M6=:r! }_`ҥ馛nJf_1"m6iOq{enH<@z'ӎ;g1[}@uAiWgJoztt]Ш66#j/㏧O}S]mo{[{サ\oWK]tQ >T&^<\r%sua 'kC@h/yeN;N?r+Oƍ׾7?UF(a7G8[G}t2dȺO@o F4 g̘6}Cp]NZ T [o5N觍1r_Ƈ&@O2%=YG)&kO|)>t9rdz^W^a~{ A@.$ XoσŎ .(d&Q@ਣ{PNou7QWUQÈxSqe)n麫:܏<,X>:t 7 >t=oƷ"xU[V^~z+_?F77#LGuTdɒ|Ά^?~>UK|Seٲe)c7/-@վ S~㏯|d6 Q?yhԨQ~ӟ;hG. P;}駟"׿>J*Ho\,\0; OtKS^o=#g?+KVyR|.txНuO%l4>s2lx"Ko1-oI{nDQ|q!DE>{\ ćTw}w n-{\fΜbcS:ofƇo}~?~ċ?y2}r>bO'On/DE/zQ:c=[+ s[mUr]/IU8?'~y]l_#q5Fd/| ݝu@~|%01JM7ݔ~bn<c:~QM9f;tקN8!7I^gqmӵ玗]vYYA\gq qM_k׼&̱8w Q_mE@ \uUg?Y&aY .30ʑ*z?sM$.ą^7a7-! 1 #Ho7bTK/aFBŗ%M6-}_կ]w]QbԲG}{L}|]܏>x2޸*C?@SN9E(W@ (?7ߜ=oo z.&c\. -l\8G:>y uϰ i1 Z"GWŨ+Ç_ aw׽u?_^w8J>.!/: " es:iM T1u3=}SA#U. !Ɨc)>+d V}XG_!' rz\w]AI@Ǜt"~K_&M~"p\AJ$e}Io|7ド#ľ#DaX8tw72Q~رJ? ptw3<3[e֬Y7b x,EML?.F<)o>#%ooy7M~vy)zE iԩ5B|(N >D8~7S?Xt]әX_QSJ⽀x8Yo]_xo6̧zM1ĉ׷#@``1d_$H5}|1I,կW\LwuC15k|I1X;@3}~єNz ĵ_lq8z]Pb$Q=U/7TAu~|.pn!!F^^#wp# mDl"@o(?q7 CĘ"Xַg>qOV@\ E"9?$:蠃R.R)h%c GA kz科#G)6Q ?OE8(2h|1^l6I1Q|}D eɒ%T-1\/9vC|s"Q,l88׌:/cp1>h%>?[/Y ob_v|[9NuAmR'N` j=Gk0>(ҿcu]LjqqQG#85Eql "lX-<ŊCk60;l\Gy(1F&E_(6!L_ຠ$sN;oYuA8 s_`%?Ƒbݿtg|t"9}\@ਏ7#@w !F"cڢ@L"hӨźѩvi~CGPqB|@7 }k_˓q1o^/GVjm6@w!>)Qb:ƥ?1%ZbMӬۜ{Tj{z;Q<ۓ1?#E2u-/| ۿM^/A(e˖=3fS@7;o+q]N tDKKPcy+^_޼.(x55^'D B@ {x?dӜt&zqxۋU^z?x@_Ш6 'L_Gw] <7tSĽ+xYuA8u]ӻ}Ǎt/ya2[b/G,,.G}qV LibG>ϓ0!chbkҏ|z̈́b]q8uloOw}E/LE a O?t)ъQM"\a%o 5*}pw^bĢ%Bѿ4b}n"t3Z]TL8;1]2--¸sd]tp@m*^L_(*F<낕+W桦]9BNķ=|::_;LgqF1޻ӯS!/: t#з.\>糡ĿŔq1Aސvm|Ђ?񏕮 cJN8Fx$l)bi|1p5B!w_8-1aK̓.t(>."}!cY71?DܹsۃC͂٤Xn~èf$BDb3M}Db^ @w ;B1I|S9PĬ#7N:x0._wq]'L_]=?b6OS=GL6a„N:z P|Q(F'7||]_r%OSjX_(6+n_]M\tc-PYO?p> AEhA xUG9zPHZ86@ 8`AW (S8\r<Kwm%~F CoZذ݅ b4߿dۊaO"8*y0qSN)VM@/tt[mUG?t'f'ƨ˗/td]~@}Aw|4 y]_sL~ݝu@\HA^1I?S}Hƍk{_:ꨣґG?/hq@/2 ӱ@[:;ٳg_lJbuAwo8W Q_i A{쑶f~F1cF?~|Ї>Q 9q|r^{m ia@YȠ.)Do 1"=#_zZtiўg'[o F?aSL<, pԇǩ ;1zI97rH %K;"`v{3p}--4M1'k߬>Cq;.d|̧~z3gNBfΜ!,7!{ "8z81#ߦoMzO ^ӟ΃1amPC,/!CB w4N⺠;)_*}A| 0p=Aq˺"uwܑ<#0AO=C"!Μ'}, BB1bQ](Neqmٲeb!f!? G:K)UuAoF͖ % pd>#6]}'h?x.>X8cRv8oG]uU8asҗ4 @` t2(X<oG׿(ebptg8#Hp@qj~ K1Ot-0X<0ËE_PHM D0B-(纠;-O*}Av+O:5#Dc$cǦ;,tAF+YfI"dԸL4)"~[W\b|0ewL(~F!.(Ra L}A@uAnKgG`Ct8c5{][#6D go  g/^x†^"TߒoJŇ^O%#1+VR~B'P oR_#NuAw:@s#lTtk "%1 :k~]Lg1Yec6Ƣ/ʎAmf-4 .hpZ @ @ @ @*UR @ @ @ @@ 3@  @ @ @ @T8( @ @ @ @V8jg @ @ @ @ pTKQ @ @ @ @. p3}{_׿di@ غDy}AVJέn4o$: ܺFy}AVJέn4od8jS2hK15pqPӆU-'PS}AMVTTS@M5mX"PQ@_PLq5ԴaU@E}AE0QMQX6j(/8 jڰE"j*/iê` Z* *)olR@M\ԴaU@E}AE0 T@_PӆU-'PS}AMVTTS@M5mX"PQ@_PL~+ pdE p$bj*⠦ Z* *)N6j(/8 jڰE"j*/iê`[&.:&#PS5mX"PQ@_PLq5ԴaU@E}AE0 T@_PӆU-'PS}AMVTTS 5t)5 8iê` Z* *)N6j(/8 jڰE"V@ɦNAI,TAMVTTS@M5mX"PQ@_PLq5ԴaU@E}AE0 T@_PӆU-GM6]t GMb)F.jڰE"j*/iê` Z* *)N6j(/x8jS8jK15pqPӆU-'PS}AMVTTS@M5mX"PQ@_PLq5ԴaU@E}AE0QMQX6j(/8 jڰE"j*/iê` Z* *)olR@M\ԴaU@E}AE0 T@_PӆU-'PS}AMVTTS@M5mX"PQ@_PL~+ pdE p$bj*⠦ Z* *)N6j(/8 jڰE"j*/iê`[&.:&#PS5mX"PQ@_PLq5ԴaU@E}AE0 T@_PӆU-'PS}AMVTTS 5t)5 8iê` Z* *)N6j(/8 jڰE"V@ɦNAI,TAMVTTS@M5mX"PQ@_PLq5ԴaU@E}AE0 T@_PӆU-GM6]t GMb)F.jڰE"j*/iê` Z* *)N6j(/x8jS8jK15pqPӆU-'PS}AMVTTS@M5mX"PQ@_PLq5ԴaU@E}AE0QMQX6j(/8 jڰE"j*/iê` Z* *)olR@M\ԴaU@E}AE0 T@_PӆU-'PS}AMVTTS@M5mX"PQ@_PL~+ pdE p$bj*⠦ Z* *)N6j(/8 jڰE"j*/iê`[&.:&#PS5mX"PQ@_PLq5ԴaU@E}AE0 T@_PӆU-'PS}AMVTTS 5t)5 8iê` Z* *)N6j(/8 jڰE"V@ɦNAI,TAMVTTS@M5mX"PQ@_PLq5ԴaU@E}AE0 T@_PӆU-GM6]t GMb)F.jڰE"j*/iê` Z* *)N6j(/x8jS8jK15pqPӆU-'PS}AMVTTS@M5mX"PQ@_PLq5ԴaU@E}AE0QMQX6j(/8 jڰE"j*/iê` Z* *)olR@M\ԴaU@E}AE0 T@_PӆU-'PS}AMVTTS@M5mX"PQ@_PL~+ pdE p$bj*⠦ Z* *)N6j(/8 jڰE"j*/iê`[&.:&#PS5mX"PQ@_PLq5ԴaU@E}AE0 T@_PӆU-'PS}AMVTTS 5t)5 8iê` Z* *)N6j(/8 jڰE"V@ɦNAI,TAMVTTS@M5mX"PQ@_PLq5ԴaU@E}AE0 T@_PӆU-GM6]t GMb)F.jڰE"j*/iê` Z* *)N6j(/x8jS8jK15pqPӆU-'PS}AMVTTS@M5mX"PQ@_PLq5ԴaU@E}AE0Qm:'N @ @ @ @` m|sG$@ @ @ @ olMT @ @ @ @@|^5HGMB)F @ @ @ ЭQ<)-Y$/1eʔ}{QngG @ @ @/G%-%pTd5 @ @ @ @@K 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs YM @ @ @ RG%-pTd5 @ @ @ @@K 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs YM @ @ @ RG%-pTd5 @ @ @ @@K 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs YM @ @ @ RG%-pTd5 @ @ @ @@K 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs YM @ @ @ RG%-pTd5 @ @ @ @@K 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs YM @ @ @ RG%-pTgWH{}v~v ?_xw=Ӿzbm&%YO>NuѻFǻfWoc; @ @ @ нQ>I0`=Nn{E3amھҎvKz͎'t͜?f~'3c~Ѵzv@[\$\28$@ @ @ @M P .p4 vǷ #wG9oQQ ^cKGnjt}f?p*G @ @ @6Q Q P]Gt6G絾x9wQQ &و\'4fȸ4wAW4r @ @ @ @` p "p4x!靓?O{u}ꣵ8ffN/\nwm*G @ @ @6Q Q КՃ6 ʲ2-_}Mo2}rfZbAqc@ߨ(30v҂gJP;m>tS=N7tѴ! M1L,qq-;8xc"x龅wuZ|ĠiqY0ixZba^iu{٨߲UK9,_È6[ ۮ= 00u_zyzbi'x#LjɕKW![dvs\xճ|ACΏ9ټR(,mf1?= e]Ozg?b|WohZs"/ŏ<6$sѣqNl<{~6Br'ِZϛM @ @ @N}5t/J./dY)*ne2G/8CJw`mg4.+͏u>#.>#8Fe鲇.f_m> ZP~E线ߺV]n ۼ4m?bҺv'9?/ 0 [Lk{~i{t{:yoE14tݟCF1N%xq+g^%1E0 -<59z7FL6odG.INMcθ 9bp7pρ5h'6i;~AV-7TR<#u/qP܎T,~o74-mqźl7rtѴ>= @ @ @<G%- pTfu}~1;bTb禩Y#GbU>RMu(,8t71qߖFux3ɇӏF~QMxS>QLKVv>O(S%6sE*=.M;apy ,Ft^C鲊`Mmf,/+:[w~}$f#+уƤk,+Q~t7Ҽs14t[ۘѤFYP\"U,FM59?n;,FPzw*PRwu>~ߴ{l4FE+?7{ĺb)qǮB^YU{ǖͶfhUhO#P>7ӵsL @ @ @ @/ Q КՍA%9 kX|>UË7L{6 dnN-+lDPg1JGn|1wbn;G[ӵşa: "[1=Y%G;9r[f# -\tdm3z^?3`5u)0Ec;&G6׈<{H@E(?Qv'тbڮA 0OeՑǯO1JRL᫃<,=+1*=lbi cq^1Tmb/49 Er?d`%f#%mwP+q)ww~M{lO~7F^QFӨ)E 8ѠƏn\c^>eeW/rv}?knuJax1Ӹ_=x~6׭lnc+4qfSVȧ(3'gvܨ{Eh(F"FZwyהJ#Nsι_Ӿ?7~qGM#aGy䏤hMg}Z{l_>x^A[8=w󻿞tۍNB[e!uƑ"Xͻѿ?պg+ 7vh,;ߘ~='\c6 n_W܈ZwɶžK[0;?zU @ @ @V8*Z1hUale-FfS 08}hPV|@~%=ln~;3ibi'pl-btƅTe7wUjҤQ)֝?@IDAT-iپ;xNcGёVeKcl 'i޺ZbD8cEratt[(I)PWݳ^M:wڹWd!m}6Ggo~H~?bg߾_Ӓw~4pm 8vO3dl=h4d6(`~XEЪX˿|4ۊUcJ߿ogSModby)FԊSaT @ @ @ @8*8*Z1h#Ĉ8-pK2+.2:lĠMzG~ĤƩ:8)hZ"?5.^I(=F9H/{}W'O|ģG]R#3{,o7t7*p8\nwmf*ЋGCK'e#KzaYM^/Fb.pSŔk./\6Bsϻ[s94Tݶ8[3|*+M @ @ @QI+Y.pId#otQ΂5kG8z/Il|FYs*-"rtNlSej"`2kPYP%i{ҴES_6/=dz6΢r7 Ŵ`u=KvLGݛ.GiIhѻcw8!5i毟r܃|qzVvhQ&oH=tQF?zwu8lTޝ>!߾qĭƿ;~tj1Z,_iŪm? @ @ @ @@_8*i 51?c̷L6ڒ4:Gj 3ËHFMNh nqNEzΧ ɧ";oǖ]U׿"Xθ|ұ8X(e:Lq4O>~t7:jѻWfSG1wR6cy1(?>G)BM$pm|CRV,Hg<81 mqCvhIGnw\i`뗦x~~Q<7?k1[OVl 5`y,iv^F  \7K0rXs6مѓQll=by`tKi6y>Ѯ8jl<]/m=l,4j*/+ƿxʶ>N=G{ܞ&@ @ @ @}F@ਤ)J֬n Zt8yT[onիo"ӎݒ7EMaCzķGoxqtG˶m}>w1"{wT$yW硝B@˶}mލw#yex3(|6Ggo~Hv˳i%3ҼpOAn =辴} u3;Ը5su7p^P6TD('t,#Mt|Uog5ؖZ5$;ހ Cn1ZQw+:[^<=dڙFnj;m#F|z.tt @ @ @ JZDhƠEw(>* 8Dn( Gxv5}xMճ)MmnljΖG_>x^Ŀ:+%p{3˵sMyvc3E;-ӓnSF1ԍO9y @ @ @F8*j;8=t):jncIN7ͻ&h䡤+F{/?sɌ|嫖m:^=m:2*֮\W74[mc=>'-^ b9iŪ]  @ @ @ @. pTG%@VGxrb6hN/~=tl)׻8]kO%@ @ @ @}G@ਤ-Jy(\ɰ4xie(HKҒ,`4sɃ56֘!cɏ;cK$8'GMss}xȝ#&i/ɶMDA @ @ @ 4 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs YM @ @ @ RG%-pTd5 @ @ @ @@K 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs YM @ @ @ RG%-pTd5 @ @ @ @@K 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs YM @ @ @ RG%-pTd5 @ @ @ @@K 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs YM @ @ @ RG%-pTd5 @ @ @ @@K 4Q  @ @ @ @-% pTG%@V @ @ @ @QIs%p4ZM @ @ @ @\`̘1p+?Ç7uxM\SHਊ @ @ @ @u8*ia  @ @ @ @ZJ@ਤJ&@ @ @ @h)8* O=4x56 @ @ @Ғ޳^;8*a8* O~qvOҀi 6 @ @ @`VځQ Q  @~* pOi @ @ @" pÛnBGU%@VΔ @ @}#gK(@h%Vjmu%@ @ @88Z9GO! G-P} @ @ GGGD  @-& pb  @ @ @@#Ou1 @Z՝ @ @88*+8*%Rh1kp%@ @ @: uxBtvG3 @, pʭ @ @ @Q_Q) @@ X. @ @ A@H;GxheVn}u'@ @ @J J @ZL@\u  @ @ @GG8Lc @@+ r; @ @ p$pTW pTJ bG-K @ @t88Qg*#@ZY@ਕ[_  @ @ @#ҿR" @8jW] @ @ @Q'Dgw:S GN @ @ )@Q5 @ @ @:dDJbiJ+iࠔ%͛umc ڶ @ @, p$pT8*%Rh1 8:m~=tsJ@J:Yf6?k4hvHilɇfWc:¢GSܔuil{~J[ѐ)=v;"E5.nvV^s=)mgJ_ҟҋ3]^D)]V*rzUܲ @ @ @J2GD  @-&QGKҏtWR<|o>ҽW^ٵ"dX.bJw^^FZQBtWSdAhئkGqNqn.s"44ᶑY<+S{mmQG @ @ @@#ҧQ) @@ lQ.])m GnOi5Ylmz}hFROL7)ZRzw#/]5SeǏbHikSzk==ь:#{6ej3?}osjV(, @ @ @`J8GD  @-&G1%%NiƍՅlm㌗ɶ}OhgtmߑM/U?*[tu?ϦNA=vxJ"]8Z @ @( p$pT8*%Rh18)]M>Km[脔?v{}G1U[/HiԖY~) 0˔nI6ڜ9m:ز14tSz88/ifu/m   @ @ @@e#'Q) @@ lQ$# uls[h ):c͐#(Kn"|z~EJ/xw Vt)ҰŽ!{j ul6܂/n霔6MA>m]obOMk_6uٲmFX&]pR6"Ѵ,h }%YhE8OϾ;oJܞY3IiZGm{KiR縔&gJdnj%Dl?gASZs}7~,`57p^Jn'S:8Z @ @( p$pT8*%Rh1>8z7h?)=1c+Km#ţ8*䔎l4kRZ8zEO t}@VtCSȔCJ3iۖoAhS:ijE6}1)\V{PLϟtmu)ޞs 'rTIG @ @ @QFਔH @D(5GfmACA4o[·O=%)m2-S?B HETTP@,RĎm/ްkY]]]{D&`HGz/JI̽fRͽy9S?g.ɳ{DfncѾ>lA@@@@@y*<@*@JjYHJCJټQIQLUi7Tܝ"[׋h𧴭4 |:JԦmci@@@@@@y*<@*@T*}‘>7U*     Q"@Hȓ@@ 8wW8"pT@@@@@*#G?#O"v@@J&@Qsi@@@@@ 8,8$b@d*p U >F@@@@@@##O"v@@J&@m"GW]7å@@@@@y<@*J6<.      8"pBB0! @e pTGgG@@@@@G:o/7Gc$ yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [GZ @bDQ   ":< yэ   pۡ@@ F@q  !@(@ȃ   [G2=XHRZNٽL+iJAM#PCUHӎgo̒?.=Uy-e\OUƻpJ82(Q(W$N GGT<yэ   pۡ-Ӄ΍ޱ=g(cUɺI؛.J'PjU9{s 3TdP~_ ;/@ *c(o;I{윿^ֿ21)9<@@ pq!pMn@@#mQ88GϗwIJIT" SȪ;I^fvne@ pR<| yэ   pۡ-Ӄ8*_TܬwG9ѿ˼wɟ{ $HoYV?j=ptuLh^_.I@ *8$ %qT8j'sJƻfm~y@@ 88&pD7  VoLFL|Qq_&MN8L3Vf8<*)Zn+s=ΐKeG춽+b7…@ ~ :@I=,(kqO.  sG`G 2ǻ߇FP7Yv]Mcy*7@hKnRךgh0WOݾkB }NSTGNx&Ӿl\&^vO[|yrΖ󥴟9]?C4-;GG[f(q=~>/ 1HW_%ov3پ?k ?ԒI P%:lqB4*I.o56^ěݵnm 77h_3FK(ZjUƵ%oK*s ~qR܋omB~h_=/h[ϡohj$ʮ[$뾿Whm> _V7f<v:6_~@@橒|~xGCDn@@*Dj^X7I;[5ilvU5`|q5P T(]IJ8LqSA?]v4UY[ g,s=M$& s-#fK({:@Hȧjr (;6 W 'e'[F瘤Hz+nRJ4su[;s=d5? }`O{Sd/?sC7~r}igdwNx>GjR#%FA/M nP?__=?sZ _d+^a&D7l#+  @ 88&pD7  VQهΓgt'q3%C I=i" ]|Z{x M+T0UxZZQHSd}eV97HZӧ5|*2&pA3'=>>Qi9a\m ih6%Mzگo?,9{ZG b% 8j޷1OUL#M+i(s%#~%;>w#KS ߠ:NGCT(RAi>wH| JӴКGGa-k IO͗͟& UH azqGZލ=%v3_%6 &*&@,oT2Ak{:Vt:a.ڊMk5`_4~Mwi돋dSrQF)&u|9V^O7(E6˄wreKNftԃɚg۩4 ;i7\u &hǶ:[eI8} :ܷ ?9Yۥ /h hO~EW}cZe%nt'컿G6*Qz,Z+xj[F}y4Z_|uVр˨!/utee6DSAZzEv0[|mYdTTPKq[/Tj%PNپʖofFlӇD#ATxjiۆmpIyڪBn_ tuGikktjKzOoƷ'U TbF)֪*HJ0l#>[ c[JVN 噰ާt wwSu  Et8 yp8@@|+@lC.I=bLiإ{ڲo bhcկNQVҦ??d}yА; 6om3L~74S,jKۋNOk 8S9w}@c.ŝR gGt:t;&K zTvzjMGpWzǻ66 Uwt_Pu:I 8ˆBtE-݃6 k[8!4 T8rNSm,]ת5 meq =WuxnRFz:hWyt3 9Yt}ɰ29]R#{rsfNm_4~bsSZi)tj8mz\|A@b]QDG7# @@@6@{4=V;ʱϷ;QX'ǂNbrLӓvd 2MeW *Qk!Wv7 _jV(薚l*]hmYFRZ5Z%I;'?Nm> dں|A/i5!mjPZʫwਬn^!&(ޠ@ r8'm4Mާr;P2PKhHCGKJ ٳVew({ JUݤMvwI Ɂ6՟Afj(7t&4z^nי Tp=MZZMiԅ2WBg. hv*5=:|vSmjUq>>?;M;6eG?s\{]w 꾍;2ߟ.;_&YZg:]1Qᨤ=64pUjEmgA9}׷2nlp;,l;T@Gw F6\W'Yt4TL˨-/LuxNem96)TQˎ1S5JBZQ(%R\TPg3$k\A5"wqzR뤶vӺgο6\E`mu ަ_]ָ4A%mjLui7Rf5fT|tV$T)Tx:Eܚtj8'T9SɌWʆ   EtL yp8@@|+@CyTtY\Y9wI3Sk5Na>iޘ;QaUw)pl]c]zF4q4GX".z!pW-AΜT0+dyw>?ߕf UTz49Z8Q{8r*`p{n/^8J+hZSMʂjJ};vBi+88JL}tT|S+1Rm.fTw:坻Gd8 cVTG|kJp[^ۧ=k+h9P#3_z]:rvॕh Pi%Qg*VJPsdF PQjmӻka^yy4sg픆8*g/\HmO$`,ٖ#GrtVwvm-#\%/ 9HO^*_ϒ[WFp6y4ѰR* (5O8ך!ɮ{t2U~>.[/A&3OGbR6S˥'n5CFIrws +ly Zl=[?vĥU?W3[SSH60t<<5Q׿4QH|-]@@":< yэ   pTMeΛGZ(k:Qa+s{'uoaW>a.p#_w8_vk'=^ʮFjD Mg=Ɯoz[VѶ?G\G_מ-+߷ ϓgvs-^S'EFzJ44/`NW{xjOu׾S~v%-O"G>7{xiGeu w^yYGj{KVrrS3 WT׉̴ 5v^E4{r)vp0b3@y$Iϴ dlx־LgOĖpXLhk즜Ŧr uj}Խ&l}}O=L֫`Z/Ok>eo/[u0I3a#N-/kkh8=GJCyTr#tߏ2-oKdgԻ6~4ՌvȖs$YT?~UB/:  k":b< yэ   pTNg)nDpʙVkwnn;ӧNNz6OPu{2i|avӔ'KOt6p'8e=R뀂)DFNUBV V*UZ.;d?+QCWuVj揲-Lީj&L)5d׶lvࣁ*F0_t|@&3{Xvn_@O 09 `mȊ8pBGiJojG9,n %pSTu~x8re\a'ݷ>#АWϞ 3*[/)lsMAh776y&'nM=Y6BXG 8Ҋ=o)g&YӰJu=춠)̖TSq֞C[+_tla+n_P".ߠ4z ˺7?]Zh5&l\YuZPmMFCA"Os;I;kzWCSs9+2{hߥJ+ ~W@jx Uii&)}Ɏ$i5nl7g#  pѱ%pMn@@mׯ]M[eImU (6΍Yl ɬ W9{wL2FrvJ:;HiZd(wNS Y|3ks^у$# niN)մ]yG->l4Ԕ-\LsU՞${9vrnG>yq`J5?Α-HRҰO-YVҖX;Y|~*FzŽ9HNxI ofensz֒uJHIl'!' ӥ.[/?7̄VJBu1ᦏme%ݩífż?3V0iy8`?>aկ&Pnj\9̘o}qsY^yYG?Tp 3eSjb4?'4ptǷNAmֿ5a%ьV \nqtǒ|dž%H/cDSW&JckQh۩6[)c/x~֓֒>_e>5lQg?)g#p0  1t\N#}(d3Ky$.%>oh(v4zhG3>uo1S%K E][m&.*5m%gpA=ȩ;f :)זԳ:Hb4̡3e˷s~#=3\,5+Vo/E=GˎG=k?9!w uMl.5:7MN;Z=ALv >tvi7pS<8^ k׮Զ!PF?@ۈ՛cT.xsY]k6'?݄${0SH [BFin6rC}ZV)iEzGjUZIݎy|G7~(dK %p=L9!š;7m& XQAݥgsZ 4M=g*g}lhH/Zs9KZZ{z4%I]o68&M@C {pkEc~Yf:)f''haB:fϯ_n}'p-]hrRe}{zuf7n:TzҰtLu>Qдy7NwҊT՜tZH[T*XzNՎx7RIܜ{^ ңӅ59cOsdڐO8w3Ux~}p|8 W3o9Mhoa%&˂M[|yuL訣$4K+*ݰM2 9SZ'$s6Ӓ}Qr.*=0I>hZ(k<:a]GzO \W+m7\O{=Z9X4Nwe6mWH?p;65^}߾_- J_ve@@w":< yэ   pTҺdYM](6pdNpjl :-K4T1 E%k!Iٺt}g=;"6 jQ_*(fv~ݦ5/Z;֛)DLjCGvc/ѪIf/0uu,t;[*}lnmUvF&SiVJnZ5 lnϭi8ft%LuР\ts_U|LU7+b6.Nd >Qy#>NӼwLlC D"pܥiՔgwBC;~az'?+['B?αZAI:pNV׫SÄe:3քbt kLyx k}# 0#R(""< yэ   pT{x 9Lme)Ӿv z)KNy7w!\pOkTz}XQh^49q/ ,QYC@@ Etyp8@@|+@Cc|3ٶb>' )ǿuh8)sjuΓS9@5JӻJJR^m{ [d-a"YL+Jp ={9+dU+Cu}"{~9o. rv"(͟2p)(;  @ p'pMn@@#- 1"@(FD@GGvʝeZ} PEΕv,4cQỳ@@߻Kk%oEf F@@ 6$KUG]8@G@t#       T*GMn@@@@@@@@J%@c yэ        Py 7# @@@@@@@@*#&pD7   SČ>}V|{V؝rwʊ2cİ#9.Ezt|hܠ  +-Ւz&;?_Vd/YlUoFz$6l؇@@@ Zy # @@@ ٹ\ߧOWc'43\v' -ݱ@e|vvԎOA :\Go!oO  "ri -}!6o}cz8A>@@<^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SG|jX  @й$  #^G@t#  > pDȧ6 P*K"  @ 8%pD7   SGbծRE_V12d/#m&:slks!@ EpO   PGz pTyGիKz5%=lݱKV*Aoz^-!N%{gn`ujH)xelخ U~Z iP7YvʓɆU#Ѯn%yrri,UoaϽ͘NnzfJ2׶y0r]l{αk{hT]V9+\F~\Կ羗is:C+sIq<ήo5mzfj%'RsY;Cw)tj*Ҩ^4l1S22~+mIv5D{d콎;^gq^wA_ݺsFqN\p9 p%D?  Ě##pD7   SG/pT.?psL (>O$a`ܮ\wNG_p|1n=Ց4S{nؐfk&3sowhhȿ#tv-pؖu<ߩ2aJyUʡyuݻe؄/~m&\48!z[`LFyL{ȃo,, 膒l&w\rw6eܵ{emr1v}rc39;hX:l GG_+6;.:J9 9f)4p4 dY!3zjp. ǵ[/=e`ByG.NUJ,^K.|@o# q >i֠|0>%yh+;:_Q^G N1_B/+M?e`\}a pT\)C@@X p1R~r9fo4rץ]lG6c`զiUkLhʽW?!g/wsbB;^w9vYsEيWOx^'9v$8t(bS#  Ĉ#"pD7   SG+p h{{LYvY'Oxt1ht=#diNAuI 2l_6S6hC`-r,]Li)Ir驇Ƿb[9;4flU~XL'Z#t~&VqwJM7ClrS&儜2Jt:cI,tZ_A5!$} \з6 =gGڦ??.;_`:®jY}Ky­](K਴+s`~_`xp}y&&@YEc@@@ y # @@@ 8\8Ft& ' hT]}NWPFzɁ,|b; &>g64p;44/}΂VѪ;چ0&}NmU%۹狻{ʱn{}6vtav:#pm'ID;aT9r=Ze7=/2zRw}iS3Oat;?S}/ZGU iL;?D"pTg wϡ.%pTw=NE}.y~u,uLvG b2 (q,  D#Q!pD7   SG+pR*Fw]E]X)6hMGiL'ۀQ h4i0VlzBϟZ]x4yʬ5r ;pL=TaCQPU[gj: GHAEC_[/eHva; ڧFRT5Ӵ"V m_nf]l˱!}RkUf['7=SP if+NAzG"pTg)~ Vw,Ҿ%\r"LSi:`5t}::0_aFlC8>   KV3b65uW^Diٸsg6$!jYmk8YfJ_;Ǯ.XI.{;;6\\U hp᫻k5p*V{Lv-]7{A񳌝\H;9Xpi%)(M)dqdGg|AAK+diER\{YШ_Ȑjo,\zwm!^^Lo~Z,Go(vfϲ|A,ʢDZ    pTGG@CbqJ4XlXU^`CRb|٢&آS$ѯY(;'O.fMPJWsLe H+~=% h# hD[!!Vz>6,>-z{pn{+a:\зW}p}6w)>9TQi}E-]Ҥ^M;mݩ7ϸJh{ Ҿ\s;9fZ?{j2MVݻ@@@@ Jy # @@@ 8\#K!Z8A/N [|dB cl{lV9vYCCMi(t?;۟e%pAO?^?#3[ιsXPIpiްVIGzlFe-6<;8sw<_{r^~o}5S1K\]n{_d䥁th[Nf-(>1V fḎMeȵmf;c0LwSin2JZ>7u4-4p4q^d|2z]v)QrY߅n9A:\Pd+t_wQi߉|.p@>Sp\}DQI@@bA(8@@KÎl\YcW-@IDAT*>&^tV> yrOP~Z 9#^8SmK[Rk -Zir_8 8X 4 @9Cc|R'%ɞ+p=;W^xLN]ub[{ڌzޯxw&ӺiN;,}K8'!-M~!9jfj>OM!O̗_`BUO7ar!u=|͋(UMiMrգ혥S TYZ-Fqt{cģ_\-o~,Y)UyN2Iϙ)tiX{zA)8Lawse/=*]4@6Uq}_9ygc˜uTNҦY/# iL[l2j0E,:<=_.\oSLpJ>Ç͑_% Lڙ) @CG}'˷dߥm;vIrghe1CP   M}`a3]jϘۆtAw5LJd3]([Eٷwre]nya|>NEh" .^KڊT:]k,OXP9] V9Q a}040Byfj> :i+3zY  :V5<㮁_wB)Ru].?YKzoJ`*@ਬ  &@cDyэ  TQ iUsrlDhEC;Ze^7TV`33MICFv4yTpOhX6fڛ*?4]Z5N6ǖtJ5N׬A-ٕ'vJ֝]L S1s+yjE+eNW!ZYrdg>,+?.A@AE A1%Ę3X`5jDcL5XAE((" Rv^{vpgv=>νsӞ;w>yb䧲e_RvfWےVz؎Wz;O{Ӆ_ t" pԉm @KG%pT @]* pԜџ?ypfykQj,tpFw|Q(EB9ɳK!p4ȡW>yK|_1*. q= @쯀QQb @tQsGq+tWCQi>M6}KOm6p^h8T}̘g**7}T-c=p}?1b3_lTmUVNQ-. @ 0*IH1 @T@9qe6ۆmb:(F'߽6 PW@ਮ  @G-$pT @]* pԜqӏjۛo[6WvCY(p45\c1pO>!Gݒ٫ 8*I!@ @`UQb @tQsGE>YfϘnߙf#Y5];COwu߼D?,udW.9]ʪ]wX =nA:Gɓ&e6+~䀆 +V68 @G&pT @]* pԼQve"@Q p4 % @ 0GG@  @ ХGG]ڵUF@@hН @a88RL @.88ҮZ 0G# @ QQb @tQvm"@8t$@ @`X*x* @ @Kk;% @* pT+pT @]* p$pԥ][ @`F)  @V ^  @R#.ڪE# p4NI @ U U)&@ @@  uiV-@wJ @U@ਂWH1 @T@HKj @S @ 0GG@  @ ХGG]ڵUF@@hН @a88RL @.88ҮZ 0G# @ QQb @tQvm"@8t$@ @`X*x* @ @Kk;% @* pT+pT @]* p$pԥ][ @`F)  @V ^  @R#.ڪE# p4NI @ U U)&@ @@  uiV-@wJ @U@ਂWH1 @T@HKj @S @ 0GG@  @ ХGG]ڵUF@@hН @a88RL @.ؾ{[Z.]jM7!͛8twUWO`޾cWsƴc9}  @ >z6eB5޵+m[$ڲoCMM @V8RL @ @ @ (8RL @ @ @ (8RL @ @ @ (8RL @zڒn|}mԓNL:{elj@٩gzwM @ @*ZUH1 @XceʲJwe2gF: [hMi\lLSai8*'RB @8hb  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@HW @@3jI @vGt2  @ @W@਻xm)>0=5M9aa~wIX}`N,ڵӤMowk:Hl<#Svmޞ6_sH\˜:јj.K @a8`8RL @z+,84؃J/$<4ϻߤ>#HJJ %k;oݧc^v4)'YO|`(mqٽ;x6Z>-2!m_>-}Eҷ @8hs  @ @W@਻Xx1iHkL>uUڱr`u 0NG4z&G- @ @G /pT @ uWWӼHƈiMI1N޺#5).Xc v8 yٚ @( pTѪG@  @ @@Qwu8:i7D|ڴiMi܄lljYO8>M{{ktWOh4QN#pT @FV8RL @zRj9hz0oZڽcWqƴ{ˎ~}dז)ڝqi)eXO&9/\9m_X8~OʷivoWdܔ iyyMo&>'K֝z_ʮ3+ȮyR︉=ii9`ոqi!3S\ﶻ֤Lja.һɠygܴ--ߥγ4r㲴?wh](+˷V޹N}-Ob4 f]df[דڽ}WQDzeI{&OVϜi׆my-;Ov3 0.lmGޟsDmڻm?.cyݾt}ZCFy*^wiiB/Fڹvsڱj׿׉Ffȸ;+ @FQEkU)&@ @MEXdǥYg? Eݲ_ R؈_ʦ{iSm殴?~*:3uLvQyX$[ +P6䶴 ?O4aG8Fߦui=noi t< Qliv,[_8xIْ8t_f=4e%칶~G/{LTj.VV%k;zfMɷXK'ڰ5->pMw{W $M;>i"S,/]Z_VEHgoa?bIJHNO|/>(櫶޲"-Z~cqT[}e!ޕ\֥];u>[קuY6qem|?~7je|BPda-7-KוYX??{QK}:C8anelo=-%#Th @ @`*Z@H1 @hrhޟ}sdS?pBO5iwo 40Ѭ75{M}j!|DlM>"RFŲ񧷥UYmX2vؐ2{clbty>}wmܖ~Ur=Q;{4'c엏ܔ# x #ĨDnDj9{FY_u߽o Ӈx=B-?> 7"E{̊u[<-xc#Oũq7\vk'ss=O,mⶁ!?;MOɖE*WŲuiٻ/wکK?0p4TMWߙ޼<}ËK|ffy-1Fѻ&<};D+€1:X#zf 6(GwI65_6bX{/}T~5+g0\[,8rX7ҋH @ @`*@H1 @hjhas7=>Wȧ)>(/Q}6쎾HQQ) +bJlʧbc޶3mtK1Wtߜ<ʃE>E # ɚ/_6y==Ӻe钴{GQi7B灛hf=9hψJ>yy{k kKkh۩1m=KonM"KLwur?>)n-Vݹ:?_ ҉Iiذu<%EQ|H"CڟeکG^pZ~߻1 8^k4p4Gcde0^qæ8p4ؤ]e#uYޣyщ(&FvGo/L߶e?߱MXl$zQ>VN(nF @$ pT-pT @ 45p4# 6ĴW1ZLbܕVǽƎeoNH&osȲEh4k#@ @8p8RL @z8ibʥX2Z@1-ҺoW ah4n?M)(-37dA9,(4nbOpFX2Rk cyYgӶO4a|곻^)Z],zO0o eK>܇TTXo}r?=5̜Om5Y6$2-O4tK[myvyZmp XvؕvowIW\bi N_V[<mS-y7Ү8q4Bn;VnLwa_[=F,BPS[&4=̝bʻqkBit1-` lֺN9rpG qM,3?v=Ӝ Bf1Z,}y`~ˎUl Yxps;5 @FQECU)&@ @M E[EhF1}ԴHMFEْ{i-}500ѷQx4'bBjgOLK]<{ȾC^4y_=0`}f~Q~p3{HGGFW}-?/Ȧ{&bU~ e#6Ni_.K[nXoUS+nKVnp`(rݣB3~iWWwڕ&17t(GN/95ťwK ԯ?F7wɠ1H֗ǵnY<[ib眐OϋeiNX5jFMS @QQb @ +Q)E6UV0lힴ HXݯ@O ^y!4@)bXq,8sJ]u|/1:SҴ{t+.+j  ( eG(FՒ~Obk(T/F~e/4׬i7VZEa L޺gB/ڽ#NX'p$sьdzY/l&|v玲]'?FEJw_.mF*F7zpܟvZ_ ?%/U}خ?fA%ٔj-E쏇)GSB[ە Q,|cGfyllTXpps*p4tD @* pTrG@  @ @@@G1 /yd>PV@fsھ|C`KO}PCٴoO=mT[h58f8QU]ןb,l*Se#3H?}hx-_"̱O3]XXo#F,z{6\ŔpE(Rbi Ŵf1ҸlNv+Y'pyIiYO1Z\cfb4-7-O+J'˔/{t+i>Zꬼ|ivjW}mfD1]\Tb{cJFFj]8Z3bi [U"FI  @ 0*CH1 @hr蠿:=M;>ieY0: $ӎ̕W~|tNM!vȫJ{PGr[ZٟO"X6^N>}o*.p4y'<:?&mKXY,U.ڹ>k˞dc(=h=(1Gc2~tߜ"if_qfr܂|=Ӵw+?ՏMc/pmPYlʴL|.3iYi[u}-i!(58ߦ5_(9L}aTj.uGSRS؅O3kJZMSvTo+yc3ε[Rڶݱ*4!F׉ѵ U(UDU}4K>]?f#%Bp4(O Xǘ.]1Y@,XxK38~/1"vwE~Үm=9Heqv n3~@_Q~׭so@ @8h  @ @WɁn )",꿯 tH&?, nٱl}ZDb곁Kt4nLjO=R@3Ϻ_Su-Y[X7pD} Ew?]6}O|D:gFf+.Hwʯd5949*rF"58YToy*<꣭G-N-qsu`#ubte@{ 㾍' 49{Ve-+P{vRG\ @FT@ਂ_H1 @hj(B) C"9PG,1RL3@L_ߝV~N c秹{x>Vs]Pb-7,M`ޑo>iϯ3)~tK~/UaQkF6Jάs"K)6_8iѻM=3'h2YK3<6=*F8za<@[>mLsElp֜g?4M>M"᧿K<# 8F:&5;/˂*[nZ~ӭK(I;=OFNPYKmats,M+2Sϼiiso]7#bE>!)צ?+bA>:p?f#B1.o~ƈNbEhiꉋmwNk]?޳Ştocd[sC>Rkhx.p4Z5 @ @`d** @ @^Giiib K6S3g!#˴ P55mϦh۽uGM~oV52# [G+1D%X1\~ A6<;) Thd)bJg7CX!-=a~敍0Od TlgNI15X#$CbĦzQacԜJ[ogOL)6ivs‚Qb4h\bd :QLklĭypmE۹n(Eh6"@ @88RL @z8tAW?6Xو;L-1rʬ'=RLv ~~)@s塏N=6YAzaykSY\#}__u߽qVu11`. @ pT*pT @ 41p4Gy;9:QB"!3GcemM˶ݓiqن5j[So H;{Վ-GҒA @}88RL @z8|wM/-m=[1֤#:&M<|N>=}etMAC^wv>]nҴc; ޻5S_bJ"nCFqqk.eZM:]H u! @** @ @^&3}LlZٜ]%u[گ*m%[ j]OOVZuLf47ִs?7[@h @_@ਢ* @ @^=s鏸ohV6r\di-i-+_.Nw:pj>Rq.GY:pgo:+Ϧ8qƴ~cԇf<SX!ml[nZN8:NH @Q' pT$G@  @ @@@G:$ pԤVW @ . p4KZ> O @ @@[<  5G]Ӕ*B @}88RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @zt4C@ @h' pN'+8RL @z6ܐ^wYm&cL:{ִ7wm @zR  @tQE U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEs U)&@ @ @ @hQEsѸq*xu ^`- @ @ @ @fͪ޽oFM7ݔ}ԩ]Ÿo֕ uj# @ @ @ @U4h u*FD؍ﬨb[&@IDAT @@N\<~4afU\m 2'0^꾇k7}ғn^1~\mެtqGU6؊ ;wcWNg޺8/޲iBy[#ǥMJi^Eݽ"J|`{i=wMLiqӪgOrݳlWZޙdƌt1ۧǞ5>Qى |d׮tsv6 #֬N,Yld%iҤ[];ˢ#֦2m[Zں>in6  @-+M"Ϳ{Y3IgN9aв7ͯt}#uGOtQz QY8*8GA @Q]37Q @ NLz4C@~; 7 @GuJJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}FਔF pwH@HਬXOfogf @ P_@ਮQi8*Q@4\@@;88*(Ge2 @GYਾ= @8k&p$pTgJi @ 8jxP:QhQv8of @  R @@T##"pT&c=! pTك @@}fGG}}wU @@X,dq{w:3>1kju̎[^)vM~`^eٽ+qKzȲuyӒ릦5K'Ҋ'Mk}VLgWZU=!<:{ҲIi-k:|qi¶zNߎƍK'MOW>Nm)nn;t׶8?ugvmK:e4w{WfϦ'M-;vLJҬkܽ?ٖ8isشsӼ|;_ @(?qs|k7PO`{OOO귮oq Mwww"^ǾnwM7N:CkEi;N0mb??mos|F[ Fq#4@ ;1 ^0%8)1H.p/8NA` Fr{@SyNDun& *h hdU$Ё{AH6!4*@$h{AY t ^M4@:p/&@G8*F8*1'k2L`X A 91d. sc\0ap/V%0 \`"^0,]yPf8RL`4 %0ƌ{i*J`X 1T.  +3c\(ap/Vޮ:QEsQeUlJH 8/*L`PAY$85 T`P+ 4NqMp/Jp/h\0A er"p4HQUN CPxM7G:ujGGwhލhٖ,hnF 8kg"0 Fs6NY;,^0[ǵ8pڙf:p/8pcLG-8G[ZW"}C*-W@@ } ok5$N2p/hN[)vth{AsZM p/h@s jJ{A;e$и dz#^tG;p/_AhG +h!^W`O; Ղ 쯠NJQ-7lFK8҆U-5 jٜ@ tiê5lNK aU@M`6'Х]ڰE{AM0Y.n Gbٌ@ xqХ Zj 9.p/҆U-5 jٜ@ tiê5lNK aU@M`6G6]:IJ.KVp/ fs]*^Х Zj 9.p/҆U-5 jٜ@ tiê5l>f:l)ue3]*A6j)^PTKVp/ fs]*^Х Zj 9.p/҆U-5 j| utqS8fT.mX"PS& t{A6j)^PTKVp/ fs]*^Х Zj 8M  @ @ @ @xo @ @ @ @Ƭјm:N @ @ @ @ xsg$@ @ @ @ 0flӹp @ @ @ @^@;# @ @ @ @1+ p4f΅ PG`ƍO2%+ @`l ڵ+m޼9?>M:me֬Yt衇Vn@ uU;w-[^iҸqJ 0nݚ.]&Lϟ&M.hˣ^m۶}Ah%F`iڵ:޼yiɥ/[,_Kn1#1Ӥ.{q1cF8q^`/+. p a7U?\cW`Æ 铟d[G;A+swۭZ*/P^xB^_2}(.RF7ߜ%KoGs9g7.@{tzpk^Aˬ$@`t _>]r%+CFSOMO}S ]y_jxDzӞ GcL1.~\s5ST~N'xby]Џ7r#O 9Xpy{^:A 4M}O-R8Zxq?J|pi[o5E@a…yHiO24S} UN?Oҗ|4p7gQ|O` \| /̯t(uug> Ot@{A! ] ~Gx^U>*C)~ӟ>)^ 1A,yCL{QGӧhvZzsSl1$P^uj\J`?}{ӦM y]vjn^#@ ~ߥɇ?|;ħ1R)xm>ϧ*}/}ic@>s `SE]'P^~7]tE餓NJu*D?tgß+_G/ "PuAS{zw@{G?#"=H|#G4F DG9s;?я~X'(^/1cto7)tA}!P^5`l$?~wD"Hh# G׾. p-~_WӢEk_ڶ"1Z!Ho?sr)yx!F>oߞog{"N3śoʕ) @r!mE!&@7̓?^y8G1QAWy7//&~QPЇK/ Gl, !BL1%ˤI1:묾?f5뮻Ca8#Ss֬Yf :8\.O۞ѽ-BcB΃Fq{Q .M" `8??01Ie(_ܹ3TP#~?>ɓ]2Q;?tӥ?c>r|$!E(F7*`c|1 |ғv/(t< 6^6u:_iG^t*i& 5ՙ@a#𒗼m# =!Y~GpYzVwVXѯlƌyp%^{ڱcGDXN>gg C'Ax3>}\rI79h |;äǧg~qqO>w͛7 .OOxR x@z_ܷ~ַojFh)Fd~v:  gBŽ2cC OЇ>Nuc\%^[ޒv<[uA>nO#!7. oH(W~^'(8(^0` tK8+}~3 [G8-^}(CG B1rHXC(O:E(VDp(b`A,/)7sCI7O{NU)FW7+b#F|J"H.ް 0=`PQLSGw]'7c nm޼yhFj*=ayh!h4g?ˋbُ1RS9|x'o/FX+v)hw/#GH8F55@/^;ezjD ]/| +_ǔx]0vӕWq;/hC{~'D'F;:n^ĹR{l>CS>?1w^ _|H(Kc:by{^:*M,"T KG!5yMڽH`tr/x]0vԕ+?XW"_.+k& 5Օ@C.tZoobW,1Iy+_EY s?Lx3՟gW_>%Bh2LbTiӦ.k֬Io}[UT+o ]Gb-!qm}S2D(_zu6:sx$@` `^iL Sp?<@W*9낱ӆP "t#p4riN*ʆuAL6#DBSG<()[x/΋[V! N^tUࢋ.?0GGO|b~7y(5pu ۯ GMheu$pH(_i[lI -FQak(>b*bH՘(Wj %F5ьb7/cXb#qFT }!@`Xڅ ~_O~z_+7?FDjSTk`7C/iz#'Sk%FTyЇ;f^ C"^'شiS b^~䣒n ^3B`L ĈGSN3쫇}zv|L>8{^e]&~c1cLoD!~G- @qqɒ%˖-K6lG&Sgӟ=A/|a QgP_^Џ7M{Aak&0~{a 2P?Xm*'G\'ӝŴgFFbkѤr!R1[LҺ<ހPڵkR1[oZZ~v!#H?? SR-FG{;QbbQ|b:؈?\4 MIO>9NݽY.41zY, )#0?>ִ*.h@ T jƇ?*ƿ0F8n]=a)ՆwVm {Aٽ.(q|`# 8"^Q,hF7(G8jcQV8 1ŋ]bg=Y Fv!+Vbję3g]I1JY L,xXZPlQQ#j_~y>7t?^q, @{A(G'?>#SF`t D Bn{cE|O:uu{bXuA0SL2yty 4j񁃘*嬳".J]#齠]zL~駧g? uu>.O!F0sI1b3G`iXǥ'?jB#-0^Ю6^QF`t Ĕlټys8|%/yIu`ZHIH/ @bdQ%Foox;H1ki1g>O{#bZX r_ րӟ|zvuA;eƮ@{AN}CB[lI\sM0T|Xa^ć b4fXƲe&= ^ફJ'p}IVԉ%AD@Y =^^<=sΨgVA@, 9˲U۽{gzfg/V{^UXzuu:lZ-JCzw}MaoG?v!;d@'傾R$UF](tƖ%H*֬YRwuW^8=#fQz)u˰^{`S%6dTJ@f 2HHto뮻.W}BJv1=H|^}segzC#H4 %wqGx[ߚJNԽbTL4lvʕ+wHklJR4+ L$ ~|3aժU9`hh(di8C+^I\rI>.~ӋL酩RF]UՍ@j4l'Lelt7t?=aᤓNn/s@Z~- ^LJ/(U炊O/ɹ@S t[YrAmo8Z*98h*o; bj8^pތ:Cj~2GuT >U *{OC*N8`VbzX]r+*t ${jI@@wf:. S c]tQHWxG.xep.H$)Yj2U.h&e:\pׇN;-0 Ґ LKTV|u\=I z.Tw''@`ubQ K4)cADŖTOpꩧtNH/' Sk`f炤e  #O@7}(tSڶ ,z I+Z<RPuSAeX2V#RJdjd!tHR1ag ۩+o!O:,Bf%6 @%,!&`B ii)amY.ı^܋oEViK_Z~!炅PZ/\\B@Q@a| 8ZO @ @ @ @G`Y @ @ @ @rp @ @ @ @@:( @ @ @ @. h? @ @ @ @ueQ @ @ @ @]@r8~ @ @ @ @8ˢ @ @ @ @ p @ @ @ @:pE  @ @ @ @,wG  @ @ @ @t , @ @ @ @X/ @ @ @ @@@QX%@ @ @ @ -_'@ @ @ @ Ё,J @ @ @ @` 8ZO @ @ @ @G`Y @ @ @ @rp @ @ @ @@:( @ @ @ @. h? @ @ @ @ueQ @ @ @ @]@r8~ @ @ @ @8ˢ @ @ @ @ p @ @ @ @:pE  @ @ @ @,wG  @ @ @ @t , @ @ @ @X/ @ @ @ @@@QX%@ @ @ @ -_'@ @ @ @ Ё,J @ @ @ @` 8ZO @ @ @ @G`Y @ @ @ @rp @ @ @ @@ pA,J @ @ @ @@ Z֮];֒ڰaC'[ @ @ @ @>8$ @ @ @ @K5G3ey @ @ @ @C@QT @ @ @ @ G= A @ @ @ @?G>I% @ @ @ @p  @ @ @ @C@QT @ @ @ @ G= A @ @ @ @?G>I% @ @ @ @p  @ @ @ @C@QT @ @ @ @ G= A @ @ @ @?G>I% @ @ @ @p  @ @ @ @C@QT @ @ @ @ G= A @ @ @ @?G>I% @ @ @ @p  @ @ @ @C@QT @ @ @ @ G= A @ @ @ @?G>I% @ @ @ @p  @ @ @ @Co%7%r$ @ @ @ @`6fp3ߡ @ @ @ @#?uea @ @ @ @pt~H: @ @ @ @zXXH @ @ @ @`-0xNQ @ @ @ @& hn~nmG.0 @ @ @ @F@Q /2 @ @ @ - 8:  @ @ @ @J@Q_e'V @ @ @ @S@baI2 @ @ @ @9ߛ:h`_]Mn_QC  @ @ @ @F@Qea9LNM.J-  @ @ @ @zF@QdE8jmd  @ @ @ @p4]ݺr @ @ @ @,mVpXK @ @ @ PpTs4^X6Nlhf)*LNM{5|O,(ਆ @ @ @ @Ep{~g{.ǰᘝZ}@X12\uϥ+|y9y =և/wޖRpR >J~Nk qpӆ?!uw}.8N5Gm>[VG]9 @ @ @ @@ 8ja@~=cw5\׬+k0<0põ믪61~wyidx`8*CwyZx W,v'D]7lu)[C @ @ @OG-p4:1rԬ[6s}z` YurD p߬No6Rpȝڗ~4{p4¶O @ @ @mG-p gK7웿νBa9[ӛTZ7quW(-} @ @ @ @zW@Q}_ yKJa- ;%l=&: 6#ۅmGp-?H˾R [H굟 a"L^pft}  @ @ @ @ȮJW~,\wM>hcv~Rjx0693+l-mvZ[Mꡭkxs0>ҷ5nqF 8XN+ry(oqGk @ @ @ @p" 8{3xI4_}qnHKC/055u[duf#.ծ^wY8orpU5Wsiպ:]QW9m @ @ @ wZdY;G];ΛpvG״pzo]m>t;]cwL[~B%(utJobC[U_u[ @ @ @ @% E~p}[c ׮fP gaǕN 8z/op-o~zTy)npM?O4Zku҆ @ @ @ @}) Ep=_^}@? ?᫅-#wz\8ji.҄V^ϟk?9-X)uv}qɩ~`xaZmU{_vV8oe돀] @ @ @ @@8jO6{InuW]RVYRQ}Q;>.cS[1l5uea]m>--((MzxMnE)|*\qoy  @ @ @ @F@Qj'(mk jp˼M]yb+Ew1p-?/S#;Ri Ͽ{p7\fGi}z`8qSƉ W'5ztcn( @ @ @ @u1Rۆၑp[bDm}4֬1;;ujaG %m? @ @ @ @pԛҳpԳY#a @ @ @ @p Kg'N^: @ @ @ @lFm#hgC'@ @ @ @DG~ 8 @ @ @ @%' heu @ @ @ @@ 8 8  @ @ @ @ 8Z`~ߝ~A'@ @ @ @M@];` @ @ @ @@_Z 8j%d> @ @ @ @`i 8ZpuR$@ @ @ @ʮO) @ @ @ @,} 8Ld @ @ @ @@us9lJreH @ @ @h. ਹ9 5@1 @ @ @ -ơ 8ꆢm @ @ @ @W^  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ hގ  @ @ @ @3)&@ @ @ @ h phGh @ @ @ @t]`ժUmmvکܴІ :Yܲ @ @ @ @K" @ @ @ @$q G%@ @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@@Qd$ @ @ @ @GA @ @ @ @A&I" @ @ @ @^p+9! @ @ @ @@〣 6aI" @ @ @ @Zd! @ @ @ @K[`8- @Uw]޾r28&5J"@$|%H!@2pY &p5iM#2 @RnH Z 8o6 ' moXj#K-Gp4[9 @,EaSZy{tV$@I@d`mDudd$@D @%7Bۭ,A,zRQClVz @"~Qpޞ5  @yP>'X%@2pY&0 h) % @zI }/冴 @vk#K @ +|F&:r 0[G @_v;%@gMgE @`O f  L\GIF;LZ 8jId @^pCK!-h- @ (, @` ,u<V@lG (nN  0kYY'yY,בeZY @RnH Z 8o6 ' moXj#K-Gp4[9 @,EaSZy{tV$@I@d`mDudd$@D @%7Bۭ,A,zRQClVz @"~Qpޞ5  @yP>'X%@2pY&0 h) % @zI }/冴 @vk#K @ +|F&:r 0[G @_v;%@gMgE @`O f  L\GIF;LZ 8jId @^pCK!-h- @ (, @` ,u<V@lG (nN  0kYY'yY,בeZY @RnH Z 8o6 ' moXj#K-Gp4[9 @,rwyg{½6nҴ4 +V-"lVam%cMy  @@ (PfH Pu3M Gj @%\no馐zQaw:Z'rޞ'>%@A@dPmHudeC%@`FG3I kKCH 64 _F Z*~_٦ @`y{ 蚀I(mRudYf&@(& @R뮻W\gqHݤm-ܲlTY&_>wvmʼ6ۄ}7'@B ,B }"rmJLf1 @ u].udɐR+Iiai,R鮧 @` ,w4pLt @Xj7UJSQJR8C;0/zk TQ <2 @`!y{!X哅q,UבNu*fy @EX 7~{]u#v[ >vmWn%ec @(, @`,tU@\O Kg?YX~}v;n _}Kr?|Ak',op^9  @P>Yzy # m_Nsgt]akB)_a! oQ{䍿(>Vn[;ooߩ.1C\Q>mnߥ U`Zw}򼙁]DzMNPGiigէ]PW[/O ;j2)aߢ0uKFd핥~`y~+el 5F @:\uUk]w tPGp>ur\T]gɟ3r%n!/{<fhX֍ev\8=~H0ySt?TZo͢$ɚi۔[Į+%kmYfP NzIM,PbT]H9pG\su6#ݫbmN S!LvY<^')_96] ilT3m9vokZI-ק%yx/m%^3V7N9m՛5X:)]c>NsC塡nay#]%no'~sS>TR>}XVS]_zN62_Wu3ׂlnqPv{+wg!Y ?'pn1+v0y!ko<,J;b/M)IAi۽7._OuuY{غ嬞-α,8ս7}nͿ@yu/ԝWj3ݕVRqms s5g6XvҊZS<ڥ6}6c2W5\D @|C?>>>011QG;YO,-j&ell,{!epp0@%Ag5])owK `g4߳~V>KuУ! o&L^ȫ.I`&.H?TkBzݥV>&/;S_Ojiq^O&M/ύg-*k >ua迩|LK 9y|0xث_BOihެFRĹbP!]<. :  θ.mtXǧ||2oe 0uǕ|0|ͫ?Қ}C4 |^.<3dik{+;w xy^>G?0S?8m~ >L՗OσL?s)M\rj_=xlޙg?Cx\]z9gs-KU#s{<$o^jΫ3xP^Zr |qqӥiLk}\c6J{SQ9rEKq*C09%S1mZ1*df :ڋb̏* {F~^O_4_}4EaM鍮=%/~橛׫ O1F^Z= mZ-?2e䍿/q#zZU75tGdL|su⛷P:sNj+7uoo&yIu^|mLp-_֒3./_lM#)xfo&s71#/yJ+K]tp4DŽW=[ozF%5c_~V9NT- @@9 ֭ QzԬZFQj謳ΊEɰzpGv?lqAjʺ/A[=&ݺvj1uͭ ŖY~QM#(9ć:uC6{p3בSxexSSւb%nzZ:yܝht9]aw_΋ ==a`Ë[tԝtƿgm.wa6Xg,oy鞭AK3A^MBֽc8VRe)u9gS:&oy҉U%پ5qbFoᯉ-0vٹwK]L^0Ґ<^SK+ >Tf|u'4b_ԴLlv9< ==kzV<ϧyRn/]˄pR ~j6tZo30(m5+uIZZ&-K[y8ժDugҞlo'\O7R?_7^z!|ȿ݉VRn^RG'\ׂxߖZ⛼ t N7:9Vʴc)^VZ9ۀXAz6x8x~ψ]/̱5R֦[e 8R^^(WY{}؍7{į1ɬjWn~b16r(}%V7W5*l߇>6V:f+^{Y\}zS_Պ\aصXjh*7ܬRTK@i|kc8*15ۘH f )Fv2?L|xGŷn͕F \@kNH/e[0Sp]#rk/V5NtO_oSQ 8Jî:Yt#(K. 7pCNC 8JGhn[;\qnUg˝=/G'_zFy) i`ؒF6~`0amj8(qkL>Ҍp:ŧ%sV<9?: Z.S8TSRPR7zPTn(^S |?|ǗYU cߌ׵8 /̯|)Kfiݱe>erh9|R%LN{n.^w0VZ0~v6soeߩbwp{( Wa6Go}vl1p6gbh>lԌf=4_'{PRPr^MIYgY L޴2t>o.V?C^F9y!Tn妝 7M;bWyk#S~sn)%5AX I] Қ}chs7kՊ?Mw_=5 5GيW^߹էTbMM'+U~'b|17ƇzS"4ZZ FMRjA*Ga@_{cpr9䐰; [GrK so*=V v˺4= pw8A9w=tĖbO$9yC&- &ߟ?a`ڮ? 8 ] 0ͭp2P2񇇩زm҃@2TDLJ2:U:!w}ffUe^ /,_VΈCǼlj_?ȋNRncym(mSl(fPK!sR r"5S]KC}SM=NV6ܒzk_lR xDyV'Zm2-DqIa$YlYYIK1B`~0[KoVyu[Z쑆bsq=V!]0wgp4@rpgHviB7@pg L'_jJuҶf*ugǺĺ{7r?1kh>>#਍L0rqnk٧ś=Z/F>Op~V}Pifm󾙛D.ƋNo ֔{v="7)*+ÂH94\ih߆ϧ֛>֘ϩ// .xp  ?ߊo?<9츑?nahuB*,Z-:6+,~>[|Ka]m4 8jO'_OG`{&|GY|q"8ihvܢ?+}`l8tp[Csm?vZnn%਋;)K5Z?k_jJu7f*yit>o.?̤_ütz(՚~A-͵᥾s S ,UXxo3&L\<NSo  қXMсE+:\J*GVOˏ{ 㧿16<7EOvI|C{LySn4|b;-|(rSlXLAh#/ID @`zCSKݪ\2}ѳfVQJ9ܝZVпŲnt/1NÊ\k p_=Eغ?kc癈 ύ ֐=VQϏOW66~y冗Ӌo>WDT|1h(5K# qzoqm!Y<("aWLlƿgaw_Λ+nx哙~XniR= <7{ꌺVׂ1CjY1Xr{`s[72DzU誻Ӏx=rN7yy7bgfh^lLf4_'{PZOr^M)o~a>ST^Ii*zn8f{ M6s)jƾ~Rk!/+#xSa?3o S7MS.MM+&cM\b?)h8w֌ @!Я75ԪК5k>knW wqGnm)d_bYwȪqbtMiջ7>k/T._Pl=#OgiJO#[E-w'4Ӎt6'ߗ5eZK/&a~GӟI|!WQ~Cx`Vu tFX XqOWczuJPMOpC̺/Fܛ@⃿E7} ߷Wne)m]ӘfqJ;z\M ;Ey&vxO] JcPSo]ե`PVl[j`5z>?>L=+Bz0{wCR#aiBGן_fVgvt T)}ԃH_|Wy~x̛;ܞ-vR*kdJa/ TΫ3~x^M9|jx>c^&/=ړTyߪv>o1kLν4OQ'KOG!yG}j>>K˂0!Į4bECeHu]_W|#4 =q+cA7O@~:3=|uj; ۂ}AR+J54W*hOsN-Ssٕa+觏_c0б2k&~OoFCh~6I:)o@4]  @`zCgĘ;C9d̀ /0r-T*;ni ˺-m*'z|5^7+cbC~}eŎtyůBiRS+Nֱ6酗W]fЯ`/w~aajm+`x.yb+#/M}M@ԭdz9+餺ۻrЌ+gVo#RtC*Ohg$Wb&sO!Y]ug>[I[u|'[W>i7H/L[ۚOz"Tz(kAXݼAIDAT0t~˹ ?Λlli6   }vk|)1px}z'06Tpr4 YGgrh^lp3}n䅔,@:ZogE'/םUGJWʱYP* fsWgBQ:敡/Fl>0xğndYSb7n'Moxc>Yrqŀ觏o/yja⷟ ?뼍8O>*wCf)Moԥoo^pCbk_xR}5X!}u B7 Lri%TfPU''9;[Ȼ7 .2ӵⵗUljLM-jW42^kZyr<$++?CB%83ϥެ*^dj}έnWӾ֝MHaBq{]>mXmQǧ||R!@ L=#^'L^wvU^a$ⳝPT?Sya7 x/T7[w1(b7?yoJ}H|妶j^4zdfhlT'lO.}@弚Ël$8}ڬn8 lZ\%wbs?VWL|k䥱~nk M{7\cL*[FU ^ŷZ&17 8Kcxil.h0q6wwO"O͆Cտܥӛ-Q3ΕxVloNCǽO @`G?Qo}8ᆵ)-ЃO +>s .v[ Snf o[=Ы6۞["&_pDZՊ4=TkbKyM&.bRmj2D[)I;xr?0qI|aB GB,@ 왺BD!QO]i:M^tX".Zw,^&oy^:-'-4붳Er!Y#fMI+m빷to7zb82M*یsmpu/_w2t 4q闪&%F9+ݦU.^/+syIY^tWӶ[?V_<̦^TZ _ * Cʽ7mge.8jWh^& 2&Ҧ2^']Lh[C`rki i~jiG_ҶnکI7[im {MZaʳS+֜b &3c7ћ ?a`7ي @@7s 6lWGټU\ 6J9ºuªUQG5Ĩg [֝Ŋ}VCr?4GezjmS,="vK]zQzzTΐp^+gp2#c+Ij~i7?OQce(>8J}i]1Hj2ZpTIb Lc 1L~y~ .Rk; #'/eB+-_7ԒX\^vb'evza~;i_y5l Z~sڟĠdԥ?ݬ+gstgqG]ȝ|,965 :jշI-.?Y,M+ݨP0+SEvNoZPک@OQ+o4ocwt~lS.mX~ Zo*P|`GS^F?B@N]+ g~n000=ضdftnQJg&''vm;찶dhv]P4]WA[*O?&teYV3ۧ],$zܻ۠֎*6 8i=Ƕ{WV W~+}U{f1PPOݪ]Cy? 8*Ǥraj]l1/VnGNQ|sw[VF'v4xe|iPNB }%sh M?pmx;#O&o+iOܟ[J:-Wײr_E糪[걀ȉ ?|sNAϏ-*4R̦,եky3u=lw)t|((@弚Gi?3oZn_4ZW=^T7nsd矘I}>כ c_~Vledu꒠}7*(VmW. pTH_uX ?&aCbvQJKMa[O @@w5+ k׮GqDzۂ$訓`;9 {ggeofN5uűŃ7U[RbJݺj}WZPM-Sw^F?xC1# +ōjӑ~Nʹay t aVQѱU#`ƭQ75ru >n+ikpKPPWp9!@`&fNyR@꜒+oʲad5oKK I]׿h0mkRkY>T+p[Q“sWu{z0KfJN΁!f;CD;(.j{ Y>m4Tǧ|RU8e&Y]bӍk~(*[.LZ] m/ׅ{ʾ{b1ajMq*㶹N 'ؽZ ϳ65J؊ӻUJ۬t S!%Rd*'<5|(ਆ}+P9FQL4}X9&.tonYw8"Z ݬ+ c @IDATEg#,9'TTPQ)gϬYO=1gNgT%$ə Oazwz{j~Lֺ5煮YfUH SqqYzJah,ZR>?c?{\%ס~7ȆGJ雧ey濼iŲf~Nm|-)3`G$׫ڦɆG&RwѼGyeÒURXȬSx;&~}!@ L6Y]W.YDƏo}7ˤl«qw4>Μ1c?/m۶ r;F-EkՊ3̵9xKո iYRV](%)n^#-_. KٸEJטz]5>DrZm.'1z$֮(^,% )zl򎹶bGIOm ܇ߋMIwm_8UʿLG*)x! Ӧy#}@|RޚߝAR _+w|w\V\f+Jכʜ`)wc.{.Qli= to8ZTGpOWރ%_aB_} G̉n[7~ qϫ?-z?{{KW}l{!J{=2$jbü/DfyOUb mu <פc7/^zOoy_0j>^h^)( xTrz+jcon 7&XSR8ޤ=w{ t}Rww|哟$j>5#_ŝUŒ殺GGxٿF;(\{̻v߉G+yֻ92ѯeXps[ӤwiF0xטx E^މuTq)מj$|Y \w5Q b Wj ]{eQoe˼Zo0`lҼ? x7C{|ZZ{#bnW,IqK.13x+ckH *!C4dOFޓ;MOzV2su06Cz73~*v9\`T58+3Tz|#5PŚ^]-x}OHY%BheGϡB f  uĉeѢE@{R4tSoNuUÒSo߾)o@T2v*eeY@ O28J@ GZ3-@(lq  P'LoЯZJrPyyy;J&UxP׮]k]^^n}7o<B..@@ j\DF8@ 8dV}q >Ge  ipA?c :pf;찃AMVTTȸqDui77~[W @z\  %ICɳ_@ #n#@ 8![@@Qeɒ%F]vү_*N0A/^lѶm[߿Z@+m[~" @ p}uH @<Ґ$@(J  $pA_ZZjzZf)s֭wҨQ,~z2e,[ִ̬iSӣRAAA*aY@ +" DNU d瑌.(@(l@@\j@@ ڠ>}̞=K۶mcǎ? C-YD,X`~nk׮ҳg؏x& Ի'N@F@@64-Z$s 4`&H탬: @ dzcc  vON@88]R p" 4@65苋eŲ|rYrȯף-ZHV]vRTT! "M" @\L  1Gb0xY-@(# ' r:ҟ:!' Ul:@vO7#u@ w;zu! \doG&yn~8r.)   4賢)$8$m*  'T$@HH[U   O}<!v#B@ >ʏMHX pN]R@@ +hgE5SHpHU&EA@OH4g 9G@@ x:C' G @ p}瑺6#@ȝ$  @VРϊj L #\8Rh # n@ r"W%  @<tDOѫ@l$(? P7#ucmpG;uII@@AL!@!n;T@G>q") @G"@EJ8 @@x40W' .IP~@nG #w꒒  Y!@>+B"Cv2)  p}HER @84Q)6|Df>   ̘1r-k] }@t#A@ llM @0#X _LW@pR6 9Y  p`R$@2\+ exEr @ pi xv p*@@ =xJC#@(:u  Fۮ$po @#AX\ pbR&@@a۠'pp%S4pJSIa@pB+ 9Q]H"*@(a!  =#>E&@(j5  mW8w@ j. 8rV)  ఀm8r)8%@ȩ0 8!`ەN .y$trv pъ@@l"Q px@@+  DH5AX @pX6 9\  pTuR@JGNT'@B<:9;D 8hpX  AO߇O@ 8Zp<  `ە]@"y$ \Uʄ 8,`dN 8r:)  Nv%#'B  p "@DEb8,@@۠'pç @EF8@JG. <Du@EG.*eB@ zGW2EC9U@'lI!@8N@ "Z1  m8S@ jV# v%#~@p : #k2!  =#+!SN  ]Iȉ @GB'g QG @@_6 ) 5GQ@l  A8QcpQJ@@lÕL@)GNU'A@ ۮ$pDuR@ t#CV   /`}ǃ ]I@ G(@ZL  AOJh #  mW8r:) !DTQD+B@ zG>|DMQjA@ۮ$p @#AX\ pbR&@@a۠'pp%S4pJSIa@pB+ 9Q]H"*@(a!  =#>E&@(j5  mW8w@ j. 8rV)  ఀm8r)8%@ȩ0 8!`ەN .y$trv pъ@@l"Q px@@+  DH5AX @pX6 9\  pTuR@JGNT'@B<:9;D 8hpX  AO߇O@ 8Zp<  `ە]@"y$ \Uʄ 8,`dN 8r:)  Nv%#'B  p "@DEb8,@@۠'pç @EF8@JG. <Du@EG.*eB@ zGW2EC9U@'lI!@8N@ "Z1  m8S@ jV# v%#~@p : #k2!  =#+!SN  ]Iȉ @GB'g QG *W'X".@@ AO(! 8W@&`ەV3 G28JH1@Y6  @± M+  #N pqz) @(@F@B zG!ó;@ p iJGi#f G^ )8JE(@@lQ(@( ! @lQڈ0 !@ `8J rKc߫l2=@m8Zp< #>E@JG Wd#\{;ԧ4{]i cۃ i@&`V3O& lذArssrii^KQv7E-PQQ!.2z咗M8Fulۮ$pXRidԩҾ}{)++vA㎰vpI+/G壏>Q&m{rטI8JP[4QH@  zGPYb$.rN?8,Y"~{:p˖-eʕ yrN]gM|0hSO]Voxp 2a'7x (@1 a;eU#0_W0Λ˥^Z<ր.3!8Y/?_ΝkkF3Nx\xᅡwOjFlӦMͼxy-ZsBGDlQ|'"Aa[oU>M8d+7w]~{)..6?j{@{n=XG Qf8  aGPYb>s袋L&MJ*ptWѣͰ%'pyM6=}oys?me뮻ҵkWӧOonƧPlݺh5j /k?$pTgB6dO>iݻw7]J@8O9{Z饗L>PS"_m^z!=&n}XzI& ]uUg#50t4|𸇡.\sYs1GK._ƎkzԞ!S-)iە\ .?sܬY0j _f8S`Ĉ:x㍢ c{JNr61 %*G BM($hv @/ e@eqi:Lǚ5kK. lHD~WLڳ޼evJxs)a|M | b}zH,,,ujQPP`[nQZuAld~8zg1A_p5Ѝo*F!T/bs #HΝ+I_}U`_|EϯW/V^mʾ~zszvYgeO>ha4d4yd3,dkךnu;/ ٨Q#i׮t4iRLzcە28V=^}&(CjXLGP#ԗhgqfW}@/]&8JPC8ZvLT:6/:2"k֗ɻ͑YKWI<ܣlۥM ޓAekdފ53إeݹtoXח++e5ҲP:h";u m6_2e̅fF-+ #`&pNRp}YԻl&T2^}@>ӪU+](?S{0%QR#p܎5@R iw}L:Մta i/~a=6I6r ekh)6\}֞; /Pcaf̙3$oXtXN:?Ii $:׮cj0]tz }k'' 80nRvJGQ! 2U@C:<C~gejr ^k|f5 \~>\Oڳ}@o٨~F(AdC{i ٲq.2a*/ d nޮ-)?&oL%VTvKNU~^ūgOҲLxc]7>E(pTZA.|SұE<| 1! {aG-%Jޤ;c )oIpz衇oVR֭z#QT($pT-G.|d@#f|4/^bݴEp;ۼ@{Xk/+C-CUHޖdon O77tHnݺU_cƌIޘ4v:4䭽[굥2uJGZ7DY@CG'.Ch@beaui;0QZʦLtԧG:/wҳ} Y^\"{Ǡ r}#eɚuݦmMGjfCg3w)WW?5i&vn#]Z5ūeepU:T_|QPz󨣎2~= R:}gM43|ޚt&}C=C9ҷ&]Oövm;n0>}=`^'5mTsO~ժUfx;GIl1v%G8r.) $'m ~;~b +dذaqLG j%G-J|B>6HFM* {no#4; vҎxOuk9cmLFPkT~T[[7M%z;4qWn  aG[x'N>J Gz#LIѧbz wTG:ү_?iܸLմpBܹsMQtdD(jcE+jzeA~Gg!/"!IZjl4zA$?!ky3kB;w XPsz7iEzC~~~7 "[o?j(сhʕ+SؾȠmQU+@(cG7|hOᄏ\{qr 'G93M(D{QjIoiJoxU_RemYfI˖-Mh̙رceM75iPIo>{:C{sMm۶l @n>xʼn<G ha=܄ A= >wyk7,W]u{W}yoWچ<]9w:,}k&̩jޜ2zx->7E#4LO[Puz-K/!:u~&}hە28VTGZs7.p\|q7CGys9qLG j%G8%luh#\n;|ѳK^,g ];5pOS7s1GyĄk=hG{g*S}thO :iHjƷCj+.. hѢfcޢvm7Gc:4JQQQgQlj_ \=h ѧ& ҷo_QICF7pFЧ> sA#GI=iCN>`e'v?֭3w{LoLءb:mڴi&dgyF#mYEn{-J{0a "m&<#*v%ǁ. 8rv) q"L+! 'RyG]f,Y%7'oÏEvټcM0@_ 8rB)F֬YcnB?.# L>OofvӡpvT߁#b1+ўwۏ#Fn &ɩ裏UY)8ʰ p@4 8so׿{dzPiz-p5XF{׿5LjÜi(X4>m&;ch/B:TNO<5/Ǐ7~zm{̛7\z衾}CiFr :?Sf7 l[n%n\?JG& pHER HZ^~ry]O*fI7jg[E TD֖ɳʼkeʵRRzG4E;ٴUSyXbG ^?^JmձO{J^nNm9 8&`&pXRzA޽{>;i@/kP.~&^nnnb5^0`ְQf{={X.***Ljf<+][=z{)ojڳp;-}Io2n6d̘12n8SaƶC1i{@&O28v@~R k]_&} Xϧ ӦڡǴA Wt0 QWҥKoկt?ڡy5뮓> v֘hk7;de>\|Qq]v6cVݻ^W>s&վ}{3_~)'xm2v%V 8r2) $%pʇ~h w;dC"gD(Ae[hE^ⱼ]HCG:hB:h"IFRT'l8qҧsky %; ^GR|Cs/9om! @ / exEriЛed|2S˖-p%]l@oiH~:_PeJu85=i@hСUkGtT8Ӵl~hKo&_ gGRQ& @O>D z;t_D 38S{>-ݶjvi'5iD zCaXG!IC/ 8PtȹF=i0[KzM駟D55sL+oӞZn]P"޶+_E98@ Cehq X@zև m7$/X@=\ z`]9QD6aՆl9f ^" aG0/iӦ@^>P&LM .b}Okf2Ch(hF:ȧ~*Omr-&0 7`78!E$j@o ?I{qӧOުQI(ʵñ! 8G)eee&i?|A=WMr9jh ~=!i Шb'?3`ފ&ѤCj/} Q}>|Q[a&ꫯ6Ci/Hz=uKKA'駟6׋0ĉ+{zYm˴mQC pK"y/R2c@\0̭Ct\p G?x'  lwuR#v9s6.ID9@{*Tכw{SYr(*{ìiRzô(vV_8|q @h4\!>X.*ǪO kpg6۳o4=cUØ`|\S{ʏM\ؾ UBJzma' Q 4|!, cMW]u3Cm#4$qH}&O,:I'd6Q\\l<5ձcd7lQ$@9^j؞ZzĈ yL4ޮz!իbQ!pTߖQzE{:w_Z(g??Ƽ8<fm =zuFcG;Tv,D}}YXF>n{ v&@pW~1L:dH6pOٜ}>=e>= 4 h"ntSVYsΑ>g\sCۤIDϔ6zhԡC߲DCGQ _ H/K.DrrrLhY{ЧuUӑ_t#8BGL4IC>gumݒ1Q:dbdoZrwVG7x MCnk׳>kz|GL(\xwEuu5tR3}2[Cݶ]曦'(=>/ iJ4v%L52UQǍA=$`ڣ>O؏3'E*{/\!6i+_) oӟ-,1+V釧<5J&[*}:o/If8Et_g>혁[&/@@5Ű۵Q)k5J4H _q׼yso*ݿ\؏9rdOu8W^yž5?^sӭʇ \|f9xM7%X@!ByS {cO{]ymJKK; 3g{}qf$է2i"pIű"+pw{5裓sܫ=LϿ:׉'70}2X 0pf64hBLϓ& +kGOk{BCv\$3,C63gάܝmdz!?|!nW !R{ u1޴vZ MpI뮻4atLڃw#DwtX>@V^]elv%LJ.8r)*C=һwoc^NKx34PQG;;c^8JPU]Yû,lQr~]du2 ^/C9RRAG.SmҞ;pG!V/Oatk/56 O|6M5zY6 @2J~1L(!" IotArEُ*N>] z凵fmL׿̮xܹwo,E*@IDATJ>rZ^M6 I TSPMl|jGzNqUOo 2dPeF!p!"iР&& i@&^ozújv[Ve]_{i/٪c+3?QHo'O Yu7iY@H(`&pM:QoB{^vی2~74X7Xf})vLyM(SjD/ÙihYB^v|))S^z4ptҞ ۵khu=kFWb-d^- j8hܸqƢ_~{З mQ]YHNQrN, L4 @G"@EJ8 @@xAO(@8N]p$  Q+  DH5AX @pX6 9\  pTuR@JGNT'@B<:9;D 8hpX  AO߇O@ 8Zp<  `ە]@"y$ \Uʄ 8,`dN 8r:)  Nv%#'B  p "@DEb8,@@۠'pç @EF8@JG. <Du@EG.*eB@ zGW2EC9U@'lI!@8N@ "Z1  m8S@ jV# v%#~@p : #k2!  =#+!SN  ]Iȉ @GB'g QG @@_6 ) 5GQ@l  A8QcpQJ@@lÕL@)GNU'A@ ۮ$pDuR@ t#CV   /`}ǃ ]I@ G(@ZL  AOJh #  mW8r:) !DTQD+B@ zG>|DMQjA@ۮ$p @#AX\ pbR&@@a۠'pp%S4pJSIa@pB+ 9Q]H"*@(a!  =#>E&@(j5  mW8w@ j. 8rV)  ఀm8r)8%@ȩ0 8!`ەN .y$trv pъ@@l"Q px@@+  DH5AX @pX6 9\  pTuR@JGNT'@B<:9;D 8hpX  AO߇O@ 8Zp<  `ە]@"y$ \Uʄ 8,`dN 8r:)  Nv%#'B  p "@DEb8,@@۠'pç @EF8@JG. <Du@EG.*eB@ zGW2EC9U@'lI!@8N@ "Z1  m8S@ jV# v%#~@p : #k2!  =#+!SN  ]Iȉ @GB'g QG @@_6 ) 5GQ@l  A8QcpQJ@@lÕL@)GNU'A@ ۮ$pDuR@ t#CV   /`}ǃ ]I@ G(@ZL  AOJh #  mW8r:) !DTQi g?7@@ AO(! 8W@&`ەV3 G28JH1@Y6  @± M+  #N pqz) @(@F@B zG!ó;@ p iJGi#f G^ )8JE(@@lQ(@( ! @lQڈ0 !@ `8JTϳ_4[FtoLM=o! AOȅڤ  )# %`ە28Z@ *GR45@(Pُ>]7y\guUpiܸOVrT RZ'dgr~UB6~^F#q:o*6A@AOPo?JqqnZz-~t= iXꫯ믿6 ÇKϞ=Z>Z`9=oVN@͛gGySN͛'-L,@(K  ,YDy_b9^\RԩSeƌAM7TvuW9ꨣ0.ut}姟~KJ-D7eIKm|I8qhIZ}X&l]ȶ+]ɓuH@ 3vvm'7tSfx< `q-'|RCD01/> p% 0QSɳҽ>[ulmn'p:ݩ[Ulx l^gBKD&>En:VeȐ!v_|Q}YN]w]ucq]w%gϮ?\=*fʔ),z3N{7Ҟtjժw}ҹs6) 8J@*4 WcN8w_z!БN.4&lbnmּO4@A! s7m\s,ZHqAzsM14Zv,\lw-4iҤIdLۮ$pͿ);h}ѕ!pTI ?<nI57|#}v?餓Jb1mUT(N9f2QU҂ S68e*$??@.`&pT]' ބҧ ]מ3O͚537kڦ .@&M$z~nwժUvHo=ҭ[7@nћo`]v-[_|!7| "mr) 8J@*|=# 6̄*gļS5\cz+e^y謳2:=kH{mMgvNHd3W\an,hGy9=8>;g-t9YlApi屽3u?E% i0Vw ;9RE8Ho!{>wi*;<{1_^^.#N:܇>vH]: w}7?Ǯ7OFk|=:Qu` sc-@1C;r9ހj(hA~â鐰_DwqT?6;~e̘1&(zꩧL_ءC3DM2ԗ]vYUgt(T& <鵒֦MUb>lo! X@üzs&{gn8 ʊ `:ߣ|ɾ%ꪫ䫯C=T03O?MGR&] {GzQ-Z!K߅#! f.K^JoAn:hL{^_Zs}eޛk0_$oM#׬\ƾĿߺu> +Uxa ?k*tGO7oz8l'i텐RFLYW&uشUq9x+9_woe쌅5ֺx5Vາrv;f(;v}H@H~1L(*E@o"qfh4 藁N;3Jnn1 7 .@v Mছn>H`:6Ȥ$&Ҟ!55]tE2ydOƍ3àiBzso!f{9sFT4#Ek8)l喕7:MfV^-.JG, LQ07BxL Q騣+WmdD(AeSEiR/yrm74T&̯g!dG@Kw%HX.{kYziY rGZF@vp]{ @ϟoaEfqWV[mN] W_} S{zE@Jeƌc`s3K.Ƙ3g̚5K/^ln GIjZh͓"رyB4)uQkcC2Wk*_~yhX{ss߾}[o dWZd9ٳgN;$'xt\Kf7kkvA~eرf?һwoF-zSW9seذaYNy+АݎnժUaR::,lەȪ @b1pF@dxwev3=+:w\9S#h$8JP-8R J5O@U9{u?4=flߢr8!HӞ>ki3T[-Vsn#GnY=tX5otדNՇM#p'g _ 8i'!bnr52=6xm8駟SO,jݍI?|Qp:D?lnMD{I&?!Zva3Dۧ~j6ѮԧbC_?`F:MIo@f@^ o3_+owpRyJJ$`V@"`p| s>9` * &T2HF})qvv9vv:U}kϞ~֭F>^42FUJjfZ>i"3Lz외*{ _{0vm]Xwܱ49 |QHb!@E9 d!)Gemxi8餓l„ ͎<ȴۆq% RiC6]t *WxH[kM0Te|Gыv- yfk_/m#3G f ůִl .8wgg;yub@@ bQZ&V"T@Ӆ{''իgxF[ҝF5x/w}-Zd Gg4FOhG y֢E w n)6ТQtq 6p7ܛ? &-^؍v+ʥ(' P>QO̵ tMn7r M1rt k߾*ܡ#?G>k[~(Bunm˹w rL$IpͫLSng;e뿂B:iZ0#tԻklc8Q@aGiX@Z%KJO7v tO#9ҽ}eNIZje{=~裏\)1Dm~i7uQGeH}ݶo}Ml#_VS$:^{i M6dȐd{]_͚5˅xcǎу|IqmYk4/\GS0hV5">i}Mٖ )|뭷܈Fz[yիiXw}whutzMGWnW8* Pʱ(3D@O?vyg;clnTQ=8Pn -Vi#eɴ,8/L^M FiB7P~8ұ5xQ2k߼ڳ )BW<֍&6NGaWCLf#>޾5~Q.pzfv~}w o?Vi:G1 ^  V1L(-+JγW_}Dt"/|VE1p릣<9s )tGN#/QN61t~n7MQ4zg:t?\ٽ&pTvMF@P zэ/P&ב|F r)[뮳#Fئnj{nSzOX+^o+v[֭[n&MTN63F&H efКU:u긑4Fw7NtM~̶,+ Z#°PhZ(,\GЊ uN;'z0!H+ eKCzudx˩v鋟¥:UZ5vZ i0Xzu2xj;p~?g=>z FQҢs*vPխN\igs$_WNԬBcklխV:wZm[I}iw>4'+/@H+;2}-.Q  w7VZi%{衇rF>pԳgO7B>p;X4Jn4}MbM7:T9a~M(̭C@;v^A]ݼ%P7B p# CAA)MeQ9*SwvGִiS*K/^{vuWɖύPM,ͳ)S t.]֨Pʧ.p;(HO˹JWfe @ 0Q G\`z ~g;m]vIi(8,} Y0ײA0'qnByт&]lf`2J;tdO/\؎xmˢ`ԡfvjnfբ7[xq^:n1'pAQUG|b/~1V m>UF[^vKB4з>޺xwՕ?QH##=znd;t_4EKm <%;iG{Skw#@my ~" ^1L(k: 4H}5H$pi:X4݇:'%Q:4HC~7]sg"pn&Nh|pN#9T J"еZ!~8 ɦѯ/<*itAMEPMͫK4f~47t5C9ąS>Co,M\s֫W/;KSi:}6 l7Mo5&OR2?wSZ-Zd F:9 HK1G:_3Mk5;w]`zg}gݵdؒidtSimۭ8s0w⎗XNGIx@jaGhx*z3ϴݻ;lnlml2M_iQfw&#=qUOǿu#X4 5r5kF+ 5.3|M7@#p_!dTbRLөiSO>ij1==XR'~0aj-4e~{EӲf[ouSÕW^i}}Qo 69'o^hݩFW_mL}Ri]{1r-M*-R_tSխ[M0<7> ~c}߯$p֠  uGQoa+hDٳgK~w~MΝc&0o88y#r=72=}:u[WN6:]ۣ|m ֱ'oM׭ۭoa8f{;wCoΗv_FBz]?nnh#)  Y1|Gu{׾K.i- "W7@Ӕ# ҢMOMyɦqϵ^0O#dэ#GίhD_O ,頃J:}HJgrR4Q4z.Kmf̘FXM~'4ԾKJlpC1 @ >lwMݻs9YO/t>ڦL;6Z,SJ#4sFS(գcǎ6}tT 3) )^a^4Ul裏lԩ\!+]^ zk C6 ߯I!@$E(>>7_veC̜94] à~GZQUKр Fr.ۦ]Ħ1)W?4Pm&!'B:m6U5-x:qhz}>m[T-xF.~aFHWgypߨI~8‘vcvLߵGA)nzsڹvj8r @F0TloѣG`N.0+ʥ(' njc9 nwmTTr!T?;L8MM{oW/䵯KըK?SI!t~'tf,~ԩ)dPyPO=ֶm*QXJɩ G 4(a(DBQ$J @tȐ!n}Z#àϢX߷n,?SQ? GU~M![5i`݂~ЦMۻ_O&\kI#iiٸo6md rL1MuӔL˯4i<}izul-N/ku5CF8z;k\b҈I]Z6 لE<޳ÊvюZFQ~A@ bQJ"V PM@Oh3<@+S_hHat) 4F*Uy tStm K˙r =? 5]Yg7a U+3VV:ִi\Z#n4BQTȕ )LcFF͚5K,R|>)& Pʸ(:H`ܸqndV}vgQF:/ׅQ#pTis٘6M[h$UZ57[6&hb0}8pWSmV:qшFlmLgVu->kbLю4JCƶ˺m ةGM87vS=6f0{HSPj۵;ڑF{ x ^# Z'pڈ5 @5(  H+ ^Q9W`ĉnS=<.Tp_SkTyPZ W )WkĞf7Iy+` Bh`ZTB?!!MJfnI oN?d=6%I$o4  \1L(b(s@E+ ƶ ^눗'd+YCnģl v2 @v>pԽ vQA@ ="sh@"&B@ZJGA@:RqMN@ 0mG^jvj@IDATa +;jו! pT(Y  ~%|l# pEWf~E@CO਀EGɡ@@V|Qpr@T\SaH!@(g:GtX @ =uh @J" @_I(_AC*[He?Ge, @(_@(8* 2FjQQ-br(@߯$pT+8#TR8J"pIolͶ-6eWE@|QplHQ9- JG)X FHV!@E 8榲  @ =oKj!@2ڙZ" PN_IਜZ":$ PZG  9 =(sZ@H)R@ ב48B pTQMe@@zGߖ*CQe3D@|Q9eE#u$ MQZ  W8o@ #DQQ[:! zGnd p2 DB+ E9 Pt#E' RG!m  \w %]@ l"@|  pG}@ ت @8p#S5H5'A@ _I(I%@ p):9'D 8 iP,@@CO("a p<  [@GH>jQ pVN  @|Q!@E9  JGhN*]H9!TQHb! $zG}xA@߯$p @>\GQc(*uB@",;"T "%@(RIe@W8DsR @\GN @ B0 @@ 8Jû @E( ~%#@: @EU  aߡ'pFj )GjN* @$|Q$J E:RtrN! p҆X  |QrE&@(l-By@@+  ב|(@(J@@ =72UCH 8TsR@"!"ќT(בsB6 B@H.;. 6Gakʃ _I@|> EGQlU DXw EDJQ   ߯$p @"@Ha(  @rߡ'p܇w@ 8 [P@JG- #u$5A( 8bR'@@ CO(L@ R"՜T@H~%H4'@.uB*@( C@@ =>MQZ  W8o@ #DQQ[:! zGnd p2 DB+ E9 Pt#E' RG!m  \w %]@ l"@|  pG}@ ت @8p#S5H5'A@ _I(I%@ p):9'D 8 iP,@@CO("a p<  [@GH>jQ pVN  @|Q!@E9  JGhN*]H9!TQHb! $zG}xA@߯$p @>\GQc(*uB@",;"T "%@(RIe@W8DsR @\GN @ B0 @@ 8Jû @E( ~%#@: @EU  aߡ'pFj )GjN* @$|Q$J E:RtrN! pa~:qX-zv-Ή8   rߡ'px S@@ ~% ACHyD 8`L(@@" ="s:@ OGy± L+ #:r X2@(8*2ȇ~hoi#8!pe깋ڠS͚5ˍФرc1 UW8F @;M i6̍GƴH@=Ԡ[<6tP]w徟__. eh)Gjzo_O}`[>caW@bQL@#ƙn`i 2ݐӒmH~'%>)|ζnB@ھ;A:ThHIӢaw74ibwyo>FЍ-[c=fu֍EcO@sӟ5j:tPkuSm6N/tabKPұW_}=\+T4uT\z饦Tc9&#MyO?Y R`wM[W8* Vy5F5 /t)T'pV\ P:&Lɖ38{=c=LZB(CUZhڼO9 l/MֱEۤk۬FZD4q\8}}=c{m5:UZ7iq]Yֽvk&]Zzu{7!bS[U)b;u0EZm[ؚmW& R(`ak8ض;/eA:aEҲ/^f~;ӣCKkٸ{ogb Mϯ}^]`v6jR[ּa*,Z?g^r\Ӹܩ7j+#'@J)&pTV*o(]}5?RPM#w}N1W{ITԍ k `C/Ϛ~U.Zk-O=Q7o~eZ**/2WJ r_{߂)AA櫮*f9Ȍ-١׶|ߞ]ju\{b`eCu~vs79\v9oMNzmnڧ8ձev뚎ESp6{ᒴ'>%8zvle ~m& v-Y]h6k ۿж^󯧼~wm~:avkƶۭϛBPZ>l@PN}xz[~?@(bQI9i "p)4FN2dH.{+y4; %. 香~['nFiHIlMljFNrbc@z![4zk{I;-Zȍ{_fHjD'0iHS _~4vmߞBt}M/_IZ"@9(t_4Oȃ"/#؍7E=մ3gt#곽 fB(CUJ葏&/|b-շ-Vko]Z55paa'`& #6MVS{hWv˟F٩ڪ_ԍxIF#n{6s"{싟Q֩1hب=G4Ѯzq;FAڤk`&> F)ҲӺ]6r?+uN'+`:'ޙHku;OtU/1cwu?g#)m:k m ş_?7Uzum+.ZbN4kZý?=.$vݞ}b_A} cnTTG^8aG7??@(bQ)9g vHS~nD뮻믿Y_c5;uDzhm(֢ckW}O} ݻww?7r p3; d!0{ld~tAIY4i6M}(G i:gBӚu5te{ntWTx=}F|4 iϛ70V{V˶+ y(DLQ @F= l]s5ie]lΜ9nA6+ ehJ i wэѾ;?]h{-\,(>?|LlLu^cӚSlUV.pA`u^# un]:d*!tCu_Ά i/~wv[Ɲ۸׉Éve0Jy܎Qϟ,ptcLu(S7M-班rX/Laǎ =`ںOy^>q'SoCLɶAJSuP';z0ڀ;%l^# P0s2hfͪNMl뭷W^ƍ'le]chm۶RaÆUW]ӓguVeܸqӵh=qӘ>hꔿktԳgOkذnuuMQwlִiSwSO/zv7Pf*m9&pTD@(F?؞|I[xeQa|` Pr_ϥi8 $typo),Zj8 -^{mw| :~(,PE# )S=6>A/MivQNн7xckSbiϖS]R;ɔvMSi=7\ML/#y4}q_RXꚠ.>N%̱ۻvizm{H;;m]*~" P205'.c)4",f^ߍfXSGˤIO4tE7t.qQUu뺰TzM˗^zR yҥKo=zؔ)SEn`foFouNA%=Wa/ZjR˖-MswGao!ʇc=fbH;gy)ϢJH r( N볈zy.pB Uy睱Q@'t뮱Mj8ғغ)Z0g=}Q{vK:Jde+ IQL(kGe|ُ0&y{FB-Q#p]NFM!Hsp[ond]j8d}(;}.lܓϵnѭN(>ptٮZ::L6Evl6XӺ]h7v ,M9FrKh(n'xsqntGo\9R-m-#9-١x2I+Ha$ʉ`ʶOig#Drw2@J-&pT(P#sϹ|4;lV((@{;CM:=uQׯow ?|TdFo:]_v7m}oX*P|7h{… EhU}4el۷o4}.5*b @ L6qӵE#)pݹNOH5j*B/'N7|xV\q*L)T)٢FeرQ_4pђ4N| a0Qf3f " 3O=߯$pVl GQiI h &>贻/6_WmøQV`>t4ѝLUZ5SSE-wAuKKMG>4i*0M nڧl~0Rϙn`JlTkm[b;Qͱ#cGY'z% ϚFxMl:<7PhH!M6,ŏ7TuaTk/Tv/ӧ咑L^]M5r4sg>`z[~4[us{m~" PR06'/SW]S {ӔaC_]!/v7 45܅^F8X`{>ȍ&J6Fұzꩧw8҈D)~ Q{6>}įr*h5=є۫Tf8* @h4@>3}cLUԲW\q ,i 宻2O# ObN9gK.z:}MGeI#)kogύ?XΝM*W߯$pTn-Gy@cQfFb=zTi[q-hHSu2WRh_?vsZNb!{m8>LݫvO";xlHUNɎ-sCvMN!A‹&#i^76> Eu/Rݙ߳? MSM-#lvTT:<.mϟ8:CeSvFm_;wc#iD#-~6p=kl|p=ִA=Vm_={ @Jj;p$=~W;<*PnA׮]ݓ|vaԚ$# 6J\y( Gz_#/*K juzpAh4NIxMTԠ4z@9/ʹ(; nM]zBy. \~.lF6̴_13!NrM (:jE{L)N=TYچ)QӸ󖦪jJK.s]{i>Sŏz+ Y( DTQDj!@J; <묳Rn F?H8b8Zd;iNyzZֶy#kٸ :OG#}kg?Rm*t5 =7GSq.ְ_';f|7bq&?|.pG޲:/g(i9unj4}}Ov_Tf[T,#: iw~*pNBbm&mOsEM5FHW FCbA@ da02 fBf͚e@S~4ݸ+r-QHdi 曭GՊtM+>>pЖ[nYe38i,V,'|bs74Z1\U ^K8%H T@]kuהdÇ۵^F r+TES<G#0իJRG.uUWvsܹsmҤI.B /xom5 BPStlJPND^Q䛘 "@FW6K/4am_{o`㏷w߽2QFŦI0Ytc[tO4-XboQ6L)ò]j8z'|k*dS;K/䞌ד>kF&9֔nC+ؓjɞ 3MqQtc-4[55p)SU7NFrfmIl"e)kB1'tǏ;i X~w]YgSev1Ǹ(xL4… JAf-ѩS';nK,MPFzDVFdJG5 S@E}k!T:alѢEv9XSm eh%ljN>Teb~;6jkfkHXGӒbӼa"~:wp27Fv๏1_&A4󊍛:l-ү{|:g&tjs9G;9hJ+Ď.GSfγ=>`um5qH#կ=zjӱ%/)t_ ʰmZL8"կntZj]hLJvC@aGT\Q5p4gӰ!}5X.]b )M6ĮغT/4 ޏ_to}+}Ɯ:׷.6xj2= Wy,^ؽֈB/N{ĕi5y˩m۶B<F4^fLSZK{Z^!a_4 }Uwn >(LQg]ve˖Ut`ʔ)O**A9{qӓtNI^fHdT͏8[B 5i~$-[_Iਤ@BUHCSM f͚vn͍(ڵkW`deQƊz/)hf;93ۉ#GiT=bZ7mxk#40/0֫tt[gwX0Z&ϭΎu Wz~ )p5w/~mό*`dK/X̍@5=m:ڥniSh럶u|jkUrӣϾF8Z:t֦y @K#P||ɦni --~=ilܸ[QXS =^Z4:ѝwޙ(EW_}%>M[/ *Mř3g hє!~SiD .ƥ{'uۍ7Xm w4VhoQfp@FkjP5AJ q$!}VSg6}VxWiUCqӛ&[^6#>Eaؘn‡z(6ʬS ӄ lƌpS9NE缆 p>w^>>/3B~hS!RSȮ/yo_I(-E@ ЊrTjt=ܠ%}?ꨣLjZe=YOO_傧4M֋?A}zHJv^֢_Ϗήzq/ $ _v:f/\b+5od)۱G ˂)ӬI0ʏ דkn-dtEK}xѾ=:hbmS>wД}! t3`Ԧ ћ6&N\4EKd:qߺ:h_љ6-[-6QLԹ -a<{;c{.`6Ax!;Tێ7@(bQ)[sF9Mte4hythRW^qGndW\1~U *iJ5tpv)ޯxPw\ao߾vW Ko쵦(ӈL/;wveoGisԩS呂Ks-ʭ(/ \>6l{:8d ,p nJuwntma~Os`ӧOjkԤC >7N;U. }ԯ '͛۱rZ :,4ou)ѴiLSnզ(G#GtI2O߯,`K@DG" @;0c~C{=}劉 U'VfG(CD=p_{pWNB:rSxM1~VmN٦7S~B1l?b][5 Zb?]Jg^O:8Yώ-Mh/I8~ϵҢP*OA'Ogqַwv)m6#J ϘPZ⏙5oOp-bi&6'6oN݆z]Ǔ?]boBS 7n%n T1Lਤ+P@|駦P+L742:~L^2MѱcG7A6m2v1n8l={tuJCWl ,߃niڒr]kQn@ <Hl}1 ((QKi֭[\AOO:w PЈGO=|"PHZ!r]|Q Fr pTNEY@6ƎkgqTn곧c*35k.2\%v;LalM(5OY`dU-z@IDATH!i 6F=nEۿ.ogb>fZmn;_l4L#8kb Ԙ)6/:/͂T \ύߏMoMH۞(7^LOv঩#y]8J;n5;5\I/oCG2N [oLm{Q0ZՠuJM|=x {@CQyD@|Q%:uER 8*TF ;4FjՈGQXehJ e`5   ߡ'p @FG@,(.2 EGQlU DXw EDJQ   ߯$p @"@Ha(  @rߡ'p܇w@ 8 [P@JG- #u$5A( 8bR'@@ CO(L@ R"՜T@H~%H4'@.uB*@( C@@ =>MQZ  W8o@ #DQQ[:! zGnd p2 DB+ E9 Pt#E' RG!m  \w %]@ l"@|  pG}@ ت @8p#S5H5'A@ _I(I%@ p):9'D 8 iP,@@CO("a p<  [@GH>jQ pVN  @|Q!@E9  JGhN*]H9!TQHb! $zG}xA@߯$p @>\GQc(*uB@",;"T "%@(RIe@W8DsR @\GN @ B0 @@ 8Jû @E( ~%#@: @EU  aߡ'pFj )GjN* @$|Q$J E:RtrN! p҆X  |QrE&@(l-By@@+  ב|(@(J@@ =72UCH 8TsR@"!"ќT(בsB6 B@H.;. 6Gakʃ _I@|> EGQlg<(>*$EATD9 3{ g8ys8A% (Y_^;tSLǪz~_@L 4 HDu' $@W"%;i *'yʑC@ G hP-@ ha@p͇rQ @ +8 @ G2:@ %Wi @H00G8Jp'4@ Q՝4 a\p*G!%pC @ =Q4B5G# 0D8X LpɄ@I$p^M @ €(L D@8JTw@@"q%Q"F@rG9;rQ4KT.@ €(;A#@r@W"ZP@ y$D-!'p1Q@̆ @UL < !  @F +* 1 hGݽ4(4b6 @ba@pT e @4a\pTi0 Dݬz5ݎ?8~\̮wz j{VF=@zcΞ PUZjE'"02$rJSy몤1)/LJL olĉ֤Ivm˷r.y$G;jA  6i$ZjY$\"9\|r.1N dC䙾p |}ߊv`t0-F=^)6dL 2FȊyH`Ŋ6n8mȑ`{'- ;ؗ_~i׷<@kݺuS7o=裶|R쳏m۶/^lG-X»Çۧ~j{J5wֱcǨY&AHb6om 6-?pW^7|{mر1R͚5e˖뮻{akYʴ,ƕIɓ.@B Kv=}:LDK9$i*~k>*/y'^{Ul^& -4b:;$\KL3jXzl#Zt"'?Cm"r';'>9,]ı ׮\E&gEͺ@zgGaÆu'|.v 4n6dn*q#eYV4h}ёΙ3ǮjsN|BQ,@8"L6@ 椤FڣnݺFTn;^`MV*-®RL["|\uh݈UK+ [l$믦=AݦM XN6¸(T"nf B@@M1cvaӘCеQGe|pl :4b:;$\KL3j)vߡ=lud V2K'|4e]vj.lZkVN8t,b̄r@a(纆 %Kءou?=G8z/|ֿ[pq[FLu- /`OG(W]umQyܹsgF?"ED`d#/l~oR  (4b:;$\KL3j6cR:_(pV.]=GT&0QeRfI"hJ0T*N=Tꫯl}7=Iqvi~YYՓ3ό|_Tz7==$1IQJ* gT,*]G%t,@dw}_z&vm+'4bA8nJK?~2P&OlgqO9SZ=մiS5 f!r(ӫ"0͝;3mܸq:K.Oc9RW"Ev1!dL@Bڵ3dMu"e!Ѓzࠃ*1_l#G}N8 (Mp4fR]ߠխV1Jm je+l ױZkU~5{}|?qݚvVf}Yլn5m[hljGPO̵Zn[os~ ?dMVYӆnZ&DI> f]'ץy:kꕮrYcOԷ5~ nް/{vuRSriĢui2IffE)u/?-u7̶h][;Zݍ{Ņ+cC'.،Dg]{'O<:rlQqӑ۴9C}m&.y}tH9r~;QL|$O\wWS}CѪxޝ .~unNklkBH +b_*;nfܩ@ \"ϼ"H{x`Ok\fU:mG?NQ:,*Z2w6^hW:ܢʱn,g:T<Ĕyq<긁ӧGĥnZE};=g2gQeE1!j[ _F$\9$Ө%lNΪE*wIߩw䅠uS?澯w}pXR&~F8*̅O( L|tCUVE˔)Sc\I7Jo?|Jnᨬ2_(sv @?~}~~H)Ǝk{챇r)$E%Rt$pUԈEB#GJ9te-Ç7sOS4&ER-[lbĈs_bDsMW"p'Q5@ $)_(-ᩧB8 @xO DjuˢZ=`hvkl-bpA8RAN*QEDu/}"<1j(9۷7#D3 2u{uOα"#6EAҍE"dEQDEi2o;QIQdTuEI-bDI'\ \49.37sI ?EK:$MIzեEdD$iJT$%i~ѢuQFIz o*K8 Ag?Uc0~oU ت \[g5oL:H)$GIT$E(^^L WGNz;/v.ړ.FEJ!? %!=G}gv'Y͛7oOѯ_e*s+b7pO-iΖpj*EEjժe&Lt3yf۴ic` O{jذa>"9Ӭo߾QqPBCQ؎xba^WF1C# 8@8SoQW@ [oe;찃]tEnc3g5n(Mp4إRD#`7zICqRԒ*R@ѫ}rvՐ/}4Nإbd%dUrQHQUnwQGRKqtM:?@ @eK$}u]URl G7:YNSf H&TKG+͓=v<.f=G?I9~_[}^^-Mҕui.} /=>hGEhHR:$I?]4ct^iՊI,~5Ks>E˨ۙ.ʋ)V8 GJɈQf'F>Ie|ޅ҄#o9M."-e쾝[.wNQT{"'tn@Gg@&‘nzM<8p`$ \E7ƍgy'S%Xo ^l4}tiz'|(#T6۬AoQƍmѢE^@R8pTе ,P0=7x)onI?3lҤI~;ZΝM?ܾ+ZI)JfyK qҒ,u$M)zѓO>YtD75GyħjS# GRrR 0P#WJ\$Q{AQ.b9r)*"3<t}YK[6!|vcKpR7E=SJ]Ě]5A8R.E@*&j{ME>ꎖpT4 $ (yG<Ǘ\tF.K(Rn^Ôya~h wM\P$HiRY=stbr.}Quu4$EˮDv<:o6Jv6)J1w~ݝ2ܧ*)ZEwMY![NQUr)!H.إSIղC#&vBnB)3 yw-QP9!O'mѢgOiJS=E S԰7H$T$⸨(jN.]( G%1PRSNT*\7t /!? #KB D8RE U4Oc-{1D-RiBF_SS-iJ"(_Hu;a?S/]tc~v衇jUE@7to?I˗//fjX6^rG xl>Q֭[ڨ?CZ'm멧zO mw :| Rkԝ,[dr7T-2uv{Rz)YѢȈiI;}YQRСC}Z7mKLڶKRj_?-˕a\p+=B= $@8Jr6@ 駟!ir䤝vG/i\pgA8R ZITJb$")ӍnhU.O~+ۅ.M@)T(IIJ*SbG>}!TLa uHf*E),W؀nqRǖN7w\jm7Z׮siJ+6~qʦp DVA҃1.%%)Jګ1%'Y;g[?\SںtXTcwh[v>cSdk)ʖD3 gQE]]*QY܄ rdQ4o}iOS\Y=QSTjkaܥViQzB#~F8*79G_z%/}iEO|>r"E1={T!UYån*"d[8ZguYFO?D*.Qԣ%Ko_G9-T @, H曽hR7aTõ]tɮE*G}7!w}H=!%W] }0 vKR,^#(y,BSuKrbGO'xT]4U6^=҈TJN|/q;ϟO#[ReߢIH"FTyF̒D%*Ttߊ"Z~NJh$H+ki״InH Ǧi)VI%GEUJpt;n^?=LMk G>UꆋrKQAi*U(GE6G@GȪyK +0`c=ώdgJz,:r LMGpt饗Z޽˽l GV E7㴟Ӵn ~fajJ[la͛,'W#jP/@"PT6ҏ}J#*LiٔTMeI .Tu؞r)~S^jҤyVnBHr}d~B(nHVWIKժU)GR>Ɖ?a=z\sR{¸(A#p>E@߷>}ع[º8 JpD8J:#E9OM I)]Gl<{择>meoi}ڮ_Xvmmo߶nk{fT/rTq4qT_-HZ4=In?|(?sB RG4f G!UiG_)5q] I JObXTW"BIHi?]N]Z͵EGgw>?Fh Rg$իQ2E'l7痒zQzln8)dK8ztwvǓL{aKyT%ԞA]6F."aB˅1M ̅Nn}1sK߬PJgHCկV)}}%׾=^u)UM(@v ˕L#| ]__ ŋMQtPnyp T`"(R)mYZׯ"QΝm A8߿ۥ(QA8R4=$r馟"U%ҘwQy< %s 7%hE6:SMl%Nkۺlm?lG|!P(ZB=*\s 6g!1j 6)cݣ)qMjPFPTF1ӲN RƖƕG91TH$Dv+J!t=6JYԏ ̙c'xesqQ^H/YnMt)Զ,R6ҲA(pO&] vSS J4o* Gth闋OmwvzZRGiiNlĆ~.Յ% :ҥ+2qV#}9kIdRye%BR_g>1 =$mۥj[RTtB$)iz\Ry(͚""uQŤr=Q. u Aw yȊFԵm/SޖBi fΜi /L뮻Δ,AUtED@zBc9ϟo]tQa7t 2ĺu2U=IR*Jeeȑ6eͥ:>lδl2{Mu]=3_UƕGUI}AJ(_{vC  4JQ=AdƏog}ڴif(MWpt٤9?=loj)e_9`+۱u7o҄mZ5]mObk~⮅vU(M8 њX˞;f'^ܹ@IDAT a~V6Si{DMS!ExQD\t2F[5͛?&*o!$D1F~ݭKhQIAgOcN.%]TPJ0)jL+뱙 (D:{;{tT;J)2Ic_߁5*kJ5sw*1SHmڦĹNP 41aXʚV-l[:~39csߗ;FN|c&M{lE)@ML#A'FٽkyVW_9jz%?`&nVMT;hJ9 /X:uʳzq>?4E =Í7 PN:C*#h,GvHG*J e…c-k[o6ؠ䇲' 7S%jgi'NN:M>7|zG)Q 7/s9t-o{ϧ8JZJ蕒$T(oSISLŢ]¸*h@ @!4VPDE\`z|Ip \&'"|H9H9Jq;(Δ/WX9E&:E(*ZT#$&)E"0rْV-Z*u&)Jd :g;;˟}QrM(ۊUHSFjP%J"̽6MN^;M΀-]:sՐ/ͯgZFuCwիӛ'c#'[-'Q` `#AEDr{/YQ*BnUiR^9W{oVQtCY6^iN- \Q='\]+L#ΓԦ(VfU*iݳ]䢽S"I:ÉsJszoC Vp;!JB[6?w -x.Z.*Sb>NT2!a$@f̘oDit$*B*zFrtHOք M+cƌUO7Ӕ<(RuhJ_ne-IK. EipԺq"6ʱ~饗u=cM&'B'SC-t\ "F@ ,X>(͹NЍ~E7z^"U(M8ҾآSGС&"'ÿq72->Q~{8iJLjY!ahk",ַ] RI;kk0ٿ*uR5w>Rdy p7*om_OkZ.Ґ9mM:TW\(tEdRP΃NdRѱ%i^H&1D)tG1W3r`h*Ku Ea[ob֫feZc3ѳ_L[ޟyLKtN+=L i|<‘3{N E2UCcY.qvi~_K[go*= }I-~٧o|TS&(k(rS= Pva3c%gu4 ѢE%պzo;`S`?p3`b7dtsnw6=_"J;$ 7oa>R^I"W6#qV.QT G +Q  tn=sj]1uYE:G^IZ?o߾?K`R%5kf|ׯΘRG$Xľw)F'"!4箏&B'HiոP2e/^"6I2EKYt‘")I?FѦNRĔiMꤨ8r+pv'PH*Z$V/y,O8{R}ޒe<&uk횷z[Ljb~nM?\oKiGܥV*$cAJ٩=7A~iFnyC \ti 1 VvneKUp4YvTT$H\RNKԇGEBT3'S?滺K4Qyܥj6~B̆p$)}G"/\w+}nuJ&LNMm \T. GFɔ&9Q"Q Jґ '֥SN82 q%,)R*?]NTn~5}}߇;k(0Q4E`ܹ6zhCOתp+*T)ǹntuGi*nJRͼ})U?(*L A@"M?7nF͛73(ɩ,ےDFJ5eEӽe%KRt%b@U ٨~E[/Kj0D8*gGM@_*⪮UUµ,TvE:kA8Js IP* Eh$.xv͓xrΝ E)p%nh?8 Eؠ=L*M8E Y ׿3Gh"iD7rF$7DJ#m[Oǂ8#)E>N~8 G}4eNTAtL$$J$NH}KⅢVݬyA}h v r펍HoEyE©WԣX$;l7*‘|6}vK~i״*61FIhکcG[nd/arFI‘{εO\;9idȻ̶G|-"Euu4WRc3‘c\͋cHA.m'5H8ҶFTQ(T4cY]& | oNS ‰^GS}5(ZH+oݭ|m$q0㤽[Mǘ5 PJ㦿QE 1L"/* #Ȝ@G8ʜ!kBJGUI}A Pa\pTZ,@b*Ə!x|tT=h.)Bi QKp$^'\nRq)6_a B$䤧?7ᵬoUH"H@P *.4 7MA:-R$E9QIGsQv~tmEnRPx~n R8 ˔-j_b%-2EYX]O_WQ ΫhA;aJQ$+o Vf6qOjt:gmw}׸N ?f8aK)Uǵ`"$E؜|d(dN}8FJQR8['mZ\7Du.}+XoT_IB#%۩;ᨴccU@GVB P"0D8*3 @GJ,@ "75jXEFn|5ND8J?‘-/g> Ni8B *z{ptKm&U9gUD8ʙFP&A8ص}T v 0G8ʥ^.J&pT2@ zq%^!ĝ瑸 E( $ GJK5=(3!_.cvLCrf#UW]C'Ͷ.RyJ[d<Gq9 #Gǘ-C&ld[ d@W"e&ۀ 8_b@ Q4IhwHնh݂v}Ug>}3gоKTg*N*H`Jy6Q}j/"KON@#KBXV'} @@0D84@HGH:ḂQNp_ҠVu;G;۱M3_"iޒw?N- حlkMLCrf#UW]̢rF8uVzV$![C8JHG d@#e* P*. @ #a\p>V 8!@ I\e{e>D-'=T[c [ʂikw[mۧ fT* rr@#XP@%@8* !@Xm¸hu; kGb}T",Œ&Lӯ6E5Z>Q:5`E_iѨjoA~qQ-sW{R]$?,~.Q۴)*,ltnen1\ X#q0@x 0D8ʧ^GHX%@  0G8ʻ@L Ŵ6 @ ¸(L T"#MC"p @a@pı@ O O¸(zB#cɖ x@8wQ{@ w€(ﺞC1%pӎ Jw2M P8T"\6 ĊQB = x@8G?QK@@>J|u @ {8d%[M(G!@@zz ĔQL;jCL +4 @%J|u @ {8d%[M(G!@@zz ĔQL;jCL +4 @%J|u @ {8d%[M(G!@@zz ĔQL;jCL +4 @%J|u @ {8d%[M(G!@@zz ĔQL;jCL +4 @%Z(ӦM#bzJ\ @ @ @ @@QtX8xm @()S6mڔ 3 @ w|2;}BM @N +>#CȌ̸ <!$QA DzDv/H(J @1'ƕG1H@`5<[@  \P!@ =Qi@ w N_P@$ƕG #Pc@ ث  $@#%i (GN@H0D8JDw@UNH#g@@8юZ @€(S!\@@W"q,@@&8dBu $@8Jb&@ `a@pNi@ %;i  @ ¸(I# T9#UB9J(G;jA Dzh>L krG @a\pı@< 5ցH(J @@ =Q;A"p1 DJDt' P8T9rv(@ M 0@@8ʵ> q% dBH&XH"$*m @ &G d$Q@A +ѝ4@L krG @a\pı@< 5ցH(J @@ =Q;A"p1 DJDt' P8T9rv(@ M 0@@8ʵ> q% dBH&XH"$*m @ &G d$Q@A +ѝ4@L krG @a\pı@< 5ցH(J @@ =Q;A"p1 DJDt' P8T9rv(@ M 0@@8ʵ> q% dBH&XH"$*m @ &G d$Q@A +ѝ4@4b6 @ba@pT e @4a\pTi0 DYFm @`? #5,X:([pwy֠ABuL޽{'v*>c-]wץk׮"&C=N>q}ك>#4ߖ2d~7*q(bi@(LxZje;wрϛ7?Kz{A2gKoOWsI'MTD8RE5".ER*JKƕGqA *EHK|.+yJ7ՉpF p '%;0;d>u=ǂpM8=cMfjXZ4cukOGeuA?|<[lmӪժ^;s>~^֩[ӎ޶_[icg-Y?/n[m֮S&FXwV~n?T~Y&^hWj6_5_;V5JYrۖҲQ]ե~Dnp7s~kݤulZ]p mhXZaQu O'BxmNk۬mnbYdKD)uo쏇5]7ifz-Zʳo~~Mj8SEƅҝ-v}cT%xckSS Xi/|1nzKY;wCiH_nڮkճ`rBw\-IkmuwS%|/kWhC2i8S.Y{`Zh+1XTwcz&j*!< ~F8ʃΦ9A@7^}U۷BhH/N+G}d\pOoG,3f^:DQvgmD83d   G:9Zrk;gu^g˼k>%wikS}M0GvӧNSIL##]zH2H=PyS-/q%Q{:CL@Ҫ=zΫCr M<&%\nf׸@E׃!ȅst"QuCP$~=2IG*G>}9m.~y 𽟧kP^>i3b|2zI(Z$S}HIiيUY{N>ϊmGR`c{dӭպvůw&,ZkXԳeb„?j𩗞´FM}P+_elޥ@<ԥk;ZWI8{3˲bԶVXX7FLm|j.N +.nu;侷g'֭_aL]{3v/-c{;fv~2o*=aBOdLrcp.~$3t .%6ZhN dWM12o5ltg6׽\6=]%VS>侟g>lݸvv3Is3c1}:fuV[s {~m$@a(Krk=OqWΗ# 6ؠ2U1Aaov?Wn]{^F:th">%¾ƣS*N  E‘iIъ\Q#i_v}w/0G-i <_7p [g?qh>)eE?f͚埰_LWbtv|'~Jcq-a\p@.tR(sp $"vm>DT? ,[um;pA8Z7;޷|-ֱZ5g?أMhnٹY$D3 ‘Ezvdk"lᄤNRwP@Zl=ql"(L3'%[̆8`pr.Ij ‘}:K'ݝأ:"6&:Jm߷p>DMOMwGҋĴm֭_f)H' JrtM 46qUeG/|1Ů} /trQݸ0&^7G1;$?8ԧg.ZNI;.#w*'}<餚kE쎙M\d%pr]TF~4#mJ&k"[q\M*WNr:qa}qv-lCj;F]NLߟl0MR,ߊ>cs ~* wQ.?:̖9ɰ㴩^mi'eYjm_z%TfWEg̘aGu>@"z7Lm*Zreܹ[󟢳  H‘$-S|u?_{еn8l&v)n@֟˺ Rhp$y\O_JxNZJOzԺuV5' J*$Q:@i }.]M7T{キO7K=pA8fh{nJ$Q&+K8hpruNT$@'=JvA?#R]FnjMv$Q $~F8UO@.b{~uٔrCQ4^j{ֿ*ҟ1Fӣ)Ғns1%9sr!_~)HjQdIzgM]{16lgSV-ꫯLnݤk=о__R͛7իoF BziΜ9eU:uCUԧDKPD8ʡΠ*@‘"AK/-g HE٧z*yg}֫W/^fܯٳgϴUp"(HTtCXohi_{챇[oH?N[_ +r GF5\czQ rGO8SѸ4= R͙}:6-FD2ˎ.n+R is K],_j]Rlw";gYhTHΔvԅuQHQg+uV!x'Dw]\4\ ISŐ+fNqO*YuiQJ}r5^ 'LRZkyIJ-uqTvO(HN5E9[u ;hO,Utckۋ.=Zu'+U-)RIJ{ָnbUrP>og"%Hl]Ϻ}F"4 T%UA8}w=fV;"NΗ/&6j?h k/13&B'~F8}WҀybnذ]q'vHJ@'."[]V:,/*z~}@*yv":hž|I,):wa;v)YE7Njv?Zl7TpcO3|M4i:… Ըqa,uʕer' d(pVs96rH_ RJ(?U$Iѣtd%)3ϴ}٧` GuSDQT$Si_K 1%+ޓ8uI@1> ,mJהbCwpi>03+)ZE{EY錝:mnu Z8:s|$KoQULj#]zS{u,6+ړS}g;qOKI7FL+K8hS#I}J*?j梜Ezyq  G{ 'a7pOCRtk;\tV~V3̢HG$h@_E :mҥ^֭լYoCy^{m/l6emDnm۶Rh&bF VxkРAAEPla vHS;n(n=F}!6L#I>.8KyYR__}q$7E^Z޽{d=4:c KTT8R:ԲڦSzue]^Qu_zIv ukW"ťǨ' gGq=dB@ъ&Nl ïtwJ7-`b׏݁X"""HJ(ݍt7go6\9f=F?8ثW0QPۤfR8 ! u@IDATݩ҆-Sr?Azw(Qd /A5?*@@T~OTP.(dI * JP@R]Q gQ#EQ,9uVZ?VORH,*5\vo+JMȚnHvV7^D: (`쯏() J*r%}Adeov}<|\: 7yHJp~Я qЅz?3fc4U)R"-H)b(_l ^_{{hoD-Xn2?%dl#B-}hNJWj]СC:2#ҸB.j*YX1P|7r ~_Zֺ["-Yf\Iȩ=z #7&B$ 1hyw//4wpJOh*ҽViU9H  KJ_SKSєhJ( QReznHH8zV60^zk_2ghG]b5=۶{M+۠RE3&L(cm#euH3BO:%A: RiR٘LeO$@G8B-T8rVxᚚҮR@UʼE[ʽ\ZOT}Hh?%8RP3["9[Ҩƫ(k9-i!;pJ;(0 I[zi[ovOFAJݻ.cj*AZ<9߼ysS~޺L$N^ pҡa_h#! Sp"y W82E P]bŊҢE 3;n^)MW$@$)\:8WlR}ِ ,XFJ*3"+&doDʓ'&;&'GOw:דƥ b '`. S8J͏<)SOaܦMϾ!ټ[9o;6+ 8P`3Z~}y7G$|!75iD(J/4sPٓlnR`@? $!bS%#X1ctMn..)~H\G nj ʡ dk=wע @۶mA yV{~()!+F'3ڲe?^GJ&?  IS1J Gk+ @p=z DJc]F.˜E/._|EQ8 ?OITZhW*T@cfnWNz7d*̑Sg@R-}lQR)gǪ65, Qz:)n^Yz.拉Mc$RU#O*(7H{6R&_h7ULơSN{9#  ZoU)c4hEyTDPJ#%:{^V2]Jʱ+&!lԳ#=YKVeWaiq鉫4S\˘^mD9#Wu7L0-H?TZŻTZżY3ʳШ9 0l.z @=DAA0>x}…c=4 "o@&Ol>JL\rzF*U*ytς>o Eܾ}sQ,pzNeʔ\.Dܽ{,X@G_ٳGic?R8rlװb$@$BlҤI](xh/SҧOEDڱ+u@)^7 ͛=! Zƌ=D bQjժzX mJ駟t(2dп}݈7 ÇjM{ +W,؄4oe̲N5J GN)֏H ( p ȑ#B ҿσf;s=%6CX(DA~ lV]~餀\.v[B$ \,S8'__VU:%@̞I^o_G*]~B bLUQ%@8=r^a4ӂJXcM~f DK8¶h2n-`. S\aZ+l*h=٪^ l>IAd*;ׂY2M |dmz~1M3^yqxw?*h-D w߾;@w,vhGf>U(S n86bQi 1~.HC`~J׭ViMĥ~#jY+=ѠTߥuw5jF02 AiQS6 Y.;W(%O!OT G NJ$Op Z@vK $@$ x8 @|pZ @"0J Gl+ "@R~I@`ݺu:(4@ZeHv}!PH@(9 G[-g2yh@ե9u_,nj^Ir dGOj'$_ *5Zf-橳ѻbwkIF[T$DA4HG ߪMC-VтR]u"kڸ@<{NGWKH_g /6"}#d"*B5w(GRZTgc{(KT"o#`UIG(6-C:F32K"W]Ҹ3bޏ?T"̶b~Թ*Y愨U,$@$`   P8rvv$@$@$̸Q">L$@'Hr $h%;ҥK#CdPQJn(YZ|cc*&Rv}c@)#v3*}x}["r6u#ŬQ\G8WY |N L _RWiﱓ2}.~FvʑY>Df\̦p2\mN9f$l#mɘ6l;tLޡls*i%OjnH̀‘+ HD  3f\I(:% y!j \rtۅ?Ηv{HTɱg=.Sj'ϴ#όpcܝc )ç_;x["VOҤJ5HH̀ ⣟XK  H$f\I(zm% y$z,% &@(/j_렌_Uv>.{+y2g_᜙R$g/ A~jJy"TL{FEZ\[;s';3c}}[sɒBdxl+dֽr9w&,_X # 0z Gvt8HG‘5"  D'`ƕH`IH 2aHHH q%D?~ #qZ$@#@}} =#Ww3G$"\ԙl WR8rI$@$c<8wG$X5 3pdGHHy(9OX#  Htf\I(яH"#Hdܸ P8r_E$@$@$@$jf@OƑ #u&B$@$@.!`ƕ\ҡl Ę#1ݑ 8#v +F$@$@$@$`G )4 p GֈHHWR8J#' <7E$>קl S8ru7q$@."@Eɦ Kq%#t(A$@1&Hsw$@%@ȱ]Ê 0z Gvt8HG‘5"  D'`ƕH`IH 2aHHH q%D?~ #qZ$@#@}} =#Ww3G$"\ԙl WR8rI$@$c<8wG$X5 3pdGHHy(9OX#  Htf\I(яH"#Hdܸ P8r_E$@$@$@$jf@OƑ #u&B$@$@.!`ƕ\ҡl Ę#1ݑ 8#v +F$@$@$@$`G )4 p GֈHHWR8J#' <7E$>קl S8ru7q$@."@Eɦ Kq%#t(A$@1&Hsw$@%@ȱ]Ê 0z Gvt8HG‘5"  D'`ƕH`IH 2aHHH q%D?~ #qZ$@#@}} =#Ww3G$"\ԙl WR8rI$@$c<8wG$X5 3pdGHHy(9OX#  Htf\I(яH"#Hdܸ P8r_E$@$@$@$jf@OƑ #u&B$@$@.!`ƕ\ҡl Ę#1ݑ 8#v +F$@$@$@$`G )4 p GֈHHWR8J#' <7E$>קl S8ru7q$@."@Eɦ Kq%#t(A$@1&Hsw$@%@ȱ]Ê 0z Gvt8HG‘5"  D'`ƕH`IH 2aHHH q%D?~ #qZ$@#@}} =#Ww3G$"\ԙl WR8rI$@$c<8wG$X5 3pdGHHy(9OX#  Htf\I(яH"#Hdܸ P8r_E$@$@$@$jf@OƑ #u&B$@$@.!`ƕ\ҡl Ę#1ݑ 8@̅#ǒ`HHHHHHHHHHHHHHHHHȘ1^ɓ|e[l7Bfi|O$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$_`d| 9X+" (lۜO$@$@$@$@~ ŋ g 8͛uex=9}š @0J>I#' <7E$>֍p侾eHHHH̀‘+"p! G.T6HH✀WR8dIHynIG‘㺄"   D )y$@$ f\IG @$x!p# GnUHHH\L )4 WpdcHHH̸‘+  y$ȹC pЎaHHHH =#{>J$@N#@i= q%# $@$@y$j\H(W&   p13pNfH\E‘!  W0J GN6HbN瑘#IJ‘C;"   '`p* 8#C$@$@$`ƕx, DBHq 7p^eHHHH̀‘;M#p GN6HH\A+); 9Gb;$p( GVHHHHS8é$@$4# WR8@$@$ G"uHH‘{m"   0z G.d6HU(;  p3pd#HH x9rH(9cX-    {f@OȞ P8rZ>$@$@$@f\I @$x!p# GnUHHH\L )4 WpdcHHH̸‘+  y$ȹC pЎaHHHH =#{>J$@N#@i= q%# $@$@y$j\H(W&   p13pNfH\E‘!  W0J GN6HbN瑘#IJ‘C;"   '`p* 8#C$@$@$`ƕx, DBHq 7p^eHHHH̀‘;M#p GN6HH\A+); 9Gb;$p( GVHHHHS8é$@$4# WR8@$@$ G"uHH‘{m"   0z G.d6HU(;  p3pd#HH x9rH(9cX-    {f@OȞ P8rZ>$@$@$@f\I @$x!p# GnUHHH\L )4 WpdcHHH̸‘+  y$ȹC pЎaHHHH =#{>J$@N#@i= q%# $@$@y$j\H(W&   p13pNfH\E‘!  W0J GN6HbN瑘#IJ‘C;"   '`p* 8#C$@$@$`ƕx, DBHq 7p^eHHHH̀‘;M#p GN6HH\A+); 9Gb;$p( GVHHHHS8é$@$4# WR8@$@$ G"uHH‘{m"   0z G.d6HU(;  p3pd#HH x9rH(9cX-    {f@OȞ P8rZ>$@$@$@f\I @$x!p# GnUHHH\L )4 WpdcHHH̸‘+  y$ȹC pЎaHHHH =#{>J$@N#@i= q%# $@$@y$j\H(W&   p13pNfH\E‘!  W0J GN6HbN瑘#IJQ9 Kfvc#HHHN )9X= # $@$@$@N#`ƕ3 G⣟XK '@(c GAq6 ĘS81xH"$@(Bp\HHH q%C Ά0&NHS8JgGӧOK ./'NzJu7W(Ο?/SeQ. G@*$@$@ EMhl#q%DuHRXnYF#uֽTՈ~y*Nn\M׮]+W q bŊ~R8 rR8 (NgG[Ih٫? z\UU%_ (pѓ?ekשJ1ݰOfv g DS8"Tn$0sLYl$_^ Gr/Bf"_|ժU2g=hܴiJ~Zj7w^>|,YDwB R^=}/p&t6HR={y{ҥʍ7(^{mH{eخKN6m**>b3~[2f(͚5;Ӯ]>޽[J.-+`܁ gqn{$ 3f|r9=N7\:G\эl 8gʛo_ nKڷo;+>S8 ]ђ|hi^L7(TJ҈z"eIFG٠mwD󯚖VoVQ-&pDC8uyJ*:WѷD\R&o5, Gg- 0z G|8R"[2}t4i's)ʕ?&dЈ#?DXB9r䐢E]w%n߿_z!ٱc^э vɣ>JHÑ[! D%Ȁd^:t/{MlgUV}\&%fKZ¶K,~~'4lP0GqmMiP"fn-ώ[++Sz-,۶rWw~[!Ξ iS777XXpd-H$̅a G)$pg7 tMZl)ٳgVg?~\^yAD3ҥmV)0;dӦMRLyǥXb2w\}Oa~Ϟ=C NQpF\HH?HƈJs}ʕudCf G8e۶mҢE ɖ-ZШQ#-I,ZH{9iqʕ GQ0>6P(+3p  p q;^qv2aF8 $'kƍ>H?K:u3j(0%&4H_Z N>P8 Qɕ|uf p |J)/ g]heF:ele!ыn>Sv:۩̘βoyrYbaӢr҅Rh ͒ $!`. S8JH @:Oe]q?Q2e$T^=vO<2d,x1ѣw "8$3HH @hhӦMH8㏥D2/F$2eymKpz!,$,̵+'OOcX3p=z 8ŋ^+ʗ//Ɏ"3‘S{"H Hy%zlwpcǎr=.P Gvɮ#'%X f$%^-ÅA;Q%7,[2IuHo߯ʜ^H:?ėj_{i Z\ÏT1o^-ט(/h֨=H+vFEArJɛ%޵|<#+U=ryLRUǂ:Q]D*;T(] ,Py̶/ϞQϳnϮ.ޯ{DffI(f^%<ҴtRү;T6?&ӤZEx0wuAllCA?sDGc*)Ϛwϡ[JİҕJ iɤ/x*&c69Ѩ?6ˇ3WZPKS#ms ү}ֽK;tެ bV %[iX9T: 爵oQuvP d9D+;/kO8|[KKS_@7?*+ߙ=GOImj? saQ\t+&Mp믿ܹ#.tsw)rFD(p n!{֭'ZECzƍ{P8" '<ь >}-YFyiҤ^QQ"hrTM6J5y;nWXѾ`WR8bI={tzWøh+YY d{Wo>Q6kxpQXw=.uqPvVϐn 4e2u.=ۚz>oWEeɷ@Ni[IZ} r3WV:T.d𷒥T4fyR_rM oys_ҥjqyf~5uX0 Zk^/zOYSaTO dѶZ^|d Z w|[3yZ (g#/&-^|Km@IDAT:ܺOnfYPw:֖_ԛ}Un\*u^!fuOZLnRk9f#]4wh]1rYiYryuUcF%e[ds|J4߽-IwIW|Ǒo:ϥO8B-^lS5 Gľc1FSlYYR qd}f+z5,_^:^ KF\ptɺ;N0tM7 ."ҍ] hRR%Ap "܈+ro3gJf9|g5HHppt #uia G@FHVr#xkETI+\C.\(8wYRJoo̸QK$p/oQ[pi yR "@"4i_t-@m֫:ڰ[+x*QPэfe Km63 * *ʸ{ZJVK$h GMS&ޡDE#"@@ti1#,zl=tRu i:eӓsQۛz_k>TmK4)Di;hƣ*JқIg'{A6y?$X,Sx2@Dڰ﨎P[`R5CG}=}^B1) vבZCdt*Q훶pVUuڸ^W*w69tPQoLy 7E PB0`@q^( D 9ҏ9rDK:THC, XV-?&NSvUnv] G?.]sO\gΜY4h1213=c" D @(zm$@ID@(PJQq-Z։(Dmkb76L+%WMT i :# :)[#+嬯y WlfĦF͗*Ĕ>Q]A'LAfVQ[6I"&! Զ): Qbw-Sly1'(KvZv0`>J#AHI+7oE)3/Zќ{_cv5dW{R1tߠd>lO`'H3dm*0lMfRRI`_/FǬep#dP oz )2Ԉm嵔9K"Uj5'|NEAJgF,(UUt$U:U?WBp>kRr Jhk$m@DJOn7pߌLiH/N-QScY =xRγloIJ\ j@Fw7ߔ>H{"Ee˖r7V޽{}w2rH7oڵKͫo!Tq֬YLncܸq)S&3o~O`ZΟ?/g֩E2d ݻ>.r}3ھ}lٲEۧo-[V+B޽{H#*N֧D8=#'F$@G:HګWpw˶m8Z jDA4iXg+Wc=&5jԐ^{M#\чLI0i+!ÕW^I0w\dԪՅkKHK ]v)&5J G} $* Gl7 $.<0ydiذ<3Ay睲c&B(H%pdJH Vi0G/#aZgTYa}h8HUk G B]UEQB"Dbk]Ep]YF+5|"<;n8Ip` E=rUH]eW[+nг)RQ:UoSiSE =y%Z4#_ӑgIE% !/{xK][ծ79eURI|F-AXORF P @ @`J)_FEB',c^/ k+'W2^a P섣T &%77J`-Ut3|oq켬R5ULrpMu?3j_u9'_0C<H\u]*U_~)_]"s(S>(qPD++&Zn&b{v,/gϞO?TGF M^Ϳ[ӧM,YhI Q]GHHURȋu{r%0 GG ? $@$C=$k׮/R ݋/+VԫW/ө< 7nԩ̘&\hРA2zh G_}@8`Ժuk-K;VE/q%8DVH nP8bEID瞓EI6mc@i׬Y#:u<(oQKI:+?R^ٕkaDbB/Ju@WT[tH(]S‘cx%\OsW ɑ1gUe,l?t\n6SOBDD1kF+UYT>'"P S5~ @Iŀz;UʻUd*SO` JZja ѪP'JeW&)L\SuAF xj!EbsQ 89z|tZEp#U"e#ߢџpWYwc߿J.1-{ы*d"W3eѣ?-E*z=ٲMHa3dhVI DAQQC_#"8 0l.λul 8˖-C 5k% &nx;wNu&sO5(KnăH(LvԩSW_u+ϗzJ4"F:0yUWy"@B*:D;B .]H7{z 6LWo?IBMgs+#'@$@!p(sttBBDC7Nz])ZIS|o<#z "(8Pצ~{R ~;A`Rkjƕ⡷XG x'@({'>(֭MdR0rcS8 5 A LEE(- JE8Bԑ@jvzH>uFNG6LHÅ2Z@V1*!"*+s׳*Ky#\EQ2r'S:_) ]3LdJ%y1h G۔ ?7BQ L8rꬴ|m!j>OTC N=?B$_G[Vj+R!mҷ%zZEp1˷ӖjF( \_*YE3ZlE7~niUBXh`د]#DIB5m{Km2Y5._Le+Py U:D`h5 h 0LV8M7ݤS{HBrСC.ܰ˝;edwyG~ }Y/xQgziӦ^aҥ裏"d'A2z7$GO?%ن F8B!CY˒%K"De&YI )RpӳZjR~}f=:VHI (XC飣'V\YGK |$qud>LGm4 G8/Nf Gs D9sFF%kֲit9Řp‚s<[3po= # Gk3 @r@믿}rԓO>DTQ<oQK$0I" s4:5o 3#\䟇CL)e6Y}};vZ MH8zM uVA$g2…]/ GTJhfldJMRU`/'=j4 u#tz(/& pʛJܪ.S=q,UT+ WU1ULsJl vxVy1keLTc*aT>KqÙl#G]<4v k`"ye]]%&%_[HXC-! b-fٽJ6_Eh2Q:W-SQяXH◀0L(~5/xp O:H3 Du[M{HMP>b)J +o?%K L2:zu=܌CTiXJpMPx DRjUx{O(z% gH >c9MJj߿N+FܹSP6uƷ~;I]vii [MtDk]?w-j]${, 4'NB9RnԿ#VB7NJ*ඝ6ӌ+)9gX 7p^eH5ҙ3gJ'NM}-\։3)Dv#0_ T&%H:ς-Uʬ斔YBz*%w/k&}R_ZYRZhʛ%Q2M#T6H(`i 74xJҾROp,Y *ᤩ<;΃Qy5CX=;{ny%zUʭJ[M]1JYYR{lM[Ky R*mb9C:peVQ&"C~l.[?/~s_2nvAJ| Td+=vJ:^_t$^Q7%Z v"T%O(`@+x'? #`. S8c@߾}7Ȑ믿m#G{].܉6 モy/fۈl<"kf7p L*VL!թSdz?1Q*UaCoG Qy*!}B*;XM?D<+(9oX3 G)!|/kPM>H:bĈ6 #} ~ P R.~mٲE3(Viaz7e!huٓ262ߌ+)9GX 7peH 4HF/KvxA2ڳg~xCQJ?-O],kNB`:vG p V㎺eSbI" ҼS%mC:}6Mס-vDE  2C[M)K4neQi  N8D|-!TE%ifݭMégp-1b 5aՊkij$ $XSӱ.S#s`6!zᔇU'D^8ݕ^zcU/T*6H?~0c|lsGֶ "P[q e[{ "^D,}q '`. {Hb$1DAB85-&=zGHԠA߄XBA8x+X_ p6 Nk0 3k,Y`TPArEğ@ !M?>bG+VHLttC6+߂"ɓ'}gϐP>dqlMNP]R%Y|jds]>|NvT̸Q<J$(kϱ$@WlYyn:u$ONnܸe:QIhg"٭\WwLpR6_$i֬‘B iJQyiҧ-/LXwH8^YUR֪xG3=Cg#)+g~E ʯvhIHz$9y|ԅoBRP@;}sP9MKK6*UuD <{C8_Dm[I4rαekF:~tAl762Y6D@EJ %}Zu[SAZP FՋnX^G-guƯ.}LNfݖsʌ'LϯfփǥGM%O5(g]-8l'd,DX^Eʩ"_ saQ|k ěsɻ+ի{v@ T O`@m " 4j|}v~iBZ(P%C2͚!$NZGKfݧ}JGCڰazZs{ GN!֏H@#\ܿdǎ?.+~" *DH ~OGunF?Xc3gv̊H˶i& #,=:uJGmkmO8Ռ+)9wX' pe{H? ʕ+Pzeҥmq)D9G9"v/TZpJRRuza 55q2o [^ $)G_Ao8>z]%-KE DA.'Ti׺k(]xA.z>ADd㳸#Rm!ʻH"=fJMJMZһ~ZW&? GN( زnj!% GLZScLlYw'ͭٽ1H &mwTfN?xەe+C; UR!75N" s!yp752RAJ$ֱ@˅DPeH.-saѥ=i5ޤ3;tC~zq7ڂE*Odȑ^ի'4n\ t3#-t\T K"+Sִ:RM%AKkY\=ɟdYb'!jּ{%֌:TE9 gR͡ǩ=vP ҹj1RBC5 dD $PDB!}lzeܵ͒z֋*͝]*PlϛU4%jV N8Z1a qY<$wA 2 @|0)WM`i-[H֬Yf͚Lxg rG2>}~Jъ(XA_|Q0B5j{lݺU~mkԩZ\ڸTRЩT8\'ZX)(<є3gN DiJݽnjSQ8rJO$@$J ε(xȇ{3d!;vL^.\O^3p~]vNOm ҲRzꥣ.e! Xƌͤ^)iMvYdJ뮐1vX:t^0 7ЌBڐB3fڵ́ q7Be]Ƴߘq%#wG$ \эl @YG7c&p\ ;wΎtU"G R鵮ȟ]\ %[Z)n #H!v9)UԕB/>ZzkQ.A@ $a>"@P >ꂴpHEAэFD LE zoJ-z?5$T,lv!?lE F‘Ux\3<[ p<؃y=_NKtnˊm2nv= f>ZV0sʄT{%sg+b{[IK6`'PѝnV) eɑY,/%8!,%StV\αeov|H%%uP(Y;cٺy(BY2e;=f2G/`ҬL%Nlj?6\Aym5PIϐ oeʜ{ͩiO#,O}q o:/J3quJ0%Gy:DjUrm`>{YM$KDb!/p- 9LB xPB ?po߾nժUKɑoz?ɸ駟g9g n=CҪU+3)Iө@̂xph[gꆿy 4ËHkֿ.%}HR L0+o ?#e+Exb-GG\P,!0}s\uZ׶m]ߌ^{M GTxj^g]#Jv-L:"~!5~{IkA0"z$@$8% @ ܹS?Ԁ4Ĉ$u=EtuDZ5!Coj`p`Jpџj1IZ%D G 4˕tMIFBӻkc.NH8zR+Y#-AXyec:* "@isE$M U&!3h~IRNoQmBDr9pdaۈTʮ@aRQ)L*J2TSRٳ*aWdV,ģ\J`B8/軥c'a[Q BJFUb9z;~JK*޲|x)[N]>MޤtyE L} 7A\7 QՓ[;HPѶ G>@:oih>4;Sz;77JR3%GyjtZ-6/-nV F97ה+5H  XUW@Gd#DAt Қ!bR$ɓzjM/S>n-;vʕ+K Ns+ƱkP8 FIHbE" "\t/V "R74&zYR A@ӢW$p$S 鬐2 IH8ܭTB dvR9ϣM+MCpL8LwxET;:V)$b2gӞ#Xi.[TTaJذl;t#o` GVE\[ '6 w/+5 疗CU BIQvE[{ +O:*Yz\f~ ز)GBD TB=}1iN`Tr7f+WIwt6ïpdT8NFf^ %5UX%"h5{5|FxhifD*5"(Y#|%8/֒H(   p3p䴞a}HH‘{m" tDR<8i.Tkw} M(YiؼwpCT$D$y\@$Sѽ͓5*:%6N)^ i Nb4TGjɭWADIB⹲h)&ضRb>_V"Uqɤ~wYpw•# BA:B'ABphS;dRR+FH Vw2O@3S:-AvA:;D󃒈Bn߯^fSǬu|JE=BiR)-K$zfI уiݎ9F!A̩ )U 3!jҡgxGɧ:s;$@0)ƋK &@RO$@$@$K+)g  P< %.C$`%AF.]:INU6Q^ppmU*)H:vR4[ :i&$A("gQLz"W4IpzUrW/%5Kuuthl۩1l()6,j^$@$@0z G*$@$p P8йK   ̸Q@LI$@$#~p2 @p,!Qϯfjcj!U@)^N͍-b(EvJv儊NdhPn,37g-ѺpʞɍdHH a ==p 8#@(:%   `ƕD H<TnH . P8 mnᨍp7גzqYynwpNUiH*(""ȺXAAQ{mUk_־(EDQ UiR^|sfϘ$d=Gg&L9GlH۾}yjrom#F*A{.v:ڷUk!A];'p<Ȍ8s@(W8*{")uO C p>|D+-h-]W*KnA`~Z lL1 &*5dv_+1S~d'x1iզRd j y{lT,SJ>4FSΊO Ϙ## pJM JG @ pɾc.@(K)N&/XrKJ= rm΍k}ڙБ=_ eZsʷ&ihR% UgڴV@;'pG=/@C@&`ǕV9ڋ!uč: (~G_'Z0}Zh"Yv f5ڱ3.,ML1tҡAu'5LR~_#ѝuxssV&̐ٹZԮ"'uKT.Q@ Lv@O(LU @7츒U \G\CL 8ʔ4A@H8J 'A.@(Ĝ@JG ±; : p3@@ Tv@O(Te t@G츒Y \G/C 85'B@H8J&@/@(Ɯ@JG7 @  @#   zG*E, pŧ 8*`Ǖ-B:xhdLQƨ9  @*쀞Q*49 ~G7   W8J̍@rL@rL@@8 Uh,d,.>]G@Q;$phh  D@ c2F͉@@R!`R1@ 8J1g@@츒Qbn #ug #@g  @쀞QFc@ eq:  q%#G D@8^  p1jN  ;'p M_Q9 $&`Ǖsco@#<@G<@@B%`BU6Y,@(O@pT+ 9Z 8.u<ȘQs"@@T=Thr @ o@@ 1;$p{#pᙀ8♀  *;'pXbGY\| v\I,@q#!@e! B Bc  p~c΀  q%@ G@ G@@P =P"@ 8u@JGf! pq@42&@(cԜ@@ v@O(Hs@HL+ % 9\Gx& 9x&  J l4XQ# ਀W8r@4 p\y 1GD  zG @ߘ3  @bv\I(17F:3 p3@@ Tv@O(Te t@G츒Y \G/C 85'B@H8J&@/@(Ɯ@JG7 @  @#   zG*E, pŧ 8*`Ǖ-B:xhdLQƨ9  @*쀞Q*49 ~G7   W8J̍@rL@rL@@8 Uh,d,.>]G@Q;$phh  D@ c2F͉@@R!`R1@ 8J1g@@츒Qbn #ug #@g  @쀞QFc@ eq:  q%#G D@8^  p1jN  ;'p M_Q9 $&`Ǖsco@#<@G<@@B%`BU6Y,@(O@pT+ 9Z 8.u<ȘQs"@@T=Thr @ o@@ 1;$p{#pᙀ8♀  *;'pXbGY\| v\I,@q#!@e! B Bc  p~c΀  q%@ G@ G #@@@@@@@@@ ˗7߼yjף=ϟG"'@@@@@@@@@ \6`d@xH:p{;6#  Pܹs}{wp ;i p&@lJ^d3# pI΍G!vIGՖ! ^ =#/K@CG.! r;$pB|@@GԂ  @W8 בdx (@Ǫ'@@c;'pqx%@ȫr@JG^N :qrN 8r04 @@ 8í k\A@;$ps@ #ɨQUO  v@O"5JW3 x!`Ǖ('@2.u$ phah  @t;'p݇[@Vڃ v\I @2\GQ1 #J@@쀞Ek #Ig@B+ yQN:d\H9!8*@,@@v@O(" 8r"@츒@d$c@GG>V>!  =#L@+G^  W8tȸבsBpTY  쀞QtnE\ pZEh q%#  pIF |*}B@<zG!W*'A@ ;$pE9 q#' #G C@@ =>܊&@ȵ@JG<@:AXU x,`<.2]CyUN: v\Iȋr @ \G2N @QGf! DzG}pMk=  `Ǖx. $#u$5> 8 @X y\d^ 8t@/츒 @d"- B@.`p+ #*B{@@+ \@HFH2j<| pcU  ీ8t  pU9  ^q%#/I'@ p89'DG9Z  ] EV@5GU  W8⹀ בdx (@Ǫ'@@c;'pqx%@ȫr@JG^N :qrN 8r04 @@ 8í k\A@;$ps@ #ɨQUO  v@O"5JW3 x!`Ǖ('@2.u$ phah  @t;'p݇[@Vڃ v\I @2\GQ1 #J@@쀞Ek #Ig@B+ yQN:d\H9!8*@,@@v@O(" 8r"@츒@d$c@GG>V>!  =#L@+G^  W8tȸבsBpTY  쀞QtnE\ pZEh q%#  pIF |*}B@<zG!W*'A@ ;$ @IDATpE9 q#' #G C@@ =>܊&@ȵ@JG<@:AXU x,`<.2]CyUN: v\Iȋr @ \G2N @QGq 쑙/mqkfNY@@쀞ㅢy T@@츒k= @8N/@(18@܍ dX e!I 8J! @츒Qڈ90 K@ Gqn@@ v@O(HRQp< @&`ǕF́@x]^: 8E(w# zGt @a  6;$p6bx-u9H@Q,GqBz[?̓Og,=kU+Pp- @(8*"!,@ਘ @@W8*@  E:R$vA p?*/~5K7!ϞrH¥+'G@v@Oਈ`@o.KNÑv%J_<>3#K@vK?} Rd:>2q%'aOf߿E׵Vڃ:}̙2m4>}/_^ڵk'۷ "Q(GqBzK!rl@,zGYVx[&L?P4lg}2Ҿ^{M~W7mUVJz]mcƌR~}9sC-t_H\Qf<@ 'NoǎQ\L۷t萙п yW몝;wJVk׮r''c{oٲw}I'*7q%#O L@f̘!~?z蔄ntu$ŠOCv7@5py%qW0@(N .|\jKHI@L eYn \Ru֙ ty7vZ9ꨣΒO?=~v͛7Xt˥^jk C 얀67o^ctIx'xBF{(5R߈MCG7|TX1|3rHyGsw}wRJ?MA;$pTІ[@4i\{R\93{Gc}ݡ rUYCزe 6L;;W^c[~+!P~qȐ!E#ptnB>.% Y&`tn;v#СCͧnjԨ!ׯzH>#s?,;vL[;WZ%:ݺ)wF=κtGK/N**W݊=@#pꩧʂ  Nʖ-+{͛+? W\q9ADUB;˗K=o/yW^-rhpF_F(>W8o @"7Lȏ?x" վ\GBU.@5|AOݺuӎW^yE۞yi֬Y8"p(wq-!-'F2;'pe&puיٍ9~xvgyYL?wkʕ+￟,\мѷ~?>sR" z @lgI{(Vt]jK.rUO7뫧~Z:޹뮻L .swGҥKECXjFæM̷ fǕZAڍ 蒡4:;p.#M^@g9sY]?]s5W_~/8Sl MmY^(˔U+HŲ (ۨT)W>e YetmZGʕ.ini*`ۼ]jV,+ChY퐟k66Kr~p΍kI e 7 R>8O|mغ]F-Y-[wk׭&ԫ&u*7G_ϪM[ejО-U}Tܹ|y5y*ҮA iTlݰjEi^JGkKb0w:-kYGkp_*u`ѢNU)SD#YӲJrLVxzZV/]-o=kVuJ?)fl-U -v|SA*8FzՋdoW@>{d)5k̟?_D=qمy睨tj?3FRAਠ dVΝ;_~fnISAg?Y>'6}t 7xr!!6n(ol۶ʹS8p=/9/-Վ+ K"@ ␘K4ti/|f"/Hn|g|<}Ow.o7s_ΔOg,wn>uԐҖ;Lieb>nS"EϿِOAʍ~#cf.ʿT-_F.Vvڳ}%65oN2'{5=|''\w ۖ kȳbZj''췗\XLojV{*KYe|<[-G~ao-+PwrsCi^(+Cb<9az.JAH٫Eq @v =~{w '~?T*[ĉE?E4`+g%pʂEQ" Eɐ!CLu]j&.P^=1bDG!AHgF>|ԭ[wG&LŋOXoذAt I5m4_~9I'$DJ(a 蛹',vݝHgO_rLI+V޽{ׯ_//y涰ώ+ \/.,Yj;w]\GvW#වSN*ߌQ &vX]QdC讏Sʞ2`/GJo~ :KoEn6p &Bq:R=g{l:kXYz}nT=l_)&]?ʈݺ&c~lHԙtUMC34Vf! vv%$.fѐUJ<rR:˾閮Z0ӽuϝyJoMۯ3 -SfQ4.fVP M LTOZ V<,vr>a =SaYt% J}~?3۔} _@[ e޻!oz뭦1=PZ?cg8ODnfOG{;+9#v7pˤ;V:t ʕYtj `Bfn|"VyŊRR%^mٲef_ AgY O6GamE# ڵ+O'N8A:무/apETCѧr,]ܮ3LG6 /龺iH_ovn.SOIϞ=uxDߺu̧!W8 qi:8)p%uU+W4׎5kJÆ I&fdhב,뮻Lz1o;OtѰmT,GCJ^tiQ@D)˘u/ Dn6p  *ߒa^ 7KU@c< rgT[ҥc8)EIg Y9| fQp c`KY鿯S~+% @q/g8eN2Թrq׼9+TwХt¶t+}<<t4Ȧ58˪"Rm(3;Pc:[qO~l~|̾ReU]ةfIʻ.K<hmm6=Fa_cї]Rq:%B-ҴsZwy(-G/GkR`AgS:}8tٻ¶[Fޛ̾Y} ۍ@L eYSvԍQ3ƙd}:o^r9B&L0|~y?r-2zhLS_~Ҳe·4Po[:x`Vi.sI*Urg)El?蠃ll \*_|maQX*E;@lpF6lY3#"]ΖԶm[s~f֧}0κx衇ʏ?hH:S}0|oǕP-ڈakWN3.ƍMpuڴi:߿!8ב0V6#9̙3on8Sl,•re03bov씞-65QfuAkߑj U)W&oϙ G @D{#M/8ٽud&9ϏSWϙ9w}3fLhGY$&mW\q|wfݻewG%4j[:H1n83.)SNy츒QE@[OoQfp\G\ B .L:;襗^Q)S4RӝgC3IU?A0'L=vyLC\tx% G'tn.Wcќ,o8OL4IJ|hX{ fiQ8C|`C#fojUWc̜+ptF0 `6:ozOxr}G-Aqhs1?˓} ŚJ7yrp)][e8?UNf/Krgf8~F[ ]gzE6T<ҢE g >Ko.b*F~љJ{wܯvI5Ξwy, >ysYMCqAg4%՚7onf¼8 sh; *Wlٙ.O˼G۷7F6̄uxb"N=k\:,pUW% _fQqݴiI$ѥ4 پ+fZ]M,xhncjnWrNosy^߇@O e_q kmȑ#]vfjٍEEzkG "T~ Kגjzã1vGSJS{ޞHHCS I*rSo"GoWiT-lz 8+]a?@쀞Ek ZL;|C FӧO7o'#]v5ݎ :Jٍthۂ D N8A:tmר.)ҳg<$8#o..YDڶm+J6GaEO_6˭6h@x.X4i :Ygٝ;)Q~}yta3mMhԨYJ6~Em۶m2|p|PuV# ?S2`ua?;$pB5hd~Fgi9p ]h0x饗֭[e$ ;><#d˖-rM78Sl -9>Q$gZvOL8U9YdK$p  괗\sؾQ_cK6]L="{֬l"pt{{StfX[ޖMv-pTD x5Х#/,ɝ67Ѫ[寏gp=`o"GO %6Y#3i<2vԨPք ݑ;@J eUl1 'S|Uy2}Ic츒Q9> PP@˗]vC#5kv#nԁV ઀~@?Ըz(x͚͌5lPfiJ?QE[@40KK4Tl21c, ˋn7o9O6zY$^$'t1oiRZDM*]7֮]+7KF.!۾}{3f2o7v\IB<d/4*`ٳ͸^aܸj <2biӦ1>#W\)w/^F~7ǜڿ+;4˽{?I3/BsUsX0бB퇽MiKA@Hq"DG;7aN~v TlirxW,reߎf52p7ߤhHv<\$t)g5'{?_@[ egF@8l0xgg}((`w!ƍ33]wT^=)~Gّt6#fZ: Fa"*C-?@e;#O{mO߀ٳg}tGK7&L }x74oBFn8=nj?8r*  裳7c~j hk1 ps͹4=cf||RsFGq!pKW=TtԨXSJ˫=X.l]4< w3O"#}虋̒\K" tlTSAa]JK+piM`VUGA5saF]LA t&G,Mo6zM~ 촗 ]Ɨ2% "Z)8ޞzAʈo~ׂ.HEvі.K&pՙ8Sy,^1|z.A f>Iﳁ#-k7 ˃c~]:N[`.Mk٘4ņ @8T{@]Gֆ! v\I([!j*5j Ͼy [N;PVO\G"+ K@gݦMfGu.H7ngkJ{y"= l/}= y|4iQj0P'i۠zԥ4r拟ט}_>"սɳ|`yM,6傥*Na#1xPJc3$m bY4tT{'mCŲ~W˷A&ڒjکi"}.Qg7 ,D ȥy4 猶IW4wLHɓN4lnz&^=X!CY7VowG:aGfjuL8xՀb eߜ@ uv@O(u Htrl@HF+ %c@@ry&8r'FKjХnGMjT79,麓Q^YGy= @ ;쀞Qvԛ^"@@츒o? @fdƙ pto. Xe\8b\FLnk>ez4VQKjW*G.=w8J(?!d8ʎzKא  W8Ȍב8sp_Q8ںc\I2i\KI{Ȇso#nu~L( C(/8 v@O(;M/@ _Cz ov\Iȷ@ 3\G2Y@}G(#-yjyYv,_YvԪXNj5^Qi,WH[bDR'<喣NjݙfxݾcI3Iz<7 mgNƹR%Jt@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,@7@@C;'paqx)@˲)@B-`ǕB]Fבb # Bs@@b =N܋"@ȕJ@v\IȊ@ #h/,Ϙ @@@@@@@@ʗ/oyf{̟?x;EoOy#        @lzt^z  *`l֬Yp ;3Ԅ  v\l&@ 9#ɹ(O`ڵS-=B@zG^N!<,*]B@ v\I(䅤 @1 p)&xN 8r$4@@ 8} ;ܩ-A@;$p3@ #ɨQUO  v@O"5JW3 x!`Ǖ('@2.u$ phah  @t;'p݇[@Vڃ v\I @2\GQ1 #J@@쀞Ek #Ig@B+ yQN:d\H9!8*@,@@v@O(" 8r"@츒@d$c@GG>V>!  =#L@+G^  W8tȸבsBpTY  쀞QtnE\ pZEh q%#  pIF |*}B@<zG!W*'A@ ;$pE9 q#' #G C@@ =>܊&@ȵ@JG<@:AXU x,`<.2]CyUN: v\Iȋr @ \G2N @QGf! DzG}pMk=  `Ǖx. $#u$5> 8 @X y\d^ 8t@/츒 @d"- B@.`p+ #*B{@@+ \@HFH2j<| pcU  ీ8t  pU9  ^q%#/I'@ p89'DŠ@IDATG9Z  ] EV@5GU  W8⹀ בdx (@Ǫ'@@c;'pqx%@ȫr@JG^N :qrN 8r04 @@ 8í k\A@;$ps@ #ɨQUO  v@O"5JW3 x!`Ǖ('@2.u$ phah  @t;'p݇[@Vڃ v\I @2\GQ1 #J@@쀞Ek #Ig@B+ yQN:d\H9!8*@,@@v@O(" 8r"@츒@d$c@GG>V>!  =#L@+G^  W8tȸבsBpTY  쀞QtnE\ pZEh q%#  pIF |*}B@<zG!W*'A@ ;$pE9 q#' #G C@@ =>܊&@ȵ@JG<@:AXU x,`<.2]CyUN: v\Iȋr @ \G2N @QGf! DzG}pMk=  `Ǖx. $#u$5> 8 @X y\d^ 8t@/츒 @d"OGfn;bDfNY@@쀞ㅢy T@@츒k= @8N/@(18@܍ dX e!I 8J! @츒Qڈ90 K@ Gqn@@ v@O(HRQp< @&`ǕF́@x]^: 8E(w# zGt @a  6;$p6bx-u9H@Q,GqBz;S]֨$O¥+'G  6B v@O( H4rH@-;$p[<Z#Y[z:#"~wsmj݋S.X!89]9izGiO`׮]uV)WEvٱc,Y2 sNӖgT#GYZx8.u>q+ͳJG) @ٳg̙3VZҭ[#amFx5k̘1.ԿmkQGqWn.%$,xN =#Bc<7nf/Q5矗ʕ++Vȳ>k>˔)#7~ɀD>6w,_\.}h[ڴi#ݻw9c_a?W##F/K.֭[ ,kThl߾]M&SL1[V^m^sip;m„ O?_ի˓O>)*UJ?Ǝ+}xbAo-O=w_;Cx#V K@_}_M?y1S0@(N . \jKH˙TZZ',XZ-w] =#jKG`ӦMrnPT)C}UVr:S7|#7xlٲEJ.mFz<}O?ӢE yB iʕ+.Esv:snGu\qqHL1 _Gy8@:M/^lZZAҗ^zi t} lȑ#m޼Yx?~9YTVMZl)w^"ϝq%l>}FL cǎ=\ A,7|fٲe堃;&Md~z駛KQ8һ] Ԗ3f?:~:eXܙO.MW^r-=ᦳ:KΝkBNږMĉͧ'Dz)RiRqBw8'G^(zM{MH_+V8D_ s۸qq&316jHz-sDG ,[oՄ5d4h ӧT߹RC;$pT-An˨uE>f8J@ <#7tt5OOhkһ+zV4VHUuF]rVPXd p 8rΜ9fo]j׮&7Ĭum:!XF;*nJ^Bc  |%Vҥ&~jTg?TgϞ-'pA7ImT(G?,Z%Vm%6K%~ RL?ȷoP]+m~\Zo.5)J35•I, iYkRliW۰T+_zî` R.h~k}6ǙlL_VifiUԮT.1ha՛ɴݳ5miPt 튷biⵛdϚMҰZE#x=v}Nj֖֠,i [iLӼVE֦Y^D[RBɷ%r|5?n߹K5iD9{n|o PmGvg.wIul&mڶ =}EAb| ו>7 fwTy'hpSzJ +6|>'tj&v=V2mЯ?-Y-5m;;?Kss>ۈz!5?iXG}\GyDMmP?C"7ϳh>4\vKdy#'M!f4sCо^{ד~&y^R9"NFzYG y8V1|EGaG>V>eJ@(:tu1]n,M}͛r:UzM)?#d׮]f?]2$Ixᇋ~uM>n8ݻteSq?,a k^+ 6LzY6뮻!;s8]NKW_%>htC=,f1ydkLM}J^r kA矗W_}QG@ %:3O>ifQХnm[z 2رcݜQdChmAD(eN/iR f-r2y%x]gshP4@3ٌlc/-7\̌T^t$IgЎ3ٜ3;:L`V |tDn6p B(/%mK0R`6eABItӰ?O͆ 44|ڄ^48w0#`!" MiC7 :+eZPS%h_J ЬF`V8z4,IצLcnE3Ii͞;Gٚ򬗿0ˊz43!i|`]==A3Lz0>g33,8sَ~ZJ?UnFgѠ^2Sd43t, h'ڒuLlQ0U`ja0խMP2mǴKs~W}8ڝᰎr/ Ep]Ud͖m2dL72}_vI BLw!d7;ߛ?]M3<=NZh5!<7Ѓ  Q2X΢6Ig N<<}5ۜͳwf;iI^tPke8/_]a?~ `2 K֎|ZQx1Q˓w}vb6CJ+5Ԣ M6fɔmI1 p{'E $# ID9#(b@sΞg8sx@ *&P@[PklNSL&tM? {R.xn_(bdj~ƹ*&Md${6}=?KL37?qGwsiؼpԲцs)ie[~?<;>-_.'Y6E&55(#>|Q!f>-\[~z@% \ѧu:$/)=#6 Pj(m +7SeJ8hTpMuo:AQ[ %e+1L1THNdkܣٷMC"ElcWmѠN:=ȶuv%t(P0J7/ G e<0=~XjZBǏhL}:G$7IQCZkIn/EQʷ^i"rUp{HZիjt>]$T,ҬI;׾Ê^37K"%鵧o-i .V} P ̞=ۜuY@G߫W^1wq}衇RWWG^jѢjfΜi$)dϞ=!<.ꀔAip}Mwn_̾UEby%QbB &p@zW_u)msrqǙs16uGc褚Sw3:nQ$l3]W/]/iYiR {/+2K6UT#I O a֯Gvz @Mwߥj/hN8QkL.yq{6֦pQ (\WUϰɟu nسVӘm&,d2Iel '-`4uw癡gGS-Q\?FsMϊ/%۶%Nh7KV= Z8g!]N2zlPPCQZ,r?:6Y6Kl5¶6ϦS) +J8p$zрhfaEcq=KgI6Ҽf2%EڇOZ!is% +[J`f)=/Yqm)I24ͧ ۆe(aBq"tM楗^2mڴqi3,Y.JIt?ۛo1m#m;|pEǯ 2=u]}jժ|X~A3fL0~}6u X_jх<^qFn~3gFe˖9~(uy@$#{^m:?_\!tG8L֭]Zv9+(]iH0kŋ}'?F+<B8Gǖ!$p饗ӧۜyi;#t@p iǕGFĒ,-BϦQJKXgX# B&?vT/A(LKVJ*g%_l/ue6 ʃGt)Edl'w]7\a}}) eO+)*{7{nHOSrM659i潅KRޯ9wEK]JTkٶ{5EV:Ŧ:r-T3ݦS$ { b+4&`'v.]b}}}gXw>^*%e{AHѷ$Em¾}sSG ^az}S:ǡfmAET^\5' ixb(Rd3E ">ΉmFw c$a(#H&|&=z0H/{׬ZzOҎhԢQY^usWGX?)SE].*5.8KuK}\HQ~aӱcGwץR+W ׿G:2 @a7nu3τԩS_$+G?͇SǤH]vuq:ϑ 8Rz)VNQ+Ut$a[KV+>4nlVei"oJ:TaGUxp94@ ga>ssc=6tI UI+GF#0&JpP+IDE8ڱYskߝ3 _ڦ?U[Q T? 3JQ%}˭F֦aQ6H }-NCVh%lq=CQiD ߗ G`4[2ubH1(=:l*O%+Yc˶o~ xpNi>4%!ھkK׶+^|dCJ%嫥ˍRgm75ZSi[\QQQhc銄960wt(K'$IgGl䲨t N/i&1-uMm)))G̱=K nc0Q!6XQ:utC7 ON[*(Ov<[pa@ A8zh3|f+CCj"vjBwj 3Hi~4_~7?^`fب:W MbV*WTW^6آAM#U G'wsk̜s]?Qi#̏RVHzzjTIH9ߚ^zh$*Ta%۶5HsTFXJVKhI"Hi6m-tV*iX{Ȫ|3mPQF( b?Gȍ/p$N.-lf͢`;(_G~A(  .4;XUϚC޻;uu=/p/^HzB|Wy7u;],=zQW㒖BMVZ~ڝ'6l|FⶢboKWuޭJ:TAGUpP9$@ -:aӳgO>Ƨz7o;>ꨣm2 K!G6s)F I)vn_lT_{w0=[6.A Gwʜ[S,{_:CrRI8ihժ^袾d$t`vۢQjEu5yVߊAF6ж鄣i~*]ӞRȧhR?1A9Av$d#Bi%4 PmfjuIJG>;Ljw#4EE VFzYF7 Ҥ^ !Ycפ6yԽL *A+(S^Ǔ*3~~T^VZ%6=VolKπ\m.z]'Dko#մE8I8z|'aSiSSC?0ĤHO*WQ`=<@!? #hsE7cƌq". GC)9RUW]etBkbVt?h ֣a"M6%咎tn(>:JTXPVHӔ*Ew7)Ś.)Q/B@ {^$*rn7LJ6t;䪔G81k."HqI27|SF ӢE wcƌO")y*:TW"%i+ TGI9 no ]dE?m2 K!GB  mDԢh'*T6Ҷe)M5!o%(Ҽ.أ٧MS]:pnE8R-hVH˞6X\lwo;]޳Q\*Әi#R]6"U6Q8<‘Tqs@"ݻV9FĪu"? Y, P-DLRe)YN8cC$t͞2G&x Loۻ6s9f7t{D0pTFc5*EzK4/E4OW^%$xfER7Rrݡs'tRק>Wz6EQZ1-פ|}};Jw1W>Rq%kAuVlƏo>^ϞJQ}I8 P(;裏6 @*J!;uT'0u(OYKy(yMrQk#F0}qT?D8Ak@!pTeJI'0kٲ;"o}5+W4]tڵk(p93#u"ǭ}w)1?bm9wɥEZ)N9=~lPWӴ^fU^H|d.s[;l+66{jf/|x$)%&a)TOuՅ~s߂땢D&5w\ .^I] PJz==JL_:Wr-M7hҠ'[y#Fq:R`tE:?z뭃xF|H$E"n{1~MRJ$}J(#G!2el];nBgCw3lZ GJvk&$C G;hhn> Z!)CQQ^>ibid|Z6 ʨczh(D *q Go^sbPT)6t6qQ4=g]n+ޤk"u IVTQtlQNkv6%]XQ!P:)l.[m#M\$mS6XX}׌iڭJ{yF,mJ5mrJ/w`Nc\ioiKgzi")J5)-SǦe+Mpo:>L k(rCtm G>R/ tM楗^r_$ZlH5gۺgaQ&M8uN޽Yw+u믿nիgQN:E(ȑ#u]͵^[lYPj7e/JRJ8IG]tI"%:e~&jS) p$ vXEomD #:Gй¡$gQsIye] ?rwJmRG8R~Kۼys֟_/؝njmvڏbZBR)RUVͥ~=zQVjvW"1@ @8Bɡ@""w /t-tzz6Ăpa A8F9FmEP G15W626BQjQ_|-YH GD.)J",cϦRZ` q:HQz$I9um̀k&ީu2c܇_!sRQnla 7H$s6pٮWҤ^-sa]"Km4<:}YnAG>mӜji/bwu G{kT +JᦔXβe Fֳ|5^=浧e‘)/pSm\g}sm?n#>Ipqπ~g[mm2ga‘R)-~лF RϽ˦`2xyFwI&F8J(| dwl`vawHwk2HC 1ZEGzرct J3m4h"k1[CVjԜҵV(Ir]`j;c~GC3:qԑ/=‘.nvQFfҥ. to/i\tԯFW$K]*`TmQ PtD荒ht΢dI'R6J-VfM( AQ^?/.{^Et [Q%u+Zݔ՟)eXЎ,|7}ݷhիX.ZVpW"@=@J@8A@e$(O=;o֍5j(V~<3& eBQC2~JloʱfrK_lbxC'"+p$)mKjR;_.?r)VZD%pnX˦*nӿRO)VP_]tǎf6]{?'U8z{wk4%y&XV6$ ͷ*QMm)ď-bZR)fv'(I:ѷ]>l_llV~;E8N[(TJȴCE>Z#Ȥvpu>]X]J<ʍ59.Q6Em|`DM}:ܦ^$M l:kDv3w/Vpl‘iЈ{X)$ IW vmZ;IfJSx^rgߘ;Ĭ+}N GFx߷4'w@u,JqFFERd$yξ:oOtvY$? #%ik"-:uj.6i\r%nB\lE] L#J,͚5sw18.`)-.\^wI;tmG)r$BzE".L.ҥyHYg^ΝMݺ%m/Q  Px$K4m\}fM£Qt}i>}mZlGb+OT]weR/hSIRrlfJvyŋҾ>N#It)ʥcUs|?@ 7@fSO==yN+]OW]uL sjs݄uӄ[o{ǖv_‘Q96bD%"E8 4jG$,X,\ M*!R GhgѮ/}\ڇNrp'6$[ӛ]zlӸh=(o۾EjFT$MIﭰ![C㋄5(^JnЏv/H۾=.cF}0?R8R.RQ(KU vAJc(fM"!j+5wz[b7 j v.J%~TD-16YrOh?5YۘKF g#=eӑiӒ]E"x} д^%stgfpվouB%mlŗy1=;SX{,@6r7 ~ 񊬣@KEw+mW_}e4IV`E (K-ASzE]*O/_mڵkgt%:mJ}͍%}nŊm[>TcʤO J@frp6mDݍܠ_7ÕlyG?`ee$<|,mUԶ~^Ńz!T$K@>oJ۬sgeFiѢn,=6(p$g]VDI+*ڈ1jN{+ɧ,‘IEigEQęm:"+ptߡ/7#DE҈sѲцbЦq=sO] >$i[E2ogFgDOρV|9 Geֹ=ژ5v"<2m"+p_)aBQzL>B1':9gMow\Hz;/i;;˯߲WHbTJ}Qԣm5{mzSɛ:Nv?Z`nGoZ33׿/yϿ1Ju`X}MmD-B6ȽXEB'츑0H+oNje_8K}aSs6$tٲ9s+;5M>^ҔtiS=z%N@ q|)xT_n ~^pᢓ@ %|> s診I\(q_)O8r,Ub(Ъ.Ifn`γ)ZoZEH"N6m3m[mEaQ *FWKۈ7wtZJk(I7*(w6P$r`? G~E( V՗6*ˢ~\neR-h%(F,o VöO0Y>^dP"Ejn_?^̔JXJ(J;XQEOAGwn[l굝7 /B:D'P(MAxM'y "|#%mI]s߀x;'#CEK z ]@R y%'dx@!Hi(  P2rUni#ԵZ fLFytPtV: CK1xo@zTM* G,TJrhs9 ^/#G墕]l}oˀ''GŹ _Ǐwz葿R5 @"GN # QBUhoHoGGPZ}kj..rۊ\pTt;.>>ѽWRR v,=4u>sၻ6\Xv+z)IDATrMT%f jǏfֵQ(@q~BpT +|@(\~^pT@6ɆBUQѬ‘_oްXۚ[ob6X"i歹ߙg?,\lVr.a2GÕZ%0g1ў635[T%H-5W3_Œ[y3߬Qon&I.8G6 F_.EeFrE6cz\x@ _ /@@J} p!H6J2fUVZm.=Ljq:*Zo߳VQ\JON6[&XvjE^VţH1Cww u,z#^A(D/!@D+ i9V@{$>$Q/O."adj"bQ`F_iRVl핷" RFvۛ/oU9 Io>6;#~mL'T?]ZbI.1+\eԭmljzϜBH$~^ /y~[6%v<8rAOrA6 dO({@@(^@P=R(#qB e"z@ "'Gy5,t@$H4 @y%Q% BH8G>tQl( @ (i@gH  K+Jm #2' Q&B @+~BpWBg D@8D @*W"U, {$H! ņ @rAOrA6 dO({@@(^@P=R(#qB e"z@ "'Gy5,t@$H4 @y%Q% BH8G>tQl( @ (i@gH  K+Jm #2' Q&B @+~BpWBg D@8D @*W"U, {$H! ņ @rAOrA6 dO({@@(^@P=R(#qB e"z@ "'Gy5,t@$H4 @y%Q% BH8G>tQl( @ (i@gH  K+Jm #2' Q&B @+~BpWBg D@8D @*W"U, {$H! ņ @rAOrA6 dO({@@(^@P=R(#qB e"z@ "'Gy5,t@$H4 @y%Q% BH8G>tQl( @ (i@gH  K+Jm #2' Q&B @+~BpWBg D@8D @*W"U, {$H! ņ @rAOrA6 dO({@@(^@P=R(#qB e"z@ "'Gy5,t@$H4 @y%Q% BH8G>tQl( @ (i@gH  K+Jm #2' Q&B @+~BpWBg D@8D @*W"U, {$H! ņ @rAOrA6 dO({@@(^@P=R(#qB e"z@ "'Gy5,t@$H4 @y%Q% BH8G>tQl( @ (i@gH  K+Jm #2' Q&B @+~BpWBg D@8D @*W"U, {$H! ņ @rAOrA6 dO({@@(^@P=R(#qB e"z@ "'Gy5,t@$H4 @y%Q% BH8G>tQl( @ (i@gH  K+Jm #2' Q&B @+~BpWBg D@8D @*W"U, {$H! ņ @rAOrA6 dO({@@(^@P=R(#qB e"z@ "'Gy5,t@$H4 @y%Q% BH8G>t@΅zNE @ @ @ @@YfW-noes @ @ @ @@„;] @ @ @ @*@#UP? @ @ @ @H ] @ @ @ @@@8ʗ @ @ @ @H ] @ @ @ @@@8ʗ @ @ @ @H ] @ @ @ @@@8ʗ @ @ @ @H ] @ @ @ @@@8ʗ @ @ @ @H ] @ @ @ @@@8ʗ @ @ @ @H ] @ @ @ @@@8ʗ @ @ @ @H ] @ @ @ @@@8ʗ @ @ @ @Hl>r讙IENDB`graphql-ruby-1.11.10/guides/queries/appsignal_example.png000066400000000000000000004760231414121453000234330ustar00rootroot00000000000000PNG  IHDRoE iCCPICC ProfileHWXS[RIhH #E^%BBP# ]Qq-,6"u`CM N9g3fPefAĤdP(@DE(MHk֒X:_Eq@ N89  t@\ XM @%8]5$8U6~{@t%YtGQV ''ͅ>V99 V"Cl]LfaY.R!Eroa-C-YaLM *\8"?f l0!f|؞-B{4+ǩYh ;"\gy/dD16ia 3bd<3!eń}fEFMFl0h^ - 束,yQ\ 0X]rߒ(=V3v@T3{5lG(\rbep?X@ [*2sy6trͨGtD1 |Q=^}z1&4a07bqWǽ(hԸIVYe5EkVYjsƗ?86m=O{vNN&:L]oLuzz|z'`|X٬J֠~X~AAACazvA#=FS]377~obj`̤乩iii}3YYusy. "â%jdɷbmErXZݲYXX7X0mmlm^N033fO̻XձqJ'''$NH\x9I+ԒLJOޕ<45`ꆩ}L9tghȞqlLC)=)ّZPjHAg#כy=OH_ޟQ1W_egn|U5ݘCI9*Pd ҝ5gVwenInO[ކAapMcNLهk1wgqןd~" S/2\ToqKK^d[T^vibŏ DDXrk+VlZ[z̶JK?\ȪUV׬!kr&kZZ_톙.VLغQ2eѦ5>WeTݨnܬy[[xߪlmmmo5AQq~֮]_u=wwߣgu n;mo>}-ood65כr9GGJM=-I-GC淺6cV/>>rܓO=n~tg<v¹s?qBEG/\jtñtl|˵{R^WO]vz7"ntߌyִ[=ɾn{>P~PPa?s׿Q̣{9_<=W޳Gߋ%y18Jj7oN|>5]λ4?Oφg&}bk#9##l![z`Cx]= *^RAdE) gRq3J l`/9zzaE`/E7‡7:Z"22e'${y;D|\:;G'kꞍC pHYs%%IR$iTXtXML:com.adobe.xmp 1048 1466 1 3@9@IDATx|Te !!tBW)* "(,u]w[\MkE@{o %) 3LLHf~83?{g瞧Ɓ⧜>l_ 'OZA    @ l߳_jHTTMY3JjԨT} S#@z ԬV#      ͑     PtW#    @@@@@c@@@@@ts     Tkz     9@@@@@jx@@@@@d,,L     @ ТhY0     (sEە'yho@@@@ph4Ҫ@*_i` @9m0OSy#(    PQ5jJTtM)'4rZ-@BJ]B;4lB@@@ X;6&g@ bt%*ㅊ#   @%XVtT%,E"!P@    T0 &P_PZs\    @i֪ 1     @ ]Ev     ܘ @@@@@";@@@@@M t`r#   Sǔs1̎D-#nSa@@@@@ t{p{ S@@@@񘪳El  P-h]-v     H@w #    T b7     ta8     @ ]-v     H@w #    T b7     ta8     @ ]-v     H@w #    T b7     ta8     @ ]-v     H@w #  @ dc<@$ ˁ#Xu Y:E5 q:s֭oݴYa@@%yu[w tZ [+a VvLt붝ޝ"ɇSgwTmWQ޵][?   EI  Tcv:[_/>yʛKsr8w&}իl- SoiGQJ+%f8yJ4GZL+h$? YެA=U /sCz!u68ٵI?7jel0tZ(hz}O>[JE][6@L\[^v ;y9'WcͶ ?>ekuKx: 䩖Lp QϼݓwnNjaƢ%W˜lr2w޼;ܴM&\6B>Lm!~=7 lzw ߿A׬ϿFR_ƫ=Q߫rL;vz?y_/wV~4V9췌utgvƴigz[VM|@. Reiv^p._%z=gj34؜nsӵac ,ȷhj6wQQ'n[UQG^_n?2^q9}V&^s\3}O$f * f&]7ιh t|w$ZwWO5OZ޹IOڶZNk)VvrCee6ӿp}Xo6-՛4nYй!RNtk'U+S6.vNȖ[f^0>q6!}zJ3&J'd$sѤ>l/mc@*C WTvb.2o/U؝sk;V.wt䶠O(sFO# }lfg/Y|E-p^nVf͆@w}4]fh!Oh띚x.+wmhkiת}(nr͂u[orO=7%k.0o=͔?^Hꅗ`pZY(GhϵEFvv/|w5}hf{?u['ݼ9xI>ok)X[|ҧ>m'N̹iܥ2j@g{t }3y3[ieʾ_SXn['m8i.Ov\QƛHF[trxE@!hv=Ojתeѓ_*?yc>AnSjumNK//(XKqge5r;#yD_|ܱypb)?s=|0s,\F4\q7,6=_Z|Wʒ ] qR6r95FK<3]5oйp:3=>QzL6wǵ_jE7^>ڒ}|O[_z F@ 6=g=w+GV2@/j8-'- 4nmvh45[ʒ[bٝt+W,]׏ rtn+[ιV+:i!]dZ#V,d,qA^܋ <݋X f/vSJO}t_]::1f;a  @ب)9tlӲ(`c==wUHy<}Js]'''fC}NnT{XDZ54Hwp|ݡOX kd)1 uBkSJLRk ` X [t|n4kZxz>"@.nP5?<;ޣVעo,$)^ANY,>ݹMU\^W_=ϭߗɳ;^j8 kw;M6׀<[2wwZe}asf=)NOSU>:yZn*zӠ&be # p¾Ew(I2-vN<ߧݬԊVR k֤WmX$oi;UyJ=|3iitU';]CmPZSءs >i0Yb5\! @~:i+};k#Q|g[LKTKՐqMw6O$wu_5:Jʥ>nB9Fw4'IMgb/7fAx^ϱC-5vQmA05kfܤr=߯陞ߚ`lw^@8aߢ;m[Bo}b\?R'lA%^#y@^^iPVhY>dz\U|¹zX$@@**QSz嵏?sZ2+klZф>=̬, 쿵e*RVk$nWw^4[_˝\ק)aQ:R_r&jZʝ])C5 2+-Vl)~9ҮȒ, [Q}r* <57mW$t C@:9;[{ӲժNons[6FŋHx+>]e||n˫:MmGE㞓CwgDsE5\oܳO,@[`v=)Y{X:UԮ-ևC$P9x$ziڡh?;/ z$Mgs9Wn;u|2Ѻ{z%uԼ@@=$_X+*Z.<=mΗ) QGu+/s>%qG ׂdmE*uDԩMKgQZ^i^_+b},WaOߖb{zҡU ӹ剓W{:`aa?G>=6w@]NGHw +P|QcO}b)V&ϜG?_`nV20bP(r(,l9 (ge>msֱh+ozG>'.-6g?Mu.T޹K>tX6i^&R5%x,^VpvoܱĦ2⻷\+3}M4(Oi'&\%4Vm~YG[к&>,;$;3]LKc( TUuz#J?Tlfm=iqB; 4Ի\~{ۋoy} "Y_=V[R!ur]ޤۗ|惮eCV.X"iZvr,;IgZt cFSҺESI>ꜛQn7Zw;;@^wmwN\w>LQ杉X<<6Ya4F%VZ4m"w]?g,,TKlҮS p297ro@m#/'c|F(O%Ke򞔃sJrڔPXWig]ulֳ|r\~슸v7w-|v{qň #{R K9iǼdC< e @u@wyvdC{[\l\ؿYwN KW9A[ ܺerv^3wwPZ`ۂٖ_1).7>ϝXK"ӝ>U ˹ fM:%߻u>F8[Vm&vlyvϢ[OzIs7@~z}a~~|M}s%uwɐ>4gŧv3ұm[taGlֱb僾kxiڸ,>y{/->Mh|zE`褝 ԙN]{S>T=\.>iѬI#'нGӜFEK4}߿zyYi-ujK.܏zIJy}-awuBرdϻؓW]rs==C㩡^Р!ߒ#f87{P+vOli-$-͔@?PgkrUcuӦki bxk7۟[쩊Ů6tb}vX-T5c.1 n@%P#HzukNCiֺ$?@;e<= .\v6@@@@@@7     @ ]w     @c@@@@@Z ֻG@@@@.+A|\    @ؚWjHTTԬYSjԨ}b@0EwT    DH@@@@CaS     I#ioSW@@@@@ tNJ     @$ 莤M]@@@@@0 ;*!    $@;6uE@@@@P@wT    DH@@@@CaS     I#ioSW@@@@@ tNJ     @$ 莤M]@@@@@0 ;*!    $@;6uE@@@@P@wT    DH@@@@CaS     I#ioSW@@@@@ tNJ     @$ 莤M]@@@@@0 ;*!    $@;6uE@@@@P@wT    D@t$U"Pȗ/וq}W {e-/k%5jEDV&C|戫US.g'dsJ =J\LT @@8v\׍KN0#oSc@%@|ɳ^|"U5;EjԔi(1Q+{$+J=^ux<$ ?v? P.͓>L֩nnr'Zw#G _o|G /wxuЧˇS+OǛNhf  @t@@@ hD/PpAN^MɁ#i8ؖW8,M5׎qh`_:w8+߬(c.3zVrzi[*Ǜ+ e' dY-C̔ǂkWDi N'%9duP[;f7;2Ktv֙+y\zGdiMZߛ%t+:ztg?QmUjY+SdzÞ#'yl={}09. 4傶c˦YЉW}*_RO8ߋ ^p C^u[m}cԾ-~5Ij%.žG;K ==Eqj@E?ؖbuٲhKX;SCY @$XO,M:3 ڗ"WB^n@2,`Վ~=%o[}.yV;-~&_G~<8l]`nd̓5H-_O75QM#^y^__ ^xLs\ܥ|P/'@)cN4_nl/wh BonhU0̤͛.ڱlV/>c]CzGƶk$lnygّuY'^\oNl lj'݂l=;ד=yoA_}(s6fu۟4kp&?I93z))5}ƒ;5'ˋTIgfvMΤW^G+?g'-n`cy;]'}YʺoyPT& YU4Y͚5d&;H\oJQ];K~_Z5$pQ9'v<@^Ԏ{e3(vՎǯi-ll[YI㗫IZZy[2$/z<<<6b~[7-Itp~s7o7}%_?/'OdL>}GG޵[j7\$ZnWE[bBq( 8BM՛-$69Kirr$0Ry} w$P`]LKm[wJ߮eݲ`d;. {oXfV{5Ҥaş<$+íU٬''yQ#4Z?.Zvjs.zrO˽mYe#8")GOͷnqI^:.^aH%f(e f(421K(-5hwyy_%IC\?ԉ)s6guGe~?SY9 1/l=-Sh ꃾ\ ~2mu j/ԩK:㣺w'v9r HR_F;}ْ-k;N W8׳Xm[ֺٲjO.Da`Kْs>ˌҳu`{lٟ#vLyo a]ɀN|Ӿ=|{[>E6E1EV)}JXU'If%1}ש4|7'_W<=G/]4=Nfm)sv^X"" P`TsǂuaG{~;=r>˓44yrVtm+_MLd?ҾimY韙,Wz#^BzvV\O[4$ͲXY.>,7=E|7꼷2߯ėk֣ٓ%/.:(Itkg0,Sf/{妱#˓&Ow~s{r͓9΂7nTyYR?>NyqZvt/Y^Vl&zvqRzʳ@w0jLT?W[Tg/ǷǴ*1MXŝ48,@aA.[Yd (ZǗiK(m?[!90y]KSQp-mH+ 4pV<Pc[˷Eg eqz`k&͵}|7`;:m^'=AzmMkA|O~E*vÂӇzJ> $M/qWiG7M^^-<"?Oi溴 ;?Qۆy?l_֦uڃ=gA7(hA[|ojkjO6qn9ylM9!5}RSJ֒W-C7鱼]3ǯU5(EYmߋm;Ԙ%cт|oT"5=lsbfBqƌ||_wGCg6y},v\Kc( !:eO f7,?MrԗIŽ}$]~mQv3[X{۴^^sY=3g>ZNuDt}ri=-zcy+ _ѻSeq.ϴ/lsn}2c=gq7 n*M7~Җx"Xl#",nl7ҽC[pU2{?tD=\:)y w&@YYE E։+x\}Mzu$q'N)oi0nXEV7V&7*̹uy?f N ڻ5j{>'>;y' Z=2-Oq׸,?lAa@Zj70B7;3,[O`vlXpL,mAT[{3; o?|6>6"s&#T3|J˾n݆8ik[o;^u kEkKiچ{oV= >v/v-u(qX~(OM;wˢ_2og|@ƭX)jSrחHgLtWKQҧfg7ͪ2wL}_շC"Q^(ܤg+//:$WsX{ VVtnUmBZǶ(6JF$ \o|kQ]FgMjks%:mNevuӂۣ8w@IDAT|ݲM;uƖjat/xw\: *N4 uPSsz[٫Aab' [ ԡXRY;ۦKbu-_Ye|MJ,>>ն [S-[Tk(ߋPmEmٖc%\ׯߖםSw=8ۚ]bqkn gTPV~Jz[sE7d.qu|^/rU&;`UZIg~SYۺ>A{$jjObJ`*,N@Oֵ# KC8yJ^@x#[€eu/@(A\m%by_xfWǒnqkKWɆ2oگW]IsZ~Hߞ4ye6 @~Vbޖr> /_Vh^_& K-c3sd#--s( JV_ ajlcޫ6f`C[iDvDpZj<&*4׊u{[,po@wyDg?['֨(j), N}]NmHYmMn{~_;wryCy?Q_sUu˚嫯"}[^6ͱnM)7E–~dwEsBP)Kw³ ,bq-ԩQ|-IC$owKzsOfYnCh4nnBضi mZ7LXWŞ YV[{K]]JI8 pF`զvnݥ\2x^,UG9g8owM`gۖͥW>fwZ^@(ǁ۱ ĩSrHۮ-Mynےۦ?)i'"Wr"c9zn7^wtY4C%+(l|nתics`'-+qsX4 g@.͝{밦2yYX.c Bw^&h~V&X}'D-RM5j #? Ń^ս|wd}^O;,^,_󶿉&8Oib-߿Ȓ<Wۏ9֚y^|]٭-{eͧ\eeVV/n`Tf9] *T5MU&Թ*yY|f[q|~-9@b #mW^/Gi+/^M9n41 rҖdk`5/I䊋IXKvC teӖyצ~^4oOqՍ-O?s>VLv\=b-X-ɇMݵ}FXI äCN hߚutGIrfaT봶+g:udvVZG-;8w۬E(n>mH=$3r{S!abӵT| w\~Jv,q_Ҏ%wh>iĮmks vvozfRZ'zkc 8"O\߾BSZVxpx$YέSP[s N_wT߮D KWU]-}wUk(vC t.MSU:kQg-q(ujokHT+mߧyL ?+ g()<#4o ]HE 칑߭cNKkj=E:m%Q3iu88v3$/?_NJwYَ!9#iδqjEoZ{ V[`{`ϮW,ؽ!iӼY0iF*GAo7cCx ; ǵKsO{4 P.z|iA;X6k^.}[בg/_78un=(C}Dq\9 ,_gxS)PN#۫<;g2ϺWmf%ؙl?wx3'm}an]?>[Jw=R<*0vu_-k^yuL^䑶J᥅)GG܆dGZI;y.^uߺńA ҭes-Ş,DQͥi}YsM}2auSO_g%;K nY2Ktz*DeYMĵVvO{ظ؝776\MxY**:N@8|pZR,}zjMs<#:wׯVȞxBY~vyj{rr[Kx \׊s) P_' :ݧzMOUmh@fmJْP] FoAlQ), Z[iؘ!0ٍuGǶ?~O2MKai.f4=[i*wנ`0-H) B]y`T3+4P;E[ߩZg-kg_. xvҸ`{fjtq%ZǮj+f4F~`m_Sn`Q}/JCuYzw;^&C5mm=ښs_;si- [';ވثӯؓ%s6j1CdL3l|@8an0F~`K}vDYX6ǽWi45{Àܯ-4E 4ip/t捵 (@bEo+~Riui˵@+d;hwYV{F6G.o-ȬD\~WonH;l\UX =3X0_y`n'hf3+55 ٭Evikguy?٭-3<8]$ 5ijޥ<5m:.kDn,`Z {kkǶl%]o|LV.w zǾV2u'Iۖ>zs^-k^uߺF^|7\rE>n>TSܯq{17>g)ݽZJWлs}'|S i.^J޿v?3>>MU#@[nr?yZo~㣵 ]>  ?oΈk>TƬ*1rn{P#%Y4G3 #uu$GWesd8W.{}9)1hEVjoS{R.֠& Z6`;s zHOqlG{u;i:&5~0GrNKkͳ޶ILXN9,fHiL9ZJ =h૴ r0Wk=kA+/h?#?vo%X&A:l<ߋPm[?ߋmo34f]-]5oߑ(h{R!زCʝh(V:" JWf&5u8[`ˎ:>UomRۚ-)}oS1oVf[@@t<٭? knN瓥UcSsRͣz;M[c9K]rDV'OIfBR'ηaٖSoI+11R3*ʹkV @};\vTYKS_uG@@@@@@wD    DH@@@@a     #ySw@@@@@ àTJX%]ܖ!i%kU"}OAʸ##"?{C{Fҷm0=?@'p,+[Nl-.((Ҡ^Kv _7y L  P+ޔ%i]rx4-mf5zxEkvԎ);Pbxݢ{%9'}V2Ƿ: <@yyy2kR2q"h )2?[DgH.egCd7+e-so8+5kJ3՗;/]%ۓJcN(qf66oM97 YҫsGֶ{dҕwAi1 }=cZ/yec}"FP}kHcxQսaP0G`f-fk}iݤ\֭AB0Ek nTԢ:c(uL Pശx)j CC5qlF#Vj$X3k:q2X7ΔE+I\ 8-IZzlޙ(I)m{࠼6eܗ,U&KqQe=^(%1dPn+7oHRy;N\l\v#ɑcҪicR @I%M[Oh@vYwMHVlIΑTm _ҥy[heLX E%}Τ97D#<@lx w57Qbjy.-n.|}z\?t&)4ܴ]jh NmZʮ ti}Oeɚ? WJkz4g\m>][X.ZɎ=>!B9Nr ֑$44a(ye$yI Nԉ    H`{>g衃6NkTK@= w L.tyek7IBݷm暖I#XO,qZsx-|!2B9>9m6 No|G(=Vo)Wo*_\/  DTrD[n/柖5I$F4Vg;[ HΩ|٪ 0]]>|BNڒUów7X}in-HpV{i;5bogk7Ͷ딜'JFDu, R4PIV]s+CUIC=lڟ%?{UFz#t!z"UlDE]Wuu]]]m] XHDMzBBza9wr'30F23̝;m{'$9-sV іJ{9/BK#Õ+f} _iafFvqv︁F!{;;)(v,$<1=Ʋᡍ@|ȿ'e^.sk\~\Oo|F}ZkFUM },L ӝV%%7H]o%rwjwI#k.+}]9z˕U]W]e֪mt/WZ4/B% 4a],dK魻"HJ86 nay"/Ζ޾3ˊMX+) JG8s!ݕ=qEvvh\o7Zx?->UAolJN+Mx}`ڣy)IEsitu<^7Z'i|`z$dg6sO͹Ə>ޞAPj^^~icڣu<]'ۖE5ɩash)AAN#Ԓ{ XLnۮ|.Fo }-^1]sԓ?wωqK~1-?vhާ|?t`nז~-=礥/7n^"}s0_sH ڨV|sX@GGŽmԷ7 +kŹŻ> E됲$Ok;lsNh][ ;@"mMQksݟ٨ gkXzv6+7XkhuTVYMǎ97Nçckr=gi7QS[K+7mNP-mn.;Ň*`(}?:Ը+p @m/`<5n[AK8ׁC{xP4l8:` ͙ŷƵtoβlT~/3ssix ڃ9%6 8E?MC]ubߺ(ΤV䮏aJIT!r`Q7]TQK||s֡4p|De{mCwɜbCwT1J+g.pv㩆?S{иHwqS9VA%0uJ~n,bWGAtɘO ͧ%oyM9`9/qϮ.!X3x"_rt}̃Y`^/<:׃ԏǏMst8h:ؚMuϯMVkB:ڗXJ2{23S:~əi͂׻dMI_^7 y0t:1-\j{8WlGx9>|cɑ.ݓK/:7龱ƳSv sV-K)5l1=E"m{ֹU-̽0< msp{mZn, ԣOHT̚jjFjO 5G@ L\3%4 rhW-tw#~BV\7Ƈ^-m> %`σݚSugVKH`ݟ?ӳ]&Y rGt OqA78X7;\2Bz^ rrs=>v7FEM,g6G,C_˨  jn:Y rhڕ4,\ r@znV^ɈpΨd&{ r;qi OA.z]^5q4osS~VWr 8j6p~rH}O׃㳽tortcr|dI.REnnۜr XE\Vg2 )kR7_%f@M~Nڀu,xoRD:@.|MFÉ2tntzݱ<%-Yso74؛oVo&F7siu iF'^%t7|Ό3-,=9%=}'-{(Jst=OO]ד>/X.g@$ \{>^^9~wG}7H;;>}Vv:N&A!=Glzwh/s5m?xT{IYCVo&W.3_'Zob=4a@s xohƄQt)ъ5MTZ^|.$@'NC퉒hnǜet;b @殥.wbMN3BlN M# w_ʔc31ayȟUW iA<:RA?.5_t'Qd#=6E?6;yS6&>PBj%z8/-kAM?Iv:-,`?!E Anl\V(o}7zP+Aғ5)DΨ_7,${-9&K~.dK]olJ(ҭ`KZL5|YFfa & ӿ)oXwqB{gm}:j(p Z<0zg zzu I#k7ӷ?PJDrZ{$ظ Gb/P5o'G%ངU58tiCM~4M}pow7ps x9ʴ{}Z{Km<^P /,T(j^}g@hy}Ml_J|x2d)-;f \b$u&C}‚(.)~hhYSx"z鱿E} ?hU/v#kٟDF7o[q#' 'i6\^I52I O )]0 @pV}?,ro9VK-vصэhj 9:aM D_WAks/@!.S- sD&%ȓmC ϫۗYD \ZY6rI-*V[JȺe@L:2+<{=9;&:M`#,dFaE'3%f0 V)GS]}oz,:OW׶[egא示~>*ͥDT;3 krf:w1ܞ;wJV.Z'U;v-Lc۴d؟).9&>^@93Ə~\b2׻Π?՛BI2xiyrQ̑3紛,. .8<mIhqP6-m2'O*4k{K=q|)=@WCݦ@4`%MS5t/ bpӼ]/-RZOCi th_Zu0O}qvd뷅ѼjW}`u?ۓ (uj1o qwF%M 1q”Cŵ-ijYj*6z0wZZO KzVKd6oy^f'1iҵlCjު+)sЄ? 9ix $[{7Ĝ۔ LKg̡W+\r ~{48OmѽIg @LD@k pFS\*etdzٚQ+z}Zԝ3tmj==kz0q,m ]QFn}ɃT߱~˵஝y98iks4 X@ t/c0週9&΍-~=ʿ?1ݔj ev+Qʰ 4ډkmy)śOs%Lqk\PRB)|s@j5/ޝ1K[szVyKekr$#v2H iISxkh`o|Vnd`Х{5_/6NF_Uܜp&_ߚn|5Rue]eujnjh~4>K?W똺Bϟlm=@,%faH/ypm[&9lBR=WZQcsB5ݠ5/W_"OdcgE?96;%_gGnQ]fzg&-Sѳi=l)rS aZ#G 4 XeQB+0U}O(&UYQl2qVq Жi5X5cԔMQmqҠp|p_xH ڴ^(gypzuæ˧\qe![Lʯ Od\3UI245]^H-t^XVt_/.K#>kW+n=4q?J`KxQaHzLoͅwf+7yrx% ?&Xd2{y5uL]s1(ԕ8LZh `@Ugfo  *xསڕojYS)o'gf햘hhKr:|:xS-ɉD}PHMQhԺ/? 8)UÝ؇MwS)F)utXx0՛=KsFMoqR 7~ Up7 񡙃 hgnO{ !VD?[~K(o?~֯1sq%\䟋|PƁ}0gWfQ*ȑS:E4S lK'rCScLm!t9>(0ONW-ує{pϙ3Ro4Ψem}x$$^^&.wb8OOѾS?52߄wO=u<ٺ@3&SDpq*)2 H7#](ٲKBO.)qi~.^)]3xHS֯R3Ieijq Cyzx;%* =GNPum- hQg:W@.`Ձ4R,|=7Iu܃v۞HϯH-#IHo%ӰI}OC[L_qrb!s|Pd[IPG?v3s{R*"1 9X%#DRRDNqOxnj̢jLނZ%'T# j=Hʮ~A5+o t^d5igKׇ 6V~~VmCu>XR*4ɂ~^tEt'_6ow )d  y@3' tCV-@U^ `EV+:=8@rG'_4Rg?N? K*x@/4_Xc8Y  .t 36@{T->| @ho"zOΖ}^`{ @ .Н8v@ @ @V @D @ @ ,@wW>8v@ @ @V @D @ @ ,@wW>8v@ @ @V @D @ @ ,`gWV[C @ N)Ю]XVޮ X2L8 @ @ tQ3]wQ*6 @ @]Mdrw [[nݺ)sWBg@ @ @f m:A @ @@G@ @ @ %@YL@ @ @Uzf_ @ @ `f1 @ @ tT;~A @ @YtńN @ @ Qg@ @ @f m:A @ @@G@ @ @ %@YL@ @ @Uzf_ @ @ `f1 @ @ tT;ĮYa;fC @ Ѝ;H /q @ @]!@ @ t~;9@ @ @twӏ @ @ G@ @ @K ݥO? @ @@@C @ @ .-@w>8x@ @ @_q @ @]!@ @ t~8@\=qETZ}&DyYTwW傽i`< @ TTVQUu yyQnݮxuM-UTV+ژSS[KٶMW<trIsuv2(8H@t[/Ism^E"Ur@]CwooY @ d[vǍ^!=kçclB2UqZ= nN!zp}w7{gb"#J:\ oVs:v:iYo!w9IӮ=]];nQcϤ97͠M~?n90@*'jhݶ=q~ݑ=BJF}|mEE=gq/n~1%wt׃i@@W= @XɉD\.3R%-AB^&ȁv#FSnՎCI[/-,L@wXP X)[6$3֝%a^f;RA;{.ΎJfA1JuNAa9Rۛ]mVUSC|7|Ӣkr@VTUs "*))'W g.@hE:^[Tqo/7DQI{[BaTveҟK_(li3[i4z_sExjrAp]@$ 5R{Wzgs=k.ksqAA]ɁoiOھSE-?j?K?ޞAoIeu&Dzj;ZqX~@)XĮRž6mˢ7 Ӓ]YlU];hrCaٞ,z,U'@/ٙM_6&rFH(oH#o7{FA)e4x̿,vJg:rZ+f,xngC7j9Et&zzwcV^kd|>rԗordЮ ŁycǤn t <@ Ds-mzKVo̼ڷ7R8@WPTB'[v6c|z꺞zE,ŹJznE%>OPbƙi|'穎?ՏFDhv_%88gJ;mx?x6 /i+[yA`omg g&~t+VK8%M2.Wyg;7dO~$MӔ1i6|PzxbRcgK۰h9J;̷;} n]7VZvO=\Ozq&2 d?u$ `m|#uV7B~gX_ʙ6pR/(jDxvW%ZPZK_ rf L=^@RFEmG,,o snkts37}/gg58ݭϫɭnyf?3S Kp9T/LLM-ɵ"I>7rZF/ޒy}[}gh`ˬRJgٷw e~5k9[2&x}Sr=|^[g@e˽JI)g޺uuu%{ۓ]0d0й{wfpa~ zoi߱4:A}-Oj#fB@σt 77Ⓒ|q 457e>i*+|r[hIzi\Pmr%%,n2qz @}.lbr ֪듕%d\nBW:qV-Q*0lRbD~H/pV}4MʔYC\mGRp=G*#|KQ\!sWytsY5 |.qkKw}Wg+AhK˕~;MC)O'c[mp0o!ۜI-/C~ɭzsYSnsY{N6SYu piiqxsT? tw%ܖuw>NsOC590=zl9RbՏUwnt͐u'32魥( N2sKu` XyՋ䡶BrÎ1z{~>~nHM![[M{Ҩ\\Zu9-5G8G׍e4[w9LC&ۖ:ΥWҸ&I52 ,'>S7]?+\I򋚙&,c44v}:\; uoiJgly\CӴ@psr@]Mޜvp%ZwYDp ѣɚh3fЍoW. @};R @0ΰvwq nGђ7Q璗E떺׍I9'bf5(F٭z{r?̿ Ew{.~亞wLV6* w| h!MԶsnOv.sd\{ܰA%} @Im'Le&M7z7'͝v9v^e+|$QPUbj?FPo&8lpSp7kxz;w7-&]r^.|<55X To|񠟭ղ -i[N, @ڿ5uut>eN3WpAܸs\ v:P rK 1+7}oW(AҊ*.Wr'ZL+ZkG Qn~jf%[ļaXv(Ly2r Kr=[',j6LAu |ZvU? d~Em>Rw{_Zy-NɐlfAq_.Q2myۍ>ߗKj9;(YMʋ:)bZ 5=0ԅl&ҝKm%\\YZN u_yO\rF45h?|H5bWVuQ޷d;Ǔ5!-^1lP_y@o5 @V/ ٫;T:MNR%y蓒$-ivvԝ*/u}lrn񽳦РlM=nL\\٠Ԕ=ڗ#{i52+p\Je H y|uygU W_;`@"\5KƷԞrҔF7d(2U+qY',9O% WV @RsB8+ũ[EU1i y>(lů xP> rrEvTYpuFkc 󠻝,exPU rZ[K" vekrtuq5C @: ՑSsQ S); cys)_λ/z@e]s53luۭ\'ڝȟΡGtR%(D>7+C8{Poep 3)W?+ >K[m!=4-\;\mEY\K~MהXbJ곫 yPD .?<.d_O)PڽsfQ5A,Mz$ Գ>.(eާq%M4lHP^I}%ӆ4)ZsA><#88>s?č߿gj $g d'̇ @z9Yd|ZnEOz󳯩wHOTf - aɌzfeځd lϡ* k\ICEe囸++;͵7%t,sw9Fa2ͷRh "`g*KT/Ϻ}?^FE5d.oY(&șpJ9m9SDwQr^Er؋"??8+Dtr/gz/wO';K q oJ5 hRw px˃L^. >IUE44+tVݼUk"5_%X9w08fGkOD<5.ylbR)t!spkd~w.UdArW%'H>-ڦjlة-Ciq%/5uѓ?YGG* e)Ma>ù&EKV\W۠Pgzzj p0UIOC[zI7L[*诮O#quON^9{ZSZz4_ꌯM4_O|Cn pE\R_:HryUW^45M97 &q:̴J0C\H=7Zf6ymJ?t @@g%Ҥ %M 'cN]0VekަS{!) )锔]k-S'75᫢r26K]dY@9D*%l ^;?o-<8uN^|ع Cw{.\Cɣn騈?lɍ9m;M51~Г @Rސ~;y˗.qo j^흷Kx.!y;Zԭ$`.539ܛKsx;[CFbn%7p'|62%qu[Ils0좔h1OIƸԶ+= ʚ=PB2Ȣ~QMmCJͨ2x˔rvhyPYgɅotD7]\Y5?8X$ݗ[anʡtZrSDj$p{6qG͟R)z @ Hi^MESjRl8ۛkr4JVz[;53ҹD`Ur{o\6 g9><٫drw [[fz?#l ۘJ;ϓ`9d~K<,i8j"\ڢ%MjÜ&5RɌ:ثpOg{(kJo9]u.Slo W@6 -]R[~ @*'- AОʣZoAH yA:2 @;o@Hy{{@@k H /$_X@.͊@# # nl9 @@ ~Jvv"݆X5 @*tv"6\UP./ļj*L4K?Ο4Hsp9ٷ6Q#nFz\=ʇ) @l1Ɵ$W @W@+ @ @]TKaCN1v @ @ #@&!@ @ @  @ @t$ @ @ t>;9C @ @: @ @ '@w;gc@ @ @@[ @ @ | { @ @ @:t`` @ @:ݝa!@ @ @@Gn LB @ @@@31 @ @  ЭI@ @ @|tws= @ @ u00 @ @ @Owΰ @ @ #@&!@ @ @  @ @t$ @ @ t>;9C @ @: @ @ '`Gj|(6s!@ @ 1@"nk98@ @ @]Tvqn"@ @:$rw [[nݺ)ǡ>wʃNCBdt[I!A @ @Jtwc @ @ `t[I!A @ @Jtwc @ @ `t[I!A @ @Jtwc @ @ `t[I!A @ @Jtwc @ @ `t[I!A @ @Jtwc @ @ `t[I!A @ @Jtwc @ @ `t[I!A @ @Jtwc @ @ `t[I!A @ @Jtwc @ @ `t[I!A @ @Jtwc @ @ `t[I!A @ @Jv8Zu<>Ltl@ @ `u15Vbu `趂C @ @ Еg@ @ @ 趂C @ @ Еg@ @ @ 趂C @ @ Е^7W h>ft.=qETZ}&Dymϧ ^40ĥͶC `K+󗴼gEeUUאu` @ @*Ok9jz vh}莑cFxqM ˨}/n@/m{{4 @(p1-gQv~榴]3d͞:llZR l?OOلdUz}7O#?{873kl˗iJM30fvIOV%nj׍ECv+vB555T? c& @ݸT GwZPvKθkL@ftO(vd@ FU-{Ҁ݁b9IԌ,[wM3 $_/O*(.q(.)NTDv[ydFݩYٴlFJӯ~։y]WsTlh KI?-߆ش{?ݺjݨ_S{ hƕЦNv4sP6v+@%& `uj$[ n-& @WA@wLH&7W=8Yo,%d4?{ҳC1L-U"E߬1@k#/)T[WG%̍ IR2 \Iso24]$IL͠%7PZv8u'  @yR @@eCn4GpI%#:[9ҤÔ@kÎ= )(TiqkR~A:eKngGzhlo>,+ MwϜF=}Եޯ0^Rye%s9PZFTQ^Q+M<, @ h{1* Ae{mYYXӨϓS襛Ôݞξ6r/kiv6;Y3`s0Mi"tE3^aqV]ʚTϝ?f69}~%賽t!J;hkz8TJ9Kc#'s^\DKkkۍ}[}輨DkcZé=i"?ޞIʡ2͠R:#cǤ>!@Ҋ*#oO6ɪi!eC#[ %p!m;;HJH{uTVYMǎ97NWypZ\ϤQCٳTS[K+7mNh뇫ozЂnA}[Sٖ)YtoAQS[n @|KA$H= r:ڗXJsk6 ^KyutorF+[3#p_V_E7K%1םvS}8Xtݸ]Oory{l O)!ue<`ײjjׯ{>pRٱW ΢9@9|]héB|Y rm~s8>XJɫ/t_tk.7hk.bB)t($S!o롒e/J.#lUIiO%-XAHV>V^X'NǙup@OD]J)o;&u{x @c $gҗ~RJ7H)lRXۛctSNJ%NmXS肘 f ,Mjr ѫ-նx2ZfŴ,s$M3> ԣOHT̚Fo~'uyH3<(JHJ%gdw&An Ki;˙~BLn˼:l,A0ł(ِٮLw)JyLrPvs&q( vMUG`oqug|0UV7=ڸ>ONmL`{:}apUv~^HHn5$e1fsFYM pl!=]xvWYw~KJh/FX3$zx%}(]dܞMǤg@:@AQ of_6Qm|9 u@ '9pI# ǒ:h5=GO+7MG#כ/mC 97Fq8IPVa 3@IDAT~ƄQ|TU/H zGDƪ(ŵֿ[&tpܺޅֿ`._׽3;F4ͣUПmҊsZ{. oY}7Mw 'mF@^,w%d,Y2wVF~?U;H~M<ŝ>yxʷ% dϑc{&䥵[k:,09ndc oDyӯyCXlXt/v\]]=gLwSHťNSSי>>ΘsxOwT͞asfix 9ssyN)o[!s..zFTIJOs*7 tt-BخhH'1'=|R{wn6[[g\Oӭy=RzԪ/.괶M:#|Rtnکj S_Pea Mi~4/*r4b.ng?G5>y1˱@έ@Mm臭'AW? CUF V9St oڸ8N^z9*zƷWc G]ПѻJ}U#nMZ lrۛ+blViN7Bd} id3 ZcHm_/HKtHn*(Sy{ [gT/3[G)0>R#X/b7֪O4œkVJbyy|U'cܙwT\/c];>9*V} W_tlWg:@ߙ69@z^@}QM<)ϟݭ33E}w*]I{ܖ:ƍW?LN@en^#ХzԱU{Ҷ;ۘyfwKߨrzأ+YyZoɽ*w!-P=rs@)}2>:B^5IыGvv9ۤog@7=pZ, Q6mߝ ䷹B#J\mNJkL9_TiYm>C\| /(Y${~$lx]j-Rxu3MiS:{)8=nwҨyUic\Powim# ت҈Vyč]m]1f`^-}F~r5_|='׿ks 4.ޞ6@{k̓ݞc^U/hJ -u!j#t>L9T$k  @N7G4Xu~iK,9Uz۩2$47-F*,)эz?}HW Oifc&ۘ)[?~ˮ&idlR=\]dP_m *t7ggԞa>Չjdz[gfP,!C7-+[p7vOLc9֟W/W$􅊝Q @΁Ng Jj*v;RFDu~cUJzW"B:=贀^T[C:x6`ܦCz\SZFN5h,`Я) }S@w|^*5-IOU~M Δ0oUkNf74g|%QGΝqFCo;#Zfj6u/ 5;]FX6UjQD\f7-j6;nnx]T-˟=jaP[nE_\t{$P4l t2ݕ$IR_,,7/~^9s8y9fO5 *p<+[*%,$H";,^V__yG Zpp01Pf[UznZN>ج3nZGD +IVwȘg&!sQW)~*7Uw#9y =W%sǼc}J̈́H-x!TOU7 h,32E(Z8Hg)?EmxK\{ܨ;Sˤ@kRy\&\m3xb/7uNf_TBSKB~/nطdByeSGA2s YN}YN=]e'7e[F5mK#oOIR^ $D-cwqnafz1+'J̷(j:%#zɳ7 k 2vag= #TVߜ+\11X5N艃*u w }#\wƞi95Jߙ'%Hʩj)>-Q!2]-ytoܐ!#ýŃ{|+O>R,yjqJ  =~.rȴU?IfD  @ 9#   R E7@\^@@@@@^-@W_>:     @     @ ݫ/G@@@@ g@@@@@W iaBu6#   }N2&bA2 @ 0_B    8n@@@@{%d     s v@@@@@^/@_B    8n@@@@{%d     s v@@@@@^/@_B    8n@@@@{%d     s v@@@@@^/@_B    8n@@@@{%d     s v@@@@@^/WV#   R`hDD;G@"r%     T @@@MPrx{H~ y0^@QW~!    %@.&*!    8nG2 @@@@@.v1Q @@@@@Qt;ꕡ_     v 趋J     *@Q B@@@@K@]LTB@@@@pTݎze     ]b     v+C@@@@@ m@@@@@U@^     `n     /@@@@@tD%@@@@@G W~!    %@.&*!    8nG2 @@@@@.v1Q @@@@@Qt;ꕡ_     v 趋J     *@Q B@@@@K@]LTB@@@@pTݎze     ]nvբS N+%5VcvwqVxsn+z;2H\3|@@5RQY)~bߜUQUm˳:8s挔Wuho@| &;KZFԩϟ.2&6FV.D|4 @ ݷoFc 5jq U`2zX%^juVnO}!)'kZ #So'$$O.1IO Q$TnY`4o:#IjfDSKSxfNN՗;nfxwc@H@]l{:5ƿjnu p>Ns( )0{rc?=5d}p4ذ#|fr C7 nn?.`m[O$b@ =iqRfLqi7dA;(M@-`_b;Dz@@VبfAn}Fˀ@iV}h1eɷ?kܺ~6RX7tP`3uSjf qFǛ|;x*C@)t;eAV֞'d[Bq-{7XTQ+OU~>W}iG)3ƖS!Uj?=JNu׵lGݞT"E,N dWjD[wơvه tZ2,!vcҳsF.r[9Td]rXjNm~3].pBM7e:[~6~+\u #eribŲK-Rœz 竩6|-[vz%Ar՗ɄQ͛9=;G$}0rD jϡ;N@%@]\TnKrם*(Cd|zP6,KTP{':Z~~nUU7[CW߬]_,+/Wd|RV3&JNa*-冗$Xo=U{;m޾`J+SVfTԲϣ+^<*?: C7(7<%~2Yj_Mo¸>K>RcHTy&_>;Ьl@@zHN J`B|qU;_.nͿ7wɞc–JfͅjQJ %PQY%zC\h3mOG]TRo Ԍ٦r_t4yAGEHl#} U ?R/jqicGIL )RÏg娙9g:p~M\9O@w݊7G 7$ݨaxzD'+7rzJ|q*$y*%2g)bA|F[Ϧ~28qf#͹үeUu/&놊R#ݯQGUUo&IY:qrC-gc[%+Up\7_f}aD}&K:GɼѦ?Vfc5N[C aNT4u12@Uxs鬃@@YȤQ=(X4Bya]i1:/LܽLu?Gd yJy)ti:dReJ㚥tBMgΙXpD/r dEm|*%h˝<# >7ʙLe?mvq'CUꌤַ 1VPz{ C$4]­۶=Peu3f~Eg؜my@8}.}.1܇N"n*Q wƀ;\vU<ƴ@/_|܏ˍSc:5r!MX ~/on>eC|\_0n7W,)Fݛf^Hq-u96" =,s/=]#`ϿvcF7\jy{lZoM7ɤ#mܰⲋ4}@" TnL/,@Gp3rM98*:6GX*ǥbZ=`mZݍiM2K.UN@iVv DjVwM7ԇ6|"Iyʷ3:GE@5y䮬1ӑrkVk`a1ƣq9 M5Q=2zGͳ;V{i\UN.-أ+YyZoɽ*w!=9ސ[nw76Emw; ЭV^i||"*ƬݾKjr$yqb-àENkU%W-zn.yjTnl[os9I5 >ZNO^9ZS}  @W W6j#ԫ?=h45kȴO}W"zq֫]yϑ=*_'ja 17td[y53.uJe./$lyHNa?U%1tH66[86Pd-y-   Dv BeZUJ͚*/ 9挜*~XoO+Uʆ1y@.sFƙkJohA?\jq9SɾDn.3&p;Ѫi  )~dzOOu;?g}[Pe|k g]_iA<Յj@V?/nNY\06Y jOˡr̓UsY|=|wYUIqEL3ܭ5聟I4)]4Zג#͹صr\r׵ǔ  }EŒ.fqܡkwOqi䨙9CDȠvN]FuuIh`ZzN_6c;*^򔻨@9o~g| @o`FwoR>2l^؀6OQ:hflOjɒ٢f_rl`Ć0_~pQ_48Mwrpc t@@5i|ctJcvU}N@]ݎ~z3r&CcY}m\@@Sy5>'$}c     }H%}bvPr8\E*Wz0 %)ZJk%,]zMj̅*^(\_V]?#Ϭ.Ѯ"   @eiR2@@@@@>gK     = @9     @ >[ZF@@@@=)@@@@@O@w2     @dN     }ϖ@@@@@z@@w s @@@@@ }     {S     te@@@@@ Ȝ@@@@@tw--#    @     '@li@@@@@t2@@@@@>gK     = @9     @ >[ZF@@@@=)@@@@@O@w2     @=    @ѣ>U\ңd @_`Fw_@@@@pRvtR*   &p0)UU\\\_~</ v+C@@@@@ m@@@@@U@^     `n     /@@@@@tD%@@@@@G W~!    %@.&*!    8nG2 @@@@@.v1Q @@@@@Qt;ꕡ_     v 趋J     *@Q B@@@@K@]LTB@@@@pTݎze     ]b     v+C@@@@@ m@@@@@U@^     `n     /@@@@@vm"   }O2&bQ2"@ 0_B    8n@@@@{%d     s 9=%[\#{ʠ OV/?~ l   |5RQY)~œWHuu7@/Rbޮ-?G @oثܷ$N/<ۆr"8O<{0vy N"p$)Uv8")Y{JΨ'a!dy2wDquumS\|8eKxm:V=9ٶCyu(^>rd*0Ђ}dJHa4G$YḎG}@࿚Cs$0~Gc m'e˱sN "Q~A=,JF//O͓=I/$D~6ǟcz7\Y*~ l۴SB||PVV6n!kqSw(7ZM2;#g_~Mܽ @ mK8wndB|{X@T` r J@~~:ٺ,5M"Z|]kr[n:zPxb[m~>-c (S_DRTiHZ+E%]]oT/*-?<$ϐll! YQ@@@AAnj@i-?XK |<=ZgޡSÞ4)xvW?\׮UtfȟC՝mT?}ZfLs&7O9V3G@`SU_V+c[ߑT՞%5u3*ˮ:%a(gHQyD{IT]9QP-z<>n2r3˟kHZ] qo̝Aׯ?m:JY)jvg۹*qNĆ{Ej\2a:IA@,\T5;XXjxyڻ[Rl6f6 !pՂ92od_ʴLcWFfU4?eN ܠW?3k^K$j`V+TQcG fclD@tw#nomٯ2O2F3HF1wy23Yq~}PWAT|dC yP8^Cui~Ԛ?Q* m.iyUia/:ȫs=1<8Q$Oyby,^N؝Z*ؐ![J9{ߦZ@BN<ف"9MK?)} 9Dr(O!']FHDP3P/Tʔ=ˍc|]l(  m$gHJfxYã۬r8ZrL=B`eowQJ v$:W936R«Ҍ;&mw|yoU7r~?RVY-Ϟ&+Tpk3Ϙ$^yi5ކeR[_߰]ۯL&j\cǪIΑ F&*ڞ tkڥ#ڠܷ,<-T2e dן&6 &o~z~\t0O/:u"`"+7sY&oΕˇ F˧}WZJ|x3;V%Ho;@F:R,$;NFl2B3~ TD&$_m1sN|Y7/7\|IsM3Bo\HHPDߖ_=nE 4),I ]Q@IDATV}ݺO2㎆HjFlS/:o=<*BOd/]r& ?[w?6vD?xV/LRt[uѯ t+G?@ t5wNj/dy1RnT'jyZ5CFs4BVfHʳ+bdІ}3Ik ._")ݏ-0[ܧӁn=S\(71}qd6㖗 ",0WCr,|PR4lEJSr T"&J7Vj<jkzaCYUܴAKX.u ScQ}iCe,SprYrP}Q`}jVA _ull_`8  J #驗,|Vt|[urKEݶWYU7\.p {U Am*ko/e<|7w``m2wxYqb\U>T_$/]'r#]Tھ}+ fkwuo0u7,_nqz@.?Yp_@fmP#ȭ{g {6gSWG7>ΘsxOwT͞asfqF@\ @kY, uo6,W/A:o?^fޞjqʆ =+R]쵏>j@ GBMaGceV}#К@Z`̸ a1VUu/](Z-˕ZTyU R9^t=GIPm'oBժsխvwȡr !8F M}vۨNܯ \cj$if_ݚ#kw҅7Gɼs  yH+_|etC jILXgUWe|#u)n|Bytqs'o1By؍:hC@@ҳMw6i|ӍG7,״srEʪjy^/ }W@䗲*}h-,$H]T~{/oZIu^}W@GJr%o8%i%\ϩ- %F`uPceg ydY1||߻&_%uF u,-GiFzy<]$UNm9gU[:p_K<,m˓#YPZֹNVb9^!Ϭ*-ZwBͦ8-/o=)Y/ZYwRe")RL( ifeE(Z8Hg)?t-:xpĩ0=Zn|L-:dO!j&=Q!ۓK򯛆ԘΥ-n:3r񗫣%ݺ^  @},'|>U 퐔l#rْNǽe:d`HzI٫R5y:}/5!1(\Va2K9>j1K͔϶l׬IyoXM2nl9U?c:w93Tz=󼤬\/jaqcsAN 6PdPר P;}IoW]o&w* | wU0 1fWyHNaW  F}E0}Գtܻ0Ҽṿ hջɲ#xw GmU*EZYtSL)>-*{ Un=@)*7ݢ),͹Iu,_#*pe݇2˭f1k7K17"I$~n͡\Ns뇹qaYg@@ n'gJZV`uEʲsb_[UMhPS~ xz2)&xvnE/0ٴ:`:{ۜܶSv>f<̕}<ݓF7ȡCdΔq/>Q6ngbIѴgA@fL͋yo <]0bXlS~)~!$B7Pt @ӶR(?!m*7u T*G{yT:`_(jfH:5BC|Jlxp\k۽r^LRm5p 9D޿{ʚܼ0՞=l9W?VaIߦ u@@%!-]J+MͮQ9CE/וE*S3多ͪg&Gq]yBJT=+yED ,1LotWTZ&5'jW=#ݙ]\\]E/j𛟝ɂ",vґ{OߺU@u7Hg ӿt/>:;#Cc+g?  )n;cs<rz@@@@@Z uIk:N/jaY@N %nqBg9w8w*wJ^<*C. g/QKHV<8C)_:zަU]*.uo{@@@@ }^F@@@' u]lZ%y@@@@@|@@@@@z^}<     n>     Z@w|t@@@@@@7@@@@@^-@W_>:     @     @ ݫ/G@@@@ g@@@@@W ՗#    3     ЫtG@@@@@t@@@@@{          jݽy@@@@@|@@@@@z^}<     n>     Z@w|t@@@@@@7@@@@@^-ޗWV#   R vpd;G@"r%     T @@@M@bxH~ y0^@QW~!    %@.&*!    8nG2 @@@@@.v1Q @@@@@QΜ9co ~_ um6F@@@@ XDH/G3eX     v+8@@@@@>*@^X    8ngҌ@@@@eX     v+8@@@@@>*@^X    8ngҌ@@@@eX     v+8@@@@@>*@^X    8ngҌ@@@@eX     v+8@@@@@>*@^X    8ngҌ@@@@eX     v+8@@@@@>*@^X    8ngҌ@@@@n}t\ Tz:~ lֆr0UŮmYvRDJt]P1d*+Cwߵ۝V*'Kj"キ@ONǻ+y;C Kqix{y$B@  D~JR>{0vfmmx Y@TڻlUkڟNsk喹:J!yҖ\ybVxw|-ZU&m@8"9sFn^C'[ml /]جΖ{=%Dԫ Iq#eقͭɩՏ%)=C>2kt!B]bo(6duw]wu-uwbbETD&"@$@H!7;d&II{>qfxs=ON~kN:K]姷Pc;L@uٲ7VR3Jdx?j\\jˋӧψ7l@_ptlxEXN庙:_~7Ny0wxT2vTJL<&KLQ*3+':ظslz| Of tw zfev3*)q2r`?YiڸUf]tAeVd:LɯF,%*Iü!`U.pa^ݢ$^351=f@˘E%e#u>>@@*Ujbbd6{@ogOjمeSxJB;xim͖~ J*뮵_;FFE7]m39T=r8ft|wYw0GlI(ЋZ߉'HQiyT-Sj<<]={dyrDҎ;{sóN͛/iΣr+qrZW.2{brſ ]:8-Ӑ7 uoC9r%\~ś3\l`ܳ_MzPs__+K>]8=?$iQ:.Wbbe#mUj|c6@hvZ F>.3]tլSǶlr<5ݪl@wV^^,'3ltZ9-)$L~=Kn@}eѧ_Y5̺̭E3sK$D3*[ a|YPzt#Sd]2qcId+}3ZۛK[vJxH:Pz ˔`IHJ R ri5r' P?jsy=Gʳy6lmHQm Ǟ(yok:/ynvojTՠ){'Xu+2_??*^~ r =«n' ª[1 r7tAݪͯMSYI}%D M[A2?XBl_}֬7 oϓ/%u1奃rpy>bΗ yV]*9Ų2Z/mmfn9ReϱBU=ݬ2 >$)Di?'  pL=+f\;I.rlk i)/F} &~.5V)z̀~#u <)`y+ڔQCs }l kT[ aOW)c/ՀZԿgrؿ{2 tlرZOg \]1nZJM_O@tԦ{J1mɔG qu'9gs߷0NJ4הp r;-|ޘl.;AּZS9H[%@jAnWi&jo+_͑/oS4̼2+C䶯犑@{^W2=7q]|9$T7|p8_``eaw Ʌ5&m2 O 6K^L:3 4%J|VF}^Vy ^=`#'V.-mq&Wu&Cմ]oB58֝pr޿/E'H~{ʠ[ rɴ cڛOW}'qIVYatku̔kf^hv23[~uluHE2-ho>>^RZ^.Y:;.<# pZ]{_EMsz;o&d?B3v?o}8׏泦nm؎$ҪֆiVwKI*]՚v? qp\wm}:( ƞգo%sP@1ЮLn]},-8iC0@M Scm-T˖["kilw흮m|H6eJO-+jKlte|L?Jʲ#??Yz|b<0zJSߖ 钩z".@wT0Q/4uNi&@{ձyle~œ&v\ KV>i?&ҷirn_6ŪI?LjG 'Z:v7m:XKk]sJw:mo(SW}%ֿUZdî֣[d|l]3@Z#ù[rCzX=~3CIet rLۏ};Uսv10Lhq;+Ͻ4ý9)mg/oHX[ƴ84d{u>:i iܗ;A@i?ʒMN*{y ˥TJim$"(nJ0ivxR47ݺh QC <0:c~oI)l nvreii$tU:}o+WvfRsJeVۏ!Swi[{}E.c}D`5aю6uCM]SiY"5C{l` Yl94,  a:uIvk=zPUq{3ztl&ݮP넛9tj^=S"g֎3\z,Y5Ek5s^<2tZ2,Ձ-M`dgV7Ȯ5 @u 8j^5P`Q[kRȽ:XeKkv/Z}iZR4a3w'muRW{_;(e5޻bT'x'ުInҷ.[G-aZVEL'2VȩkF殃F8u\9"L]u:>C@ qcځ#Viݭ-m;GZAnSY:m"+'W$?8rXgSƎ^KIS+?I'R)]t7B6m(/WZ D;.ťV7Ww9gv{$$0 vO%N]䪩[ :t${^Me]xHtˆKNU}74Rks]\lѼ_=]LjJnQ(քlu],nZ([eWRecwU,3]`}h 9mvU}=S'ujvNe[^՚K. 8~nK2Y5f2]MѬ`Fm=gޔ烹IpIk'nn|gO>G@ ,f<קWR _;z\}DZVFk=hmM&!A~L/ cPφ[QP~ӎ˛Z'Ot7$H&*(pW%=+ۚ5@]zN=qeX}򋊥{>iC8" 9Gɓ˓%%({s~L\=+7x@րxBFlI(;$((r+z\׫ߦU?vz}/ڒr~sduf%AOe5So buL: bOlOBdfM/j-na:6W sXbXۏ{sd_g.XdGLb`;Z(ζ7e6,{§WEVWJ?^pϓ:؍umOy5z?>az%<#`eXTR*uZH[yӼ߄'}Θac~SLٟi&Xdi  pNJqYbg5}65N>%ťe2RM6OKN+AVyӲx0hw)W/@58&*EjcJM*#ZW*&2% I3W:p;Ô1,5's粽aduRÛ5?H?I^q(tы-gڟjI*U&}x 0y#/F~6ZWU;!99R^~J.];pZɘ5OJrLEg ΙR)\OHJ2mEId8|ֺL, C3ֳD!A)4][Adx--I8^(%݅C     4X@wX@@@@@t{Q      EG-3@@@hux ;2[Ɂd7@@@@@*@y@@@@h%[Ɂd7@@@@@*@y@@@@h%NEO7ώ2 @@@h1{U(;l2[     @ O@@@@@-@e?z    ym@@@@h[     @ O@@@@@-@e?z    ym@@@@h[     @@AQI}a~@@@@E yVuV@"@Fwk9     FFm@@@hk;_iKk`vA"*@F     n @@@@@St{ꑡ_     n v@@@@@t&,G?9*'d#5=Ruvy_sk@h&+$7@.4AEwo"~}Vm*1}dʸQ.=ncqǥL;>2kd2LD@]0Czvsc*{4Ys =/pG`}+37ɋSYy@@ |~|~c~B #OțKWȩӧ%( @5WcAy%R~{Jn]`1ٲj7.]j.@p)@% =U $[.ѱ{cs4]%pJM ݕ @@Kdھ|5[\mႢ"yϤ\~q2r`kVsWk7ȓ-OSj  FK@@@fؾR).A^~JN̔.j?wXˬ. r_5z[lQ=Dʞ%!)Y{ (@O<*'t^.EY"Y5"mVA m1G=6FN$QVjir- ^~?rEO{w0P_v\>ߕ-]D&9cry.o&ڷ'忳-;{gou~O[LY;%_%kFUPwʀy~N?k_ˏ˻3tpصC,I>SyT9R\978~SakB\>,?4DRя۸>8TurY8OsLKz^\7*B|-?_鴜 ּrO& S;^d׺ 8ڟ+;˨s 7eJ`mnalU=pDž[ Ik#C}嗳zJd|;KΖO6<:B|j. w^gkg+j`K_ˑu= ˧dh]dsBl[c#ǓƐϗNaa5i o.Jn)!A2NcݽdKNyQswo&oy97 @ΪwmC6vNk6 r7 Ue6YãZ?f KNu^L>4mx@ @ 01]7վjŽEY$HM$ysxKv`=[xRLf$֘הø5p{]}Y_ 17kMrO:& y m:o#>U%$kAsHM g\vĥ?(}}^g].K;ˏYAu=_{UyaUTߧx T|b$eXu n 쌍{?rux rowF;D2!{ݦNf̖Ano=.ڝc@uTEZM ^ ]X-}՛I^t Y梊`ebs=}~T$ ,#WE;ϴ|~!-qWt ϙT|%i|+fhe;YMafd= 7& ӳ9Y ri wm4|0&#jaElx=|iwksػn[{WCJ|1Y2J,.;!~zq-6@@79*_,]"en,, 嵏>_{UҾf7rȺm{4Ǻ\˻-xOw(1Ѷ-&sV.0+76g@$Vw/>-!}{5ʧN ׏l|yyN_ٖm_&GA5m_~{y[~g@h%2_k+{nZ2 w-Rr YJp3e@~u-ɋ?>ŖG.YZWHPҋnm31,7]"œ&nfX@&hu&i+ۖhxק_"]\рi&#r%^Syy޴.šTbg +˱,.ɝˎZz%$[?FYdIۭ~V%=b2wj61Lkor͠aڄaWgؓǎEQqd&T8Os6~rIr6A]N̔E:HAݿWFWks4@%ݭ𜛽,,6\fF Wd@ۃfq4{t;eudzWy4Y#SI-dYy8rd/޵,I˲S3C>@+W&DŽHH@lxJx`M'ʦ485#ֳ7(~j٘-Uꋷ~|<{@hW]4Y.?XYme'hPVndt0 ]';tDٸ]F #S+/N۴{& J̙).-]PT$&չhXH\TFkowPZ'GSN)ͫsYO E к' ׺'Ru"|D/މ8̴{F2p2Jt𭪅h v̀MѺ;ߧ˿'F^n'NtɅ2CrZ{mOrTG'z }廸R˿-;nV-sjq>t?;A@JYUKk @ڠ@]Y&'rL&cj:r]]:EyjEwv^sWonʩZuIXpe64{у/>&=.κ<]y@Zn^!0uig4ԓ~DL[OdguЌ/))=-կ3ۯ'ûwai%ۖY=8Pm(ttYo;Moy~νmszj{"C~Hƫ<`+'gMim:01*b>a7 Ւ&]m@od)sCօG˝%s|\8yòK   G O#I)zAwԷwm*ekdXV]Sk5R>ZZ WSc]ycu@Bd<9^"t ձorЮ UHM:O(+Oh|$_2u[]5귩rm 3_2xm>1vj1=/i=9_s0W.Nx6sp^/Qrm'ľbJ?Q$q,dg^qBXnt9tXʲe~!V;%ݍ9v&ʏYYIj:.:u goWc0Ls/..nΐX jxrL/W_fAWfPL%h.,.ا  @S R Z$,(HivdCU86ea shL_~IӁWp5jz  #$D_ 2kVe =(xXL]4|qlJ]; "娖0mMeªTY'[|ˬzk0[b4(m{ dh3sM {t/.BGS43? s߈#i%cDty2uPvg;&rھyc dw2m(ҋ~y @C:jlnW:}ޞT&4t[,LvHLT@ޔ ?Y$$VwsmWZk߻Lzض)ajgivqP_1AYws 8chx4:s{=iG6ױ:?UK\>,  9/~%RYI>9^ ]跷?`]Hy^rהƗJg@@#PZVYՙr2CrKdGӣu=٥!{𱧤D4{;S˵|wҀddHv^JhHt m q?q&ehrͭGMqsw#-Trĺ;1ӪV)NFjl6j0b~3m Y[x]z:=GʡNz~Cz4I۬7n>e\Fo~@@$m xs4cLrMy6_?;)@S:v~݃{JG3D|c3U"в>5Ho{A@@ڢ=?ZNrQksGFwk;J3߫*wB@@@ BhǙ=Dпwdtsvڭ5$2GF U m\N/#up;/:h2 _~~isJі;eB'P!   >Z̋;Ξ-#   D@ Jp*     h-y@@@@@ݜ     -Z@w>|t@@@@@@7     @ ݢG@@@@ 9     ТtG@@@@@ts     h-y@@@@@ݜ     -Z@w>|t@@@@@@7     @ ݢG@@@@ 9     ТtG@@@@@ts     h-y@@@@@ݜ     -Z@w>|t@@@@@@7     @ ݢG@@@@/AAQI}a~@@@@E }VyV@"@Fwk9     FFm@@@hk;bGڷ/خEЮ⹭y G~!    %@-&fB@@@@Tݞzd     [bb&@@@@@O G~!    %@-&fB@@@@Tݞzd     [bb&@@@@@O G~!    %@-&fB@@@@Tݞzd     [?2tev&#   N?d@V"@Fw+9     Vt#~#    D@w+9     Vt#~#    D@w+9     Vt#~#    D@w+9     Vt#~#    D@w+9     Vt#~#    D@w+9     Vt#~#    D@w+9     Vꎳ xu%L&A^_a-kX'SOip3iZK~SXT,%,ڵkɻB@hV oavӒWX$AFV^)p)+?QA/~+(s(>B@u-'G%DtdqX4n?~Zk'[}R\ZfGzu"w̺DzvjQDg@Xq8rT)F5_$'P|}tw6a>vv?(v1qBXHzLI G5ˁcT:Ez-ϐ5gB0;Vm&qǤžG{Ȗ-&pmZdx?j\;co|CuzVf>p=%kOuvKBRҧG/.;B&iS(;7OR|ԇ[]Xv<"ϿGw:7KL+k7XsLt79tD;i7^2UYBA|[@6QNL7^%=:;?'/_B=ﳭ;/l»#6@ZSl@S,QKN8 "O ]ïVɉҥS}Rzuwۺ r7rHe۬mgH8.&ӗz%d{򣛮9i鹫]P#m4_nrt.`B 50a`kD  vϩE͕UP&se\/vkܔ;bi-۾.Snok+泿u vnRCbeR7 vLѺZ۴(oV\Z*I'VYPT$G븀cJyF&Z|k6YãK0i;~$-=.朿o?LiU_rwQѮA^~\]"%VFdD}$&*ǜ=e07wr@Ζ).>^ZuB*Y SsI_7,I6nδʫT_jUeI<2E99J^["~*R+}^rה. ^.EejE0ք<}2)&X>~hĥɣ%ȷqyֹNq}/kNqxSTzJI|=pƐPy0U4|j68mkko5>Pmgzz"v7 @O0W>X*>|ߍW!-Д-9-Z].Υ}5:CTgS""=;rp_# >l޿;"{7 DuhWXYݝ%",ԭ՚vұP15ey2_T"L'wXeZM%w_{EJdo􎉝R߁[Xpf_)#GSd!2mĀ>Z7>@ nyeM\go˒2W6!ҚT*(gT} {aWt8_>y,{xtFnl~v\"};cjw͗_]dw-kSտ&TjMGr4#c-O.*c{I8w鴥d^ieNab_oR ;zlxXߦ?9&Y=%2[>>`eOt\}5~hzt 5%GXoN_>=&C{v|٨QffF2}p p m'>XcU#.%5@y?AclFK9rvd7OY|D3˘m?R\llx ԋɚPh`>ٷ\ϯTv%\0Yk,Vo&X1մ #k>L~tY" &g_9Cdwl@wdG[ar*ޒ*'eWؼ'V cDhu$sE:iʍۭ 铭@wfm^6xF6Svdѧ_YTΙuYm9M7w<ևV[.HW9\7ǂOIYuL?oAnS;j/&  N8 v`knNn+OJ"{G@x.{]53M[_>(嚝ge\`kEZ٧ɔ1e՗j Yi N` {Ȇ$ tlرOg \]1ߗ[ZM_ l?gj 0uM9|\[81Yڗi : ͤ]pom/oɰJb<(^NjV..׼P3;UbdGbʨ6-ֆtk;x` /t:yy@72-h}$[>Ln{5#m->& ӳ9Y ri wm$k_` k[>dzLAn3VO@rfz;߶h1}ﭽ+fڐ%_4dۛ#NX[:gwjj r_>o`f7uْU߬mɯ%}le|ΕE@.By5 jmb]Mp͔vxշHK~M6Ț=о_zҲr-+ZwI,ñr ٶx-/DVk{v}i٪S<4h؛}~{ rO$eML˧ogs?2 f_/^Z)zǞHa_ +Ta1֘oS{XxMOօ @cZ]1-uYSٴؤ"vպ\ Hhf6Wͱʛ9+L3\kXlM9%ZŴ}!_khoZnD냊@޸a q9T &vح_|`~-  ^mso,iV&;An#ֺvn;dԦ rm /ۨ33KfWi$}G/3Bc mVn}P={m4=wJu\aJ ʃ,[?i=E?T#jbxo#>1n75t o]g\մsӋ%W cV&}iKll 0J)mL۠5M\+1leCZim̐?tj{ /ь4M%z@S Uˈ=.7ey3Liژf2 h='YˋIKi^sM8-GSt^A{Ӓ5pU&8Sӳyt^J6XA&{I\%&&m껍2OOy@ +ngږ{vmjگܸMko,A*^'jz6[cž>>k @=RٺǗ!3 W3@O8dݾwJ,ޚ%_ˑgW$g.ࣺ6?݅$ޭSaVw_ֶNzPR(]!$$]s'w2It\9we2T6SRdp57>ޜ =ؖ4,Mpr!Sea g'' RmG"J^I naWMnor|TbWTV7ehۛr -OoPzwmџY&QJS3fORF kw~u#϶ (@ Pc$jV[kQǎc=W&}2۪ef#61E \di@z0awVAM4Ͽk̟ea&WWXPT`uT}ɼ\NN׫iUиs uMΞ*DZ&T ޺ݫo©e6 P\0ǶsՌ'ɚ>Tݔ$l)ĴHTڅכO-fUo߻ȤgԂ~MͮI:So8nIO!;:8OQ/bf8dUIZ7Mֻkk WǧQdM\쓴@u%~}8j(vEx T&|e-5^=6p(K[^}2O0rӷM^^}vebL(DOt2''kڇUK oǩ5m&tnRE^&H)]*YYp cNvWusۍtׂݪ=^aqތIڏZf(@%@w'n^4%D ^^4U0u!-KV~IAx syaZ_kdؚNL7i*(l+{J1Z7H 3><ЫTcVAl 4C7Jq R+7v?X귂j}bQ2̓jByØ~얉 @/g_^lEݗޏ4ofFoc O q_.f6H dSObw]?ewNOmMX ' ; _.ąKbof^!M&(ɪyud⠣X}03;)0,Cd)HL`(p`yiM؞< z;a[ien {C>3I^oÞ A|}]S_?U/?u T^[wQJ8iZ59ywm7-^2J(@ P3 Unӂh$8W@IDATطi?kKmI.U00H2s J T+/%uèN>9~a!pws1a- 9fP\u͙@Rer&ugfs;ly f g`~ CHn~V[_j_q\mV탚w],^.P-/~ž 77c&e Z`]6L5mN7c+@~Tsuq;fh'޸/:j_X5_-7$\kn&_a=$PG5>W>C yP`-\U߳%Kd%;꧱[c,:Ât\0NԖNٕPlUqi5V1Wi!/7듙wqAMB͑X$Z >7]3in[JkS?z=W潎1R;{ r[^VI&؀-mx74j (@{pɣ!>`\fo}P1R'k/EP˚!q|J:~`iӲ'Λgϛ/%%|Pu2a U [EY l}|@7jk}-wl]{TxI"er̩g 1Xe3Gi1o(@ Pm$MfZW5yU_WRYi!um4&vBU"FJp#ܠʀZԭT\NL)3AuΧaeJy* զαDmok m2̇STƸmJt󩠬-mP,/z{CP%XRJ0\_o^gåAeJ.<jTl9/Ea`ȗkQu ߗɵL*@]֥zi-/W P:*geu:@o< ӌ LSdHy82;?UU3BHlkKLF|Kj50^8@gP%c=L Tdgކgᣒ"Jo}#ɴQCr=(@ 3ϺΤͶ[: 2OS e!cEK~li*<-4V2JzYʆ-̑I5*V,Q2QiS zlη?G P ӳRbR6A4@~M9ĸ:v$\ϟ*O%]3+(`O趧eefe-WQk xa޵x(@-2ySrqE=zJJwJ س3q(@ PCU?þƫ{!S(@&@wG#MO+xw?]3͎LSrנW3KSB1eFZZ؞_P7cAY,ߙ2QgaLNF P@p怅qK$|wWD P_Kߜg(@ P(@ PNXNnI ty./@ P(@ P(@ Pkq(@ P(@ P(@7?(@ P(@ P(` t)@ P(@ P(@ Pn~(@ P(@ P(@ Pz<(@ P(@ PhG'Ox* P-֭[;趃!R( ?ޝS@GPmOGC P:kn> ܅(@ @_Bn/y(ЀI"{ 2%A\M P4 dqk3Qgyuz[KP(@/PtC)@ Ph@uu j /kG-@B&N t0e^2wlQ1(@ P-ȭZ|(@bܓ@P(@aq)r Y&3$ט;m6SqG P(uAQniv0a럕=R(@ P]E@]]oOwdd ΒUn((@ tTVA5'7Nq\(@ Po *ˤ܆TQ6nlr(@ ر@]6Lĭ(@ P(@ QU[v ۩_Ky P(`M@ϢPdvcQ(@ P,!*v[zD=On957]vOD P:5%\y tw;K(@ P@puqH۱n {őP(Х inU[6F P(@ PԼ@'ktN'u1m7ç(@ P~YݒI/p(@ P= ?C, tӧc(@ P _/dC P(@-pǹN7Q(U*wI P(@! @wXq(@ ta= )@ P(@$`Yܦd tjp(@ P(@ P@0 pwnH P(@ P(@ P3 85K˛z(@ P'n^SSjTTVۻN|K P(@ P6 0&&D P(@ P(@ PUݞZ8. P(`7*8;2nnJ P(@ t8(@ P(@ P"@wS/(@ P(@ P(@ t8;-(@ P(@ P(@ P) t7ER(@ P(@ P@`(@ @IiҳrPX\KK}Ծ~s+RdYyyYuMMZghʜ(@ P*@9(@ o/k%iρi i|=wm_hfEzަL+keF P(@{`^M PZY@; -zUc-s(@ P(@"@wG(@ P8'OĎ-FP4:x As(@ P(ХRK PN-yA;d gd確&4[o-lyj]gBkMO9Tl1VUUA]JUGnp? P(@ 8(@ PJW`ONCLL=(@CQVUҤoDwm[} I 9’28;:bpq6{~=nbCn+? G'bTd?+M6fZl?xť߮6M[eL0 ڰFnAl(@ P= 8}z5ǫ)@ P'2c!?{*e22"НW>e8W>^Xm7oߋ| c_>%[L|: a(#]2kR2lkEE^] S0aP~G_w_㾶G?@[ ~ R1wO qWSƯ-{$>oLk5T65u=()-óK>CJf"tڸ1 ڃr>Ӧ2uF'wx橮5!9{U vӃL P(yLcwAftwޏ*(@ 4I`!uv t=h֗*db]%پlK NcMy:vŷ;ꨖ߿/W,?d bj] l_6nU5g<>g⩻?o t<\߽M^;4]} k1o~\2ʃZpZ @e+3iq\hn(7mKU r?s2.;ݘ!~"56P.S(@ P X~(@ Ph o)1rPJ钄T*e2ypIpdn r;6H[<#Y&njxy"#'O zyxCͪUUטΕw^{1ȭ=qzAd9я-zx׵6ktLc[PoN5}&UYglV۬Xcv.7䶶/Q(@ Pmwc(@ Pm$0Iڻ`(K`Z7|d0;clB ٶ,ɈV-UߨjFu6ׯ˭^j3M'klxN8kY;KV:Dߧ-_UvvJz6<\]dq*NmS}a\R߂z5-]q<9v3ʟ4?S(@ P趷;R(@6%gw n%.2I}+뷄tmUJf6oFDHd# P;GQ&M!p=0icLשnBKИe P(@ س|8v P(.N?b6ܧ|VQ0t LeYA2#;G멿M>z{zI%P+(,QvB4,xg'_ӤLr'(@ Pv'vw8` P(жF dZrcDZ=>⓬no˕kҺۣ.}:kU*3\[3ssQ$>z x[&fRf}|p_.3&o(@ PNne0(@ P=BCwkO7ceM60}Ё4嵻7>(߃Z#qr]e=Scu#ʌ@jZ۶G nTy⊳@H)s Oap3k_4QA -W^%~Ҳp% dyJ P(@{v4!xɨU?kF~TCi49(@ PߵOTUU^q7iDP/Tuk4eSTZ^fYܧ꿭ssֶ*)ue`7wؤffWxՉ)W&'e"Bul(@ P]W`wQI q*URLvx8f P^(@ P(:[]Fr)@ P(@ P(@ Ps09#(@ P(@ P@`z^8(@ P(@ P(@!9.WYyq*Ą~>jIhPX^i~rsl9{aDO6;';(@ P(@ Pm!@w[v>rptwĔ޸gAF$Wٵ.Xw+I|>bC4н8J@ݴ]31/^t)@ P(@ P@`ߢ3Լr\a$e#2 Şb>?c 0J { 3/;(@ P(@ P-@w{NJ%+߉ւܗ k@ڒ%oGW&ҷuGn)HWT& tA@ P(@ P] p2J]3mZل.x~ :1g/ʤY3 (@ P(@ P)>NWLүGˏȕgi?ԺٟjNi%p$ePMij#)%zj̎ТSKPZQWYqbgsTrXScw1i7/Cs+]-OuV'c9 JڭEJ*Uj5,E,7lj2T7rp_'G&ll_sqm9ߤWF<.nxL P(@ P:Nt;tƷpvֳF[=aZ2-R{1~09}\Otp*xef#] N|98X?ozX/ƾ7kyu)xdE"f 1n?3}qńmzvvL/oE`4rOθ#WO'p 0V+>M*ҙxeu5Lk1AxG]Σިr͙H3<P"bX85lHyxs,̸Ochc^cSs-9;}Ao]8(L=LxHs Kzk7ނGW$=(*1<PߪX"ű4(@ P(@ Pv(`k!@yU1HoTgvLrsG2{y?'r*px~yF(RrqQZwC}WTbk^¤j*ڵB`ZSɟoρҤuϯJĮb yv(~c= by8ŋk0;L|)S{nyxk17L.UGڏX[Wg! 򰢡cTe'UXC|q 1yib3pC>XyP-}/DrBb=B}6*}☖:Xt(@ P(@ PNm'7!I{5vRFapA5K;7ꎫ@,YN"Qb$cًdv0ɸK:gW%cKl!^/d^5Ye%@6YX)0>Z GW2)c3{l}^8"g}%b z8`#3~em|3vSc K&⇽)oD4*uhLoоߋ{3 .ߒM2 >]4#ɆT-nRE-Aw=La:1Nq籈I/7~'`s\VΎ08o\uFjSAiYփP+vgUM))ool r[Ԃ*R*!iۖnU 74:FAW ?.%yj Zw-v2@^1ȭ֫,J6)R|(@ P(@ P(pʮ=^9*.NG޼a׻y6pTD!0[.uU] k'Xl閴ͱR"zzXFyW*Jt`7R6bB.TjAe}6-$kY65⑔R sgjA H֭^'[eߚp_mTdwѴ`s}{E64LXW+UZ}"|'$Jk9ml|,Yҟl|"cv WT*}@;PW%g׏9ի,6m@=(HuNv۬P\4ֲ2~[K~J P(@ PQ&t1y8kդw%ܮ?LO*V%@]_?e7V 8N/Rԓy_Ke'>ޔhj&<-f *baJ\ޯdOoNfzXX{\WXmG=:"SܯxM21IM0=hӷ^VUPW]&!cvמgL6APXUݢNlvr䙦ǩ8Uymk_)@ P(@ P= t@wkg8Mm#*)PeU}ZVmɒU?zKzXLWk4Ne09X<Նߪr=.ugIjN W jMgnIZw_djY0rM6-yUjiJŴekkƹҤ&B̛w{94gl<(@ P(@ @ t5XWUꦣUR2Kʁtvu6ddk-Uth6V8Ɋ[g?3b\-иXMkO拧Ea?]fKm]'Rڲ{ޕm6Ee[:ݛ`oq{9r]܇(@ P(@ @i=ly,cZ۰ST[MjPVXnϷdMS뢥p;p?W\;!P$d,ओdA_9r; xv0daLI|1}q^^ѡdTw>A9i5+$}G]`0KF'U;0:<*/bfȆ5^-V%i}^ ?[w<(@ P(@ tliOǾ5U_e;db¥[OP0Q2az ~<MV5,*lۚ|Dz]H.eg"яCˎ7 w:L.*|{B hI]랁hy/Vf(5˩yØ~o46~}]ql%2Y]a?vCKjHj )cWJm~D*c8&GcT뵸tp(@ P(@+Д=۲wAr|>a1q@r155X4=*;ZoE|w.~0.C2ƘB\4& Y/w7r×#qGp8؛}$ %@dZ/QᲱfgv &Q݇W$og`DOCcY1==%[_I7*<< ǰ$ʃ'ۡ<\1>s^Ϸf`CtҚ|xUΏ;&Tc >.Ñ峵HV[]j?\4;ޔL$C|0;DV9^~.XR۾~ۗ\? Oxa?ޖ6;4g6 zy1[:R(@ P(@&@wG#dVԠF~+*0vhž\yT Exk}d˿EbSgfh 7eGU$:9J pAw;uTV%X%3zcJ*@.&Iv{πtiUrC));ބ *#yVʄnbVDx[5ɢ7oYe &kEzݟyAZ໡m] ǀPS&n=(LZ5r->.ZS̍ 9<"oKdzZD3(ZF~PYQ ooC9.C P(@ PvENUAbxUڲ҂=6ft]k1{8bjV'~ɺ^+*ꧩMŇFxj?M=)`g۲_[ܪ?geBHSNլ}Y2C$ڜ>㱥dF< QApUrg|6M<Z=ZԌ%t6?3sߚCCr=(@ P(@ P,,w#Z'vC t6/2;uz(p}y^ P(@ P@ tN(@ P(@ P(@ P 4\g?MLhI@2PT&Va~u5wD"#{x݌(@ P(@ Pg(@ P(@ PZn}<(@ P(@ P(@ 0(@ P(@ P(@ صv}8x P(@ P(@ P` P(@ P(@ Pkq(@ P(@ P(@7?(@ P(@ P(`N.@_^M؝)@ P(@ P(@ PNLcz,Nne趂U(@ P(@ P(`?F P(@I&߱Xdf.>t @G(̘Jh*<U5pC-ڿRSqt\\\cO7/;u7;#,4'oO~''p.(@ Ph@ť-<%(@ PP'OjAjTTVˣ"(QAv֜+{_3GM>AvY{7|$~֜U*mrӺwqtEEq!g`sVT7n1s{6oʂ3v\]Cc/AQǦt&jc/+mrnO*OH P;K(@ PI`ٱZ{dL p.DjQ1]zU0_7 r[M鿴X"lkXgP/@w\qUig=3%mc٨z2{ߔ7AAW&DU;߿)]qf<`ތ7s<=<=ZzlؼO~B݃f\(@ Ph@UUK6 P(@ lnIFI "h?-eG" ]t\|8hkI A#ZvSݔ$ՂdEpfȚv!gǴ@w7 J9tu<.oYp`bN4ONMC\b|-yPZ^4͎{QR(@N @w'f^(@ P@=UZ@gҡߪ+;3viKOB$m#$0P jB$X/wg2 \N̔ 89,JsWcFfV6BTnxTߦ--=eڸƑ2t J7[h.z֮gV"#;G/$(r̚U:(@ Pn?k(@ tX pjKDavv$z~bmӲn  6{!4W {“}'r;hf>#=<:cIY2" _[vA^A\ppKWQw~u"7@x1j}W[ƣٲn]\Zk/^W|<h5u > 8s sMlٽ??,}Ev/lu݅%%xpXWKo fG >h O<!Pxy@fj69;|Ř5iLGK_1o6 P(@`}y P({6T6 v-g9}Ά_cƆ#gae6PmȊO^;%ydd_ظm'\\\g(se2J{匧e5[s (@ P0͏(@ P& K\ Pa58c8L>ުRjIbgm\Xedϛ9UnD o/ ʝTʤELj A3}x>{ҿW~jgݱ,z9QǩVV^7y5%%}<?kY CO_U>3Peޫ )U7sc P(@v`]y P(`?B!Xsw"ɘsE8;XWAhl.F/oIyvCtUS~0nI]n"H^M7ieRJ2jk]ҿ~ݷ,B^jKղsݚ5[jNK ߾TvN^qyxwo?`ƤڃN|y@$c\<'(@ PJ twk(@ P6 ;? 6O_kуc7Gu–}}Sus?0ڼ I-nj$9-֏Sצ5O/}Z"5U+H9яnϚ={D+inxmɧ8g xֻl.R(@ 0mw(@ P&#O=j5mi[{"8=OܫGvsMiS#Y&TmxDuR X7w1#80.P(@ tNiR(@'Ϸv9GBe~x~|gk lĐZW %LZ_[Yj۠υz ōW1q($Ʉm>(@ P twˢ(@ PMlpǵma s7x8y`zT}{HmzA!DjYfE8WUu5R$빵۲}0{D <}[G]-nn@IDATз6̬xZPVV.uϗk ?K P(>,]>< (@ Pvb~ňʎ†v3- _bZqV nN t?=bdt /9!ƅE@T"6/5sivg|Q~U˵rqv ϝ6ŗjݟP?8993ꂻZݖۻfI]{~vs"48l}s 7.s(@ Pc tNH:im'O֮OyE/l(@ P-~ג߳jjNJ U:掬d5#oUC.Oq%U%H*LFAE½v'.)-Exh_aR s $Tx<8::"!Af6(C(@ P;]Qpuq&vt ׵~sϞ.n{[+(@ PF#; ./(@ PhT9e;P(@ P(@ P@G`#(@ P(@ P`Q"@ P(@ P(@ PY|w86 P(@ P(@ PhTF(@ P(@ P(@ td;(@ P(@ P(@ PQ%(@ P(@ P(Бwc(@ P(@ P(@Fn;P(@ P(@ P@G`#(@ P(@ P`Q"@ P(@ P(@ PY#c@S K-␡wXUV,Ǘ۳q s*%$ɇC7% x(@ P(@ tM}WW>;aqm_X*+^YhF>_W.y%5ڟOwGy.!ESjgwx8yhKJPRYjܷ[nput;9_FRQ2 g^RW]Y{V>%t񀻣{6ûC<4jjjp«1h@{֝{pjvٿYr<(@ Pt?yM/,ݖNJ}᱕I؞P/ꁑ A[iP_l;^F0i(ЕYBˇƺikvSlHSd|uvpa89pvp6W ee$3$%¡d zz¡ͣs/7 Aۯ[#8LTM]`\%|89m꬝wJLI^VVgnٳ)' P(@`$i,ֹSzQ (D!8~47F r 硛O}#<s F0IāCR/c;*0T༥\V 6|}[=oDq3(@ P@ m!ܙAL5FvQ͝s+QP&f5|쨮%)2)ɓuTPRNj\xM]E^{{$~GS_I%P6=9^1ḪҤcgU#!S>iEejgT8ع蠑ef/rG.myB ZBԶحgӷ뚻",$A[P?Bj7Tr!ٹ WuSdž;hNj;-=C*oSƣ7mEE̖82s m4ǿ~|O P(@%n~M`L>E\:b@1ձM(2baҽaVkyUx |#Gk|>>j\Q)DI)3S{g+ΗZg¸s# #ߺ wVUtsG%A^V{Wfr4Nd{gqD ?$׳5T  hƂ !xy8b!EM(IUk!E~0~U/pRSXutmz+ |>3/i:WwL:#x-zkKR @g Wчm|=՟ N\(pʫ Pm*X1Dv׷Ld馕#1EJq JJ&ڶ}5)TWe!4fρCx7Dn^^|m̧={㡧ǞC1y<)L'e g͞oZW'jIxwXi+r $PQ͚-l |j .~6؃,m];]Ҳ2|XZTT:/8*}Y (@ P`T:jΑdL LQeFIw}3L'= YW/CUI sޞK̕MR 4=UvOc%}<0{7zJP3)dl%3C+RN´^mdv;$;Yo:vxoWdJ>@tXE}W' 01ϯNG toxɆ~-̥dyaZi3zPahdKg<]w.!ګiFf4\>>P231'KvvIMSꂵ*VV^X6 ֩T)=w_HDHfjlȲR*; rDžBC7[oj\okiƴ}/|{]=U}L1|%ޤn)+3}C\qT*J2Rl*SR*?nVro rf(;@YlU+)W˗r݌l37lZ.姕3yoaZoTľx^Zt[X_i۰&7X۞~=p[P* Ty'"",XD2U[j͜u냤t,rx{I 0 4:6dRKݬqM8^[ܾwJ{mJYxAnjcМK r:88?{UIB iR (T,mooZv{C,*v@) M){O&o23$3$$?kw]Ţw Ʋ +ɶB@!  P 6|Jq$O O_l~K5Xp]d+ f:ypByMݧzQ?:vYlJ(1)Uڴ̼>`.ٟW _fsmL]67wnlum B9Z2ޜݭѿTA}l9S@>$Ƈ?:OYLo 7.n>9qe_SV7ې=_gKTezG(|t|d]MAßMP8 8YvqL*#t/,q,uEh/9 ۮv#>?^X6Zzة>/<1r8&D^Ժq**VtuvƊ?BzgF׵zМ:?8a!Bޚ_gi6?jezoRrLd)})_gNÄѣj]Y ! B@"%L( yR,&Qo;Gy)!.\ɀ ?eWK2sP`9(XeJ_H ldGk{@I(ú#9ud?o'i9;jz`j? E* ɖ r ƕE+9D?'NcIDȆBOC=4*1dGSpL{+9.@!` +Xu2=a(&#KN$mpnWwCQ\yb3Vvd_B5z [6Ԯ%Zm /nk+fS'gwZFVZ5}!vYBa4Z7(Cc~v ·?Χ˱x ̘<:nVm_B@! B:Zɖ}1wuxCHYlVwgEVUt"YauBĮ-S5jMmm:kxlJ6)s/elнlO6n99{V,ϣ>۔xv$?"S-]eK&=|g[+nz Zc(6BIYB%C縵ؐ=a_kUH$RV䡪ޚ!oݨB^yRZa0} 1pʄnunJ;]ѦL19 מ7"~ c֝Xi3l܂CNok3! B@!`!y.XdҌ'H@OzpQDԗ֧"2/VV+I`5_Zi?jW+Ir"儺~H(3Ú<H"QW,}OcdZ_ }:gA篨z0 qȧsV^Jۓ_!>ٞ^O粙<&1ɣZ3tK,Ʃ'-kk=WƯ{svswxs+/ŖB{_:;~ǹleg?wMaWtLB-T“5~~Wn ٗ(d4dl!"ƍ.|-;.;'zpEOg徉m9=i9;A֩gK p5znf`oiz'^g;H Y^9axzU"%gডK>$$^T25o?)+_St7{s.?&vo>#Y:Ylғ,3\[GZFa!)6 it/yI׬&D }Imdo(< !=)ӿC1hB}yEWB@! B"لn 5Ѷ!?cNpuCcjaf;Yax턇[.17xQ=]̭5fv3ynLdsŠW>f5"m ! B@! BDn)A(7z)>JPmk¥]s 8҆ B@! B@! . @-B@! B@! B@ ݭAUymvݒ+9uYFǝ/US3//BMM 7{\]n<'܇?6=Niۮ=63wm!J;iix1jP̹~9Sη`%lyڢnfV6~߹O>X[G^~KJGC.B@"[4-B@@`sV:LJ>J¶ ؕxbE^8y4rz^*'$D0JO W9Vs]Wp/Eٽ;/LӰlbW1mm_[ь[:y v$p)5#'?M_fEϧ*}/++xm`k7}>^5OtgeROS:ꎿa͆M9;CH`ƎoSjTTV'+F+';B@! BwpVB@!ai)̲L\/T#$ ~N~zay!|23@֘zUU[P!wr(#l_+7}ݺM{{z*E5Ku<<.[Hv-E?ߴ}umKtt?YOn.N>x :93#&Ɵ)?ga ! B B@! h\ GFI?RKH=lLXB(\s^1֡wq0Q駯^[涯W g'8P&O}<G–KpK 6nh;2ܼ<|pqtKS ?"vݤoꩣ]5B@M@&Oz/B@P} (c#$C͝V'm5[ٸ8Z;b Mx~F|}MޞR}K(+׷ 5,;)&M~לdd mlÜs5lABR +* m7^Ml}昖 &nTs55M-_UU\cS˛S%<鏥'&C Mqpٰ1"7ՆB@! ZN薷)-! B@t НGLrb 2X3aۚp\lP^C|yMc#]E>_8; aGND>ƿgaEv.zIw #1fPJTd l?]&!mI0~x,w%9X_wؾ{/ri<__xlˇ”Y?y1taρ#3ixx]jqaw?bƭ1J!\5 sg׷1XiNż2srGvƎ^rrv=={i¿b$<{&&%#4&g5?(.-QC=HތuoCNn>Jϕ+w{SƍAfV:|OY4X('?LZLwaI .zQg*"=O,Ͽvb@^?j|}<?l<= oo̜R3Q)cF}ZKs><9 Wttp W-bı>4'B@[)B@D6V+Giu)Xf"$v~y>pS+m;l'FCQSSI;SCJt̰-i_ۈVq  i}#Ob׾Ų^'ۋI- 1TccT2{ _Փ'Btl8T sW,ZboD^EM;waݷ+Vx/?z[RbۮJ|k1j+c!J|N4$(P4ٰ?^z>E~쾻qSrᯛٯlCG ҃t&/ۉO@KX:QzW6B@Kݗ̥ ! Ba<$gpgND苲2B*ٗD,>[ukB޶|~.eɨkFlLجDS A sۯ ptUn8r* >"99jN8##:,ФݬjWƓᓧM]ۜն2hEnSQ5nYy"r :aQe3_zǺv1>Gzk\884(rs_#q۬kY,zyc؋Z7NE*XgCHT3ӾZ#,Ru—<9tGmng5C[V[n7,Ţ>kci0zTʘ?wUY*˪ڇqhZYGcUhVԙ89Ͱ ~P:f?x"7%lK o"M망oniB@Kݗ՗ ! Bv >>4I^W:)T#,0F{FYؼ38kcuiX}KQA^֔%j,fS'PsZF-u9T7i_=D].K;#&T9QڥqOrB۟bxXlfL{n7WwvTW]j$}Rb42 i 4X5Zc4Bwkhb%CM}_eXZT);/=G_i+ GY,B@Xݖ) ! BCpuU@ܽ#uVqAzI*k*fĺD>C縵ؐ=aoeVot;K“]=A2Mѷr۪?lg@jz:6n݉56+9:v_.~[}s6u=)9%C}PbGz} V)}G4MtjJR6&{'^~gb#BJ;B@! ,C@npVB@!С kĠҪR9e:HQellor1c/la%q[R^>6ܪEdtl k~]v^=oo}-^~=r&<iԑW<ݐL(:x@?PXlwpm V\:B™e$t 4Yk=Ide#;+nj€>MvR(qiBJpGxX"tgfjzYY9V@=d=7q+ ݇*tĠ |OnOg*77BvMXz&,jXB^{4^{4־ߧ[dR__ĞEEX%`!d[! zE^! Bek Nuª2kc*#}*twc|1.h,HځugSSlKWi%o.|<-̞1MJJ_A5^kQ~\}ȢHG~=Aw8o")#ylЀUXps̩CmeL:x߬^rX-߷bP>֛pS/ -7qt#M;v>4-ە'/߮ĊUk)',s N! $$`_0wu8B@c:mu1(O! B@! @g$-I! :1lWT#B@! B@! }]pB@! B@! B]QB@! B@! B# B%veB@! B@! B@F@vEeDW AWhѱGor`ae$< nc 5ݧ+T{na$!{O toRD;>e$vO [K#$n_//gzCedmٹ=jC>7RMmg(5aN *((j8{WTVnhuO_UU\}ݟܲ<{mLآ0[ËU۰6q~x=Dn2.B@t|񯡌@! -"%gȳ֞0P60JrL^,5K ˗č~Nng#44?朏37,M cMۧxGfR&ۛ_Gh1w{#]5?_8;95sD$^cGw?[L'-/6DƘaC*ӯSD-i0}^P[a0?joVϿm~ !ݰtwsҳ"7]3 ϿK-Ҳ2,GݸkזUWLᯖmݺJ}VXTM4elo 4MؖdzO wI7LbxSzQ1æ^&.kDv! 힀IB@!к+ͨS% !Pϭ[׺LoG?D-nQIWtY9Ͼًb$#$0@ٗ` g5?(.-7^777cېq/Jh~?o"3']!:6{ΜJآ|[Rv/zclڹ ]לx/?z[suPګd3g(+0FT]2[~H>nwzvոZ'4(t7fK$H8m(]\v5+HIdzN`Fa^q~eR!B@t"tw$B@! @(,S  XYZؘj$1݊=ZƔUVT^% 編% WAKG{"/rnxWNSvOu]tݲ`&j2=\1g3 *qgq&fUVǙÇOnsWoyuWOQn\hNԺeeXˊȭ3\%(}7=:iasF{msXc>фS.=7 ,7Mf~{fܙ8LFv.Q^U,yN ! B^[B@! L"frsFeCQJ%N( 1Ɋd`V7 B ]/\  cmǖ %o){M9:`j$x,Ŋf]ea#U^Ժq**VtuvƊ?BzfQu4}GX넯ۜv$j+nӏ[n7,Ţ>kci0z @dߊ: bu3څ͝a>%׳"`AuW]^}9qPv/62{C*ded)B@t|"twk(#B@! @E{u7ŕNSV-߳FPd1 Y < 0'U4gpWۖOC'`[Lrxe; YQ׃=SM@ia[KkePלն»ןij<9hI5nP@}_fc~v ·?Χ˱x ̘<:n~G\dQC[](Ǫ  ]G}=zۘGNenaJq7We^kA ! ݝ*B@!ȓ#112 Ͷ?ǭņ FPt_OEliKEKcN8}ޣ ef{(M#6;w&MLcr=o[oD8HMOƭ;ffٸėfk;AWbkdĜ7ߚ]Q] .`яNՅ6cϡy7| zbGɍ*twǑFZcB@! 糃J/B@!`: daDƢ-ݘP!N6ElL- a.V1\scR:LxCr"nu`>J#5&m95wӶup!F i4aIm:Gmv3zCIK۟,mIwBwsMJ7}S-ϖCfH< eE! \D\׳Cf4,ٚBg2J8y H"~h[lll>ӹ*ÔvPyp?MEz\J (ٰ\8uLhZnjEp"[#ش;J%/_6oGdtUHgK_6br( ShqGxLfe~O'\3I96^h-APT¢b,|=5<)Z֒K` 7Gܤ~r(*D%M|P+8 p7NU|v2'zLB@! @! %ZvĦ J_Gpu^/[&a͑Dۮ)C_|?=}ƒjܽNȳsVFg׳/Mw?ĮEvDOT8xc#i֝ G>ԒTe2,bδ,ei~5٦p=TjwMz O-I5/GdiT_nPߞ,-\,y%<[=cQwIi˖?h˱y-ۏ6YTVTA?7Ibk'6^yg1ƍÇ DetWV4e<" Т߱˾n9uzȢ] _}eCSW'qt۴v삵5|h[+7^xt-ڗK)JAaE7a/{/DTsqzHƾANBE4S .02`iD,ʃ^(,E\YTԔIzudC! XDX׫=Fpɮj'б]gL|=<[u{-a]qah;:uC9{v|?4vp=tAf[(ruJ2 j In̶D- $(:A~GݣFj?6]mNb"p |}etWj.h3un7٩{{|*FuC)w?oWbŪI & ņ(쯸|{h>2[KFۍK멱1V?Mݢrۍ~;rkz\cf،z<w7@AAZwHB! Dş׼e0D ZVԠ>޿῀4!BCt{ ^\sOMēW߾4 [">˭]XbcݣU:kgYwqV!CݍMtuߵSC*%Ź] &L,8%56_%ډRĞ"r% ;ew1ӳKoXY^z&uB'6MҲ2$$&!$8i;M G!`ޓ4咪$&2=Nsɓ6ٸB@!Ё2UWrOC2;U> ! B ߫}]pi: t7?6̟ ]k|05HV] H˯P` 'y79P L%p> p0VѸ&;.~S͹Jh\)4.k7y럱m)GBV<;HL0M(W"\5tLz 7"&gS;{6v91s\!F髽Mÿc<~T c|dB@! B@FF!mŭcһ#rIaxsߗ!> $`[T>}6$b=:Oͨo1e_9^=)ue fMm6'=Y(-Vwk=+i6]41Müd^887~Kb5>6j2se;'q,QӮZTK[6ϣ׈;Ž1s?c8Q}Gjp^ oGTJ)06xUGrQD2af j_-͹t$?En_fFsmAV̽"VAxlZ̏gn򶿣\^0ۏb ul91gD]9fN}mrx]@6[;f; =Q=s6,;CBgX{8 lz0u-M !uޭd[ ؘ G=ˆЖ9~' b7" ㋵a:WS@VGe[! B@! @' Bw}]5~:mL KN# xRfx0ooJ I{0N8+GLZus@8NKPNj|F"_nxkc $8]Pε4Hrwƴ~%XVP\8Σ~2] l9}u۞tDO_{\w7OƏy+|=uC8erSIc$fO+}؞.JFl=ea_|6? /]m1槣Ð$xyKz7qGsf╳\ImboW8G_4mu?7_SakU?EogLtWRIy]'SF!=ږ9 Ψm qL YLJ7j%׺&w=\A8J33??Gvطqv_߳\iR b˴e,y]i#߮Lܸ$ ow1\L>n[ox;(z]oFp5=߽?X_|-B@! B_d1e0X"Ȩۍ-Kk0DnGفMggW%`l=eƲ͙kgT|}"*Gc`^|\U"rsVw$qF[e1xQ+d. .b92@1$XZ-@e?zxY$Ibn]u4RY,b]KOZg;F6,?w2G6s?Ó+)"C jsg?1hc$R]=+:fuzHRטq%[Ceo벎~悁!8Aƍ&LޓؚF?}c/x<>w_ݐw(3U2ہ<^9Pcݑ,EA$>9~`O=`ڟ&83vɔBu䁷n Sl*BX促{vc揯nQQ)K! B@! ^T52.ZہdRB'hEn>$EL@Bv9ۛEվ&U'ocl|? ~XFxu;bɝzXf;2 o_M "i N-\OّVO讨mEw+޸":=xe}.6H'?kIMI1}<_+(7ޏ2C!j{|@]ZQg~JT}vgz"7B/ <7~7: s{)G[S[/zM?9d="^!B@! B]WՂ ꧩv7Sg:$M ЯGlXZZy'rX)W迒?2dbV9ƽ~=cț[corיզ˞_ǝ4c*6-KA޾\ݰ'%gN[l-qe˼7$wtjI=2s:t%(Կ.s?3[pL#JVA9˛.;[O=xV31a8Y.gx~' wazƿ+ݧZoq&à[o$Uk1i; 0_<[$>koنMj,[=zCW7%gq'7WkJ ! B@! dt:r@9ԘXdC심ky2[i,!_aL 林+5g}?As md⺆ڛMk[Ʀn~EHLy{ 桱0ֿ`@/q[ֵQMpڒhp9'oђX.r?~M)[W^Grys|Ei(RZhjG[g ^Ǿ?[z*ńzdb̫ܰ2O] z3| J`?i|!_n>.!B@! BR&e2/囫-n쭊 Cx/N_?h-T>QF#"g)J!4ekzsX14f{ \wOjv%y$Nہ'I$+~x~x) r8Ao $ě Q ^XVwr&P{-RW#U9S$B@! B@tvS>J%M'bV?Gld=j_Wţy;¶vIՍ|cYb' W2]2i?S#gW& h]hϩ޴-24D9x.1(ەD*#t_~E49lA=3Mbl"`o;LUZQx[tnً>9SL ~C$lqK&y8(09Foh!v]) |6ЊB@! B@!997{q8^3!!Lc>j&S`jݨQ3y'~zlI z9E<$ EcR3ӈk k)x;{;+E6 L;b`# ؘlg38R)\GOc_7"q& wœe5JCMJhJ a0a_b-];ۮXKMIFH7>qcke{Q= ooe1iya9fyr.F=دDzJ]^_<҈Ժǹ^VauzZ̈́~MB~/+aی#Cu"<g{ B@! B@! :(.3tgӛ쮹J!m)[\V9qX=&ԻS;6)\odcՃя%ft2t>{}iM΂z sK,Fv`n&d@~w7 3x(ʸ52sY-=Ś,]k;$OL 蓌džěݾ4E"Um7t, _w6%QX0WaS`(_k0qn}^o~`Q9aSҏ P@62gDb"?:5Ȱm`^ ωrX1knv%??k4#Y0;[-~~_q`x'coV[FQyE(>u P|kxnE3~/>5toNJbqDy/ Vq(VOܥ1AF7;IdJv(,MՃrenOy'wF=S{9qxzN3u7_ߜS譊uGڦBtgb/G&kKI! B@! ,M@nKL&3e1E[I.#_$<Ьϙݰ`q1~$&ky:g^A^$z1+>#Q$MTO !}(z P0oy 5ijĤ?4˳Ksȫd{`>;%Y>IrKO= oY&p&v􀄳99xRBn`1ВaL~x0 &*$ʥ>F78ӔKƢ'ɪm$i⾶ V{Z‰8KE1,U&GlpY]FV1(bK{+ [rse8Hv)ޅƢC<'t ʺhADvY8\m]KTN42wޞ+u/~߈0F|rqu4  sr~v$4ڮ A1%J&okl؄Ș3@AH`Ǝom1}(++W-{!L=R[^VB@! @[8H vwӮ]m˙[sGU8" ,ІğϦ3[G7.4FsdE}òƶfMɟ 0o+aSCw:ּ̍nhyVit=DT}VDrV97>Ӄ@@ Z[nGy+s|?EI_^# p* mV'rIBLCaLIbD糺ӧWk$5=ܾs:v 4$ " TYU^I^'y@@TU+_6{m%MNMbb䂱g1_>(I:ehv<$@sZ3?  !@ >B@>KD-UbL:v N[}= mM6rťr0-M#gcZI~~d׾?!Wh9fB@@ 讘s! TQiTSKRzKX)! Y<22R~|T +? @fp??Uk䮛/s~>D@@tn  pk+[@0k;99}sƳGVIݥYtlE[4tG~+Z9_ʷK%}-A2L@@@ztW'kC@(C :"ZùYZ$MTu}znwuo[3?/'dr@@j ]@@, nG6t2+kְ,^3ܖum#21Wn/ /@@!u,E@U%Aɯg%!b;8u0琬:۾ckA %!&>5Pbn߹[N=}Hn^I̔+/ >˻ڽȟw!%N5  TR_0\!  Awמ0HjN|NKlX@wMD˙0a2du*i9rYbrse~k(,1Yn{k7" i  TBA]}V`9NB)\'4@@ w-{#??_rYتkM?(MRkl@@:MF JDDDCk9-W۫u1eM ~@@z'11Qn2ٯ^wIv@@0tI,  P7tL&_Y7:C/@@@@$@@@@@ka}<     n@@@@@ և#    @@@@@ka}<     n@@@@@ և#    @@@@@ka}<     @TGs#  ),,˗ff-   @3!    U34B@@ln'[%:9xt@@QչCB@@@@@B "    9u!@@@@@Ptż     uN@w;$t@@@@@ ݡh1/     P4hP+au, Gq'@@c'Nx{N c@A_PP}B@@:&sLKۥ%J Ժ[=`   p ffпGJ&}1  ^ t?+uwt@@ќI9*'Fx t  lnR2#"##G;,}G@@ Α;vi[ ѿA3?Ug  P,q2(Kf9)%5ƍITTϗz ®!  @NȔItTDFFkrRONlWJ  (dtOhgy}옜(,t"ϲpv@#Htt ƃ/ho*2Tcw:{>ԻI#3$د0x>env@Ꚁ}<*RY,_FK~}-D5m@@6"h' {@6 ⸵5yDѭ3՗vOyV@@}uBz`d-UYV/Dd]apw ^  @m D7@wCg, ']?ڒd; P8-n&o}6[ݵy-@@ @xz˧RZ;BODjwp{-  dtۭi gn&@ em{~oy2e@wNF@, vLO |5MC@(a;%0ti4Ԡ@(omk+Ro[dei?6 n'W;)q uR. rmnP(NN!z9΋5Q}>ƀ 2q@Ϟm{Y%>3 @ m16Ǎ!+P'7y}y&]_$ Jw:  @ X@a|(:@} lP݁"G@@@JpFs)1@ި     @ ^Oֆ     Pk!    Tdm     ,u Nٙ@ |:9/JG 6ec2W iSrL-rH&!Tȱy拽2KLs{TvJV5@-)IDATkl8,r!k77 ˗|iSyyrhh+Q0KHئE8+Ǐ:) WGw܍ut 8JFNyS-ڂ26R{~m~~K۷\zR竉U OȾ'%2/5їZ3w=927O@Ȍi!%G_mu^låo'Kq0g~yn2GBgLOW-HV hzz|(UN*seK?;yOexҬi7Gir%'S{ݖmߕ d6 Rgh2K&9̾a؝_^`lݓP^v1۴L?ߔiA5;xH'm.ҫsGwv_]P5izBV2_R?$e @tq&f,?%6L']&4ˇ%=h l# з*pZfޮ/a @ /䮩ns`3bH7 z]~䶠C?&fxU'p6\9bck.i]g&ʧg ~ /G_-9sΉe׾rwo9:K );rU˝(wg7ii]ߺe A/3 rɢ%xMvVMi]1myF@rSʙޗv\l݊+W;@ 6dKVNn#m7t?*99㸳v-yqDE5-y?yqZitKht`Η}G%QlXbVO6߾\8'Z6zlE\} eMuc[֍%"S\&Դ\8>Γi$RD_\۷zL[WޱuϺ RPeiUq8JNrf]:㳓ٯeIm)1ȿ]{eޒUN1Hg8˴o!_M;pcHC#؞ZF WB3?|-cme5}⍵=)gRV%yIs@Fuώya>߹rDn;wsM=?$tPQ=d1Sv :\FM&,rCf˦|FnAܾm`aw OL*G\k䆗62/;:bJ.^ϪM?.O#o/I< ޗ.xbuo_^(ߴ79G䵅%??e?AO ~Z{ou"Z7+>.l{7kedX/l=]' t';vqw<,v:i-O/mZ_8 u߬)zNtoɾͤoO{!|U4/ ޹X.A].uۃ3vW2h ;]Ξ+|(yA5]䖳yWY/ץ^YnŲɆgGINnaH?ngK:CǝIv䝻9[rPm@,dfQt,Mb?[+~{anٰugݳYsx>3_ĵG(mmC_zظU&k6owjng-.tnN4ؽM˓XcF;zkbe!;9_qXw>#93GT|Nfʏ&]]<7/OKF/`35k*j}zzSdNW&v h G@w9uzOm[&fxUײ%x8 .hoS"*?^.sdȅ[/\b? )2L\4T-뻃N Ԃʯ,_"m/lZh6F|:]$q<3`ސʠ_:Ypf EȏǶAmO^]o ǃ]Ęl:/Wz_^? -ur&9u[eީDn.'}49Y\bŜ5ir˛.r%ֹka{K縴.y^!F:|9K6͑ w'GUmufY2Ҿu t/YAfH|\ o\t :E+ζ wcžZ;p7iD|fZ,MM%9\3 ﱧn@]w8t3 eq`?@k wţ%в;T:k-Ȃee#yݶ!ߺ; hۀz@ޘ5W/]%-ʈ}Rd} fAn [_@lei5 m4(f1tml,ĖҀoibzvt$1KkMc-!dVb xk-/7 FIگA0 Y{M|W353'%z348z?79A5U,=$}W}r -IS൲.n`UldN{ʨynSW;lMӶ5k=gdY0߂^ PϙiVtk-:=7vLOƬP_[&-zk錒W}X^וݩzZ)gwm⧒]H3I/LU}P woyul*yoXG*P2Ósv;An˦~~eC .[< 3tk[HyvjWthwq-u;+}V<ݥH@wTF9ղ7,ן=<2Uqp3 PVmt$Yݵ =PcGKƞW8=nĝUR̯m䢳O6:e^Yo~4j(x@ޕ:bwy.6).i6}bq~rҬiIв7)ҝ@qgZ^g\S)mSg*H<{ U_1[a2Q2y8y~ Yj>z tnMM:耘~Wcn9@(@m m՞SFzQ >tL@Lvj r\1x Nq fպt@$4lflt+zz3H-#<)ƒYhٮ58mˌYfփ^5xfV  rrMs!ONwў|oLw?PKX)b rVwlY,[>KZ`*O]ִ7e.'zΫ?Sb5Vfr~{of:O/ Y۩WV+ڂtS;+P~.nf箄G/vBYwgAn;==m˰w[UeG_|hKbx>dZ[/.åQk.MКm]4^(/3Kh3]$3H^atʔ=LzusL3hv&s_H~deRhi>f5~UFGMW\]t0㻟}%_e'_Yuxe12oܻJ8_/5dsG si Xe#n4]b1@]]Ve\ݔ%ok}o$-l .x_~}HkA[ Njy}o,]X4pGim_Fq<u Z}ꉃ];ZТ,]2=uFW+ҜG8p^\?vjRdc6gCnZ?RLͦSE7;׷ymehk[eh'3ڬvjZmv=?V۷UwyLvZWO\E񝧢?vzz{Ϻ~C lEvLVUw=<#uU?hlˊ mvudtfͪ=8fAiP1_^|w#60m{2qDL59HRnid|۹[5mއ;,OwByuA[V+Zfsq{n#`wMH~Z_ڀm>>~w8._VZ22+YQ =!E/`?[r(|ԤCiupàAamөWG'NY]):@e {͑c2KS4meQP-}8+LۅTFZʖd]VϾAo#h:q< Z9Y&O<̶LuJ;Y~殯gY7hMoZ;& ^ {uTurzhomn{MC_.]OۛLC;;EA5S3*BҴdH6`25fM;dOA&vVf:` \Yz93S/ϧzlZA+EթN{no~5ػ*تR0I:g6*nEk/lߴkey{Ay}\3r[yO-;A˒$kmZ&s2"M-S]2vFvѹ\yg'(.QdKu23rŐT}MLUu߲]\©Mw2S ޝӎ]RtjYB$M/E wgu펅/ kzI6أ NӋGW?VyђA4@v+a=wE8{MC_%î_1f1Q ۋ.[Qݚ:2O^< +%QE_m/5sxO]L2!mi- p^[]K&J\lX{}^s{g^7֬qku)K{oM<W_g{ +Om#5'=Iw&I8_eWơmuMܾ1ſnܿ_$5+YEUkc5?E/ \w *.2irp-xm-qCi[_|6j^g^g[NMc` ؇Sܾ\: ai K@w:a7wAvNNܡ5-wȖ%Z5s%٫dbN_PѬsˌ~}OFP`8,k\Fudꠊ6Hڑ_x[FKbQosz7oAQZhIv- @Z>%GK[kTv;sYm3Ss<gveV[m֢ٜ5iwwer=Zr$M#sNwe)6ʚv%J꼮^V՜vOy%U2n_ٮV2}q̌, yrDvn6Y?+V*ZDUj Cj[ E`zg8$|K)yam[u3/wIy7%-Ćͻh쒿8$|8֯72-&oW"E2|@o j-ڰr&X&֣hSwʼ%n@tl9ώh:r 5;ݻjymN,=ɱ1wregai"dpfޕ-LV.e_ַr2×vM Ȱ| o% xhpyƙd[Q͢GWmu[-8XOwIZJ<%g 69:f`r+W{˵m?+HQ: i%$fǸݞ8-=ihK@k_lLRioҰxr뛽rC?}sQ"~6퇎ˋzw?*Oߨ;Ot՚ſr;ňG.$`\tzVk\C|V2~@K:*2ZXs3gYt:YFFwm&_頳iZ~+ӠX9'?Q.Rv KuOO?^Uj dcEWء,sL-t^$!APg~)nNN1Aw?Z8MOt6 -;ҵIr$ C\r߲tȦ{ߕ)ͅʍ[ByEm=Iw;_e瞥`3ݡ㹞q:uMp[-iW^,OH^gԞ%/\\XCMb猖OY$Ͻ=S]'|wYd穞֦\نZV;!K;lPV<س]wM!sK=Qv/<D^ maͲ{o!7^e{ h% Zot-s]-qbnOL} w y`&hg;ۗ%93OǙg\:hdW騃3VY@ֺnn%O\ h͛H.\"hk9S-%vgƢg4ňV6)-7v(6𤮲14oQ rLK|*8Ҏ_:o>'H,;+,3WcK/}[;}rg ,]En=zn{v< 5An|rk[efòퟙYD/X w&s7t;6{X^wr2ꅒ\ ]jbuE](=kzcA 0vѲ L ůJڳmrUݧnǝ^GM8=jz./8%i42u/ke}f%IzhFH \G^P-e*>Ce2 ЫTzKO8u jҚսl=*7{]+OBxt @݁"V.2@x~2JK@p,?XIG~v4),[8'}G ;!{ݧqf/@@33䡧)QQiSrי#CG@@ .,zaO:U3u`kerPmNJ"   @- P PI.     @ CG@@@@@L@7     @X G@@@@@ts     >|t@@@@@@7     @X G@@@@@ts     >|t@@@@@@7     @X G@@@@@ts     >|t@@@@@@7     @X G@@@@@ts     >|t@@@@@@7     @X G@@@@@?DDז7IENDB`graphql-ruby-1.11.10/guides/queries/ast_analysis.md000066400000000000000000000105661414121453000222440ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Queries title: Ahead-of-Time AST Analysis desc: Check incoming query strings and reject them if they don't pass your checks index: 1 redirect_from: - /queries/analysis/ --- You can do ahead-of-time analysis for your queries. The primitive for analysis is {{ "GraphQL::Analysis::AST::Analyzer" | api_doc }}. Analyzers must inherit from this base class and implement the desired methods for analysis. ### Using Analyzers Query analyzers are added to the schema with `query_analyzer`; however, to use the new analysis engine, you must opt in by using `use GraphQL::Analysis::AST`, for example: ```ruby class MySchema < GraphQL::Schema use GraphQL::Analysis::AST query_analyzer MyQueryAnalyzer end ``` Pass the **class** (and not an _instance_) of your analyzer. The analysis engine will take care of instantiating your analyzers with the query. ## Analyzer API Analyzers respond to methods similar to AST visitors. They're named like `on_enter_#{ast_node}` and `on_leave_#{ast_node}`. Methods are called with three arguments: - `node`: The current AST node (being entered or left) - `parent`: The AST node which preceeds this one in the tree - `visitor`: A {{ "GraphQL::Analysis::AST::Visitor" | api_doc }} which is managing this analysis run For example: ```ruby class BasicCounterAnalyzer < GraphQL::Analysis::AST::Analyzer def initialize(query_or_multiplex) super @fields = Set.new @arguments = Set.new end # Visitors are all defined on the AST::Analyzer base class # We override them for custom analyzers. def on_leave_field(node, _parent, _visitor) @fields.add(node.name) end def result # Do something with the gathered result. Analytics.log(@fields) end end ``` In this example, we counted every field, no matter if it was on fragment definitions or if it was skipped by directives. If we want to detect those contexts, we can use helper methods: ```ruby class BasicFieldAnalyzer < GraphQL::Analysis::AST::Analyzer def initialize(query_or_multiplex) super @fields = Set.new end # Visitors are all defined on the AST::Analyzer base class # We override them for custom analyzers. def on_leave_field(node, _parent, visitor) if visitor.skipping? || visitor.visiting_fragment_definition? # We don't want to count skipped fields or fields # inside fragment definitions else @fields.add(node.name) end end def result Analytics.log(@fields) end end ``` See {{ "GraphQL::Analysis::AST::Visitor" | api_doc }} for more information about the `visitor` object. ### Field Arguments Usually, analyzers will use `on_enter_field` and `on_leave_field` to process queries. To get a field's arguments during analysis, use `visitor.query.arguments_for(node, visitor.field_definition)` ({{ "GraphQL::Query#arguments_for" | api_doc }}). That method returns coerced argument values and normalizes argument literals and variable values. ### Errors It is still possible to return errors from an analyzer. To reject a query and halt its execution, you may return {{ "GraphQL::AnalysisError" | api_doc }} in the `result` method: ```ruby class NoFieldsCalledHello < GraphQL::Analysis::AST::Analyzer def on_leave_field(node, _parent, visitor) if node.name == "hello" @field_called_hello = true end end def result GraphQL::AnalysisError.new("A field called `hello` was found.") if @field_called_hello end end ``` ### Conditional Analysis Some analyzers might only make sense in certain context, or some might be too expensive to run for every query. To handle these scenarios, your analyzers may answer to an `analyze?` method: ```ruby class BasicFieldAnalyzer < GraphQL::Analysis::AST::Analyzer # Use the analyze? method to enable or disable a certain analyzer # at query time. def analyze? !!subject.context[:should_analyze] end def on_leave_field(node, _parent, visitor) # ... end def result # ... end end ``` ## Analyzing Multiplexes Analyzers are initialized with the _unit of analysis_, available as `subject`. When analyzers are hooked up to multiplexes, `query` is `nil`, but `multiplex` returns the subject of analysis. You can use `visitor.query` inside visit methods to reference the query that owns the current AST node. Note that some built-in analyzers (eg `AST::MaxQueryDepth`) support multiplexes even though `Query` is in their name. graphql-ruby-1.11.10/guides/queries/backtrace_annotations.md000066400000000000000000000065761414121453000241140ustar00rootroot00000000000000--- title: Backtrace Annotations layout: guide doc_stub: false search: true section: Queries desc: Use the GraphQL backtrace for debugging index: 12 experimental: true --- `context` objects have a `backtrace` which shows its GraphQL context. You can print the backtrace during query execution: ```ruby puts context.backtrace # Loc | Field | Object | Arguments | Result # 3:13 | User.login | # | {"message"=>"Boom"} | # # 2:11 | Query.user | nil | {"login"=>"rmosolgo"} | {} # 1:9 | query | nil | {"msg"=>"Boom"} | ``` The backtrace contains some execution data: - `Loc` is the `line:column` of the field in the query string - `Field` is the `TypeName.fieldName` of the fields in the backtrace - `Object` is the `obj` for query resolution (used for resolving the given field), equivalent to `obj.inspect` - `Arguments` are the GraphQL arguments for field resolution (including any default values and variable values) - `Result` is the GraphQL-ready result which is being constructed (it may be incomplete if the query is still in-progress) ## Wrapping Errors You can wrap unhandled errors with a GraphQL error with `GraphQL::Backtrace`. To enable this feature for a query, add `backtrace: true` to your `context`, for example: ```ruby # Wrap this query with backtrace annotation MySchema.execute(query_string, context: { backtrace: true }) ``` Or, to _always_ wrap backtraces, add it to your schema definition with `use`, for example: ```ruby class MySchema < GraphQL::Schema # Always wrap backtraces with GraphQL annotation use GraphQL::Backtrace end ``` Now, any unhandled errors will be wrapped by `GraphQL::Backtrace::TracedError`, which prints out the GraphQL backtrace, too. For example: ``` Unhandled error during GraphQL execution: This is broken: Boom /Users/rmosolgo/code/graphql-ruby/spec/graphql/backtrace_spec.rb:27:in `block (3 levels) in ' /Users/rmosolgo/code/graphql-ruby/lib/graphql/schema/build_from_definition/resolve_map.rb:57:in `call' /Users/rmosolgo/code/graphql-ruby/lib/graphql/schema/build_from_definition.rb:171:in `block in build_object_type' /Users/rmosolgo/code/graphql-ruby/lib/graphql/schema/build_from_definition.rb:280:in `block (2 levels) in build_fields' /Users/rmosolgo/code/graphql-ruby/lib/graphql/field.rb:228:in `resolve' /Users/rmosolgo/code/graphql-ruby/lib/graphql/execution/execute.rb:253:in `call' /Users/rmosolgo/code/graphql-ruby/lib/graphql/schema/middleware_chain.rb:45:in `invoke_core' /Users/rmosolgo/code/graphql-ruby/lib/graphql/schema/middleware_chain.rb:38:in `invoke' /Users/rmosolgo/code/graphql-ruby/lib/graphql/execution/execute.rb:107:in `resolve_field' /Users/rmosolgo/code/graphql-ruby/lib/graphql/execution/execute.rb:71:in `block (2 levels) in resolve_selection' ... and 65 more lines Use #cause to access the original exception (including #cause.backtrace). GraphQL Backtrace: Loc | Field | Object | Arguments | Result 3:13 | Thing.raiseField as boomError | :something | {"message"=>"Boom"} | # 2:11 | Query.field1 | "Root" | {} | {} 1:9 | query | "Root" | {"msg"=>"Boom"} | {} ``` graphql-ruby-1.11.10/guides/queries/complexity_and_depth.md000066400000000000000000000063601414121453000237520ustar00rootroot00000000000000--- title: Complexity & Depth layout: guide doc_stub: false search: true section: Queries desc: Limiting query depth and field selections index: 4 --- GraphQL-Ruby ships with some validations based on {% internal_link "query analysis", "/queries/ast_analysis" %}. You can customize them as-needed, too. ## Prevent complex queries Fields have a "complexity" value which can be configured in their definition. It can be a constant (numeric) value, or a proc. If no `complexity` is defined for a field, it will default to a value of `1`. It can be defined as a keyword _or_ inside the configuration block. For example: ```ruby # Constant complexity: field :top_score, Integer, null: false, complexity: 10 # Dynamic complexity: field :top_scorers, [PlayerType], null: false do argument :limit, Integer, limit: false, default_value: 5 complexity ->(ctx, args, child_complexity) { if ctx[:current_user].staff? # no limit for staff users 0 else # `child_complexity` is the value for selections # which were made on the items of this list. # # We don't know how many items will be fetched because # we haven't run the query yet, but we can estimate by # using the `limit` argument which we defined above. args[:limit] * child_complexity end } end ``` Then, define your `max_complexity` at the schema-level: ```ruby class MySchema < GraphQL::Schema # ... max_complexity 100 end ``` Or, at the query-level, which overrides the schema-level setting: ```ruby MySchema.execute(query_string, max_complexity: 100) ``` Using `nil` will disable the validation: ```ruby # 😧 Anything goes! MySchema.execute(query_string, max_complexity: nil) ``` To get a feeling for complexity of queries in your system, you can extend {{ "GraphQL::Analysis::AST::QueryComplexity" | api_doc }}. Hook it up to log out values from each query: ```ruby class LogQueryComplexityAnalyzer < GraphQL::Analysis::AST::QueryComplexity # Override this method to _do something_ with the calculated complexity value def result complexity = super message = "[GraphQL Query Complexity] #{complexity} | staff? #{query.context[:current_user].staff?}" Rails.logger.info(message) end end class MySchema < GraphQL::Schema query_analyzer(LogQueryComplexityAnalyzer) end ``` ## Prevent deeply-nested queries You can also reject queries based on the depth of their nesting. You can define `max_depth` at schema-level or query-level: ```ruby # Schema-level: class MySchema < GraphQL::Schema # ... max_depth 10 end # Query-level, which overrides the schema-level setting: MySchema.execute(query_string, max_depth: 10) ``` You can use `nil` to disable the validation: ```ruby # This query won't be validated: MySchema.execute(query_string, max_depth: nil) ``` To get a feeling for depth of queries in your system, you can extend {{ "GraphQL::Analysis::AST::QueryDepth" | api_doc }}. Hook it up to log out values from each query: ```ruby class LogQueryDepth < GraphQL::Analysis::AST::QueryDepth def result query_depth = super message = "[GraphQL Query Depth] #{query_depth} || staff? #{query.context[:current_user].staff?}" Rails.logger.info(message) end end class MySchema < GraphQL::Schema query_analyzer(LogQueryDepth) end ``` graphql-ruby-1.11.10/guides/queries/executing_queries.md000066400000000000000000000077541414121453000233070ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Queries title: Executing Queries desc: Evaluate GraphQL queries with your schema index: 0 --- You can execute queries with your {{ "GraphQL::Schema" | api_doc }} and get a Ruby Hash as a result. For example, to execute a query from a string: ```ruby query_string = "{ ... }" MySchema.execute(query_string) # { # "data" => { ... } # } ``` Or, you can execute multiple queries at once: ```ruby MySchema.multiplex([ {query: query_string_1}, {query: query_string_2}, {query: query_string_3}, ]) # [ # { "data" => { ... } }, # { "data" => { ... } }, # { "data" => { ... } }, # ] ``` There are also several options you can use: - `variables:` provides values for `$`-named [query variables](https://graphql.org/learn/queries/#variables) - `context:` accepts application-specific data to pass to `resolve` functions - `root_value:` will be provided to root-level `resolve` functions as `obj` - `operation_name:` picks a [named operation](https://graphql.org/learn/queries/#operation-name) from the incoming string to execute - `document:` accepts an already-parsed query (instead of a string), see {{ "GraphQL.parse" | api_doc }} - `validate:` may be `false` to skip static validation for this query - `max_depth:` and `max_complexity:` may override schema-level values Some of these options are described in more detail below, see {{ "GraphQL::Query#initialize" | api_doc }} for more information. ## Variables GraphQL provides [query variables](https://graphql.org/learn/queries/#variables) as a way to parameterize query strings. If your query string contains variables, you can provide values in a hash of `{ String => value }` pairs. The keys should _not_ contain `"$"`. For example, to provide variables to a query: ```ruby query_string = " query getPost($postId: ID!) { post(id: $postId) { title } }" variables = { "postId" => "1" } MySchema.execute(query_string, variables: variables) ``` If the variable is a {{ "GraphQL::InputObjectType" | api_doc }}, you can provide a nested hash, for example: ```ruby query_string = " mutation createPost($postParams: PostInput!, $createdById: ID!){ createPost(params: $postParams, createdById: $createdById) { id title createdBy { name } } } " variables = { "postParams" => { "title" => "...", "body" => "..." }, "createdById" => "5", } MySchema.execute(query_string, variables: variables) ``` ## Context You can provide application-specific values to GraphQL as `context:`. This is available in many places: - `resolve` functions - `Schema#resolve_type` hook - ID generation & fetching Common uses for `context:` include the current user or auth token. To provide a `context:` value, pass a hash to `Schema#execute`: ```ruby context = { current_user: session[:current_user], current_organization: session[:current_organization], } MySchema.execute(query_string, context: context) ``` Then, you can access those values during execution: ```ruby field :post, Post, null: true do argument :id, ID, required: true end def post(id:) context[:current_user] # => # # ... end ``` Note that `context` is _not_ the hash that you passed it. It's an instance of {{ "GraphQL::Query::Context" | api_doc }}, but it delegates `#[]`, `#[]=`, and a few other methods to the hash you provide. ## Root Value You can provide a root `object` value with `root_value:`. For example, to base the query off of the current organization: ```ruby current_org = session[:current_organization] MySchema.execute(query_string, root_value: current_org) ``` That value will be provided to root-level fields, such as mutation fields. For example: ```ruby class Types::MutationType < GraphQL::Schema::Object field :create_post, Post, null: true def create_post(**args) object # => # # ... end end ``` {{ "GraphQL::Relay::Mutation" | api_doc }} fields will also receive `root_value:` as `obj` (assuming they're attached directly to your `MutationType`). graphql-ruby-1.11.10/guides/queries/instrumentation.md000066400000000000000000000014561414121453000230130ustar00rootroot00000000000000--- title: Instrumentation layout: guide doc_stub: false search: true section: Queries desc: Wrap query execution with custom logic --- You can call hooks _before_ and _after_ each query. Query instrumentation can be attached during schema definition: ```ruby class MySchema < GraphQL::Schema instrument(:query, QueryTimerInstrumentation) end ``` The instrumenter must implement `#before_query(query)` and `#after_query(query)`. The return values of these methods are not used. They receive the {{ "GraphQL::Query" | api_doc }} instance. ```ruby module QueryTimerInstrumentation module_function # Log the time of the query def before_query(query) Rails.logger.info("Query begin: #{Time.now.to_i}") end def after_query(query) Rails.logger.info("Query end: #{Time.now.to_i}") end end ``` graphql-ruby-1.11.10/guides/queries/interpreter.md000066400000000000000000000145011414121453000221060ustar00rootroot00000000000000--- title: Interpreter layout: guide doc_stub: false search: true section: Queries desc: A New Runtime for GraphQL-Ruby index: 11 --- GraphQL-Ruby 1.9.0 includes a new runtime module which you may use for your schema. Eventually, it will become the default. It's called `GraphQL::Execution::Interpreter` and you can hook it up with `use ...` in your schema class: ```ruby class MySchema < GraphQL::Schema use GraphQL::Execution::Interpreter # Also required in GraphQL-Ruby 1.10+: use GraphQL::Analysis::AST end ``` Read on to learn more! ## Rationale The new runtime was added to address a few specific concerns: - __Validation Performance__: The previous runtime depended on a preparation step (`GraphQL::InternalRepresentation::Rewrite`) which could be very slow in some cases. In many cases, the overhead of that step provided no value. - __Runtime Performance__: For very large results, the previous runtime was slow because it allocated a new `ctx` object for every field, even very simple fields that didn't need any special tracking. - __Extensibility__: Although the GraphQL specification supports custom directives, GraphQL-Ruby didn't have a good way to build them. ## Installation You can opt in to the interpreter in your schema class: ```ruby class MySchema < GraphQL::Schema use GraphQL::Execution::Interpreter # Also required in GraphQL-Ruby 1.10+: use GraphQL::Analysis::AST end ``` Some Relay configurations must be updated too. For example: ```diff - field :node, field: GraphQL::Relay::Node.field + add_field(GraphQL::Types::Relay::NodeField) ``` (Alternatively, consider implementing `Query.node` in your own app, using `NodeField` as inspiration.) ## Compatibility The new runtime works with class-based schemas only. Several features are no longer supported: - Proc-dependent field features: - Field Instrumentation - Middleware - Resolve procs - `GraphQL::Function` All these depend on the memory- and time-hungry per-field `ctx` object. To improve performance, only method-based resolves are supported. If need something from `ctx`, you can get it with the `extras: [...]` configuration option. To wrap resolve behaviors, try {% internal_link "Field Extensions", "/type_definitions/field_extensions" %}, {% internal_link "Tracing", "/queries/tracing" %}, or {% internal_link "GraphQL::Schema::Resolver", "/fields/resolvers" %}. - Query analyzers and `irep_node`s These depend on the now-removed `Rewrite` step, which wasted a lot of time making often-unneeded preparation. Most of the attributes you might need from an `irep_node` are available with `extras: [...]`. Query analyzers can be refactored to be static checks (custom validation rules) or dynamic checks, made at runtime. The built-in analyzers have been refactored to run as validators. For a replacement, check out: - {{ "GraphQL::Execution::Lookahead" | api_doc }} for field-level info about child selections - {{ "GraphQL::Analysis::AST" | api_doc }} for query analysis which is compatible with the new interpreter - `rescue_from` This was built on middleware, which is not supported anymore. For a replacement, see {% internal_link "Error Handling", "/errors/error_handling" %}. - `.graphql_definition` and `def to_graphql` The interpreter uses class-based schema definitions only, and never converts them to legacy GraphQL definition objects. Any custom definitions to GraphQL objects should be re-implemented on custom base classes. - `GraphQL::Schema::Field#resolve_field` If you customized your base field's resolution method, it needs an update. The interpreter calls a different method: `#resolve(obj, args, ctx)`. There are two differences with the new method: - `args` is plain ol' Ruby Hash, with symbol keys, instead of a `GraphQL::Query::Arguments` - `ctx` is a `GraphQL::Query::Context` instead of a `GraphQL::Query::Context::FieldResolutionContext` But besides that, it's largely the same. Maybe this section should have been called _incompatibility_ 🤔. ## Extending the Runtime See {% internal_link "Directives", "/type_definitions/directives" %}. ## Analyzers GraphQL-Ruby has "analyzers" that run _before_ execution and may reject a query. With the interpreter, you can use {% internal_link "AST Analyzers", "/queries/ast_analysis" %} to get better performance. To make the migration, convert your previous analyzers to extend {{ "GraphQL::Analysis::AST::Analyzer" | api_doc }} as described in the guide, then add to your schema: ```ruby use GraphQL::Analysis::AST ``` When you use _both_ `Interpreter` and `Analysis::AST`, GraphQL-Ruby will skip the slow process of building `irep_nodes`. All analyzers must be migrated at once; running _some_ legacy analyzers and _some_ AST analyzers is not supported. In GraphQL-Ruby 1.9, you can migrate to `Interpreter` before migrating to `Analysis::AST`. In that case, the `irep_node` tree will still be constructed and used for analysis, even though it will not be used for execution. In GraphQL-Ruby 1.10+, `Interpreter` _requires_ `Analysis::AST` and will not work without it. (Soon, these will be the default runtime modules.) ## Implementation Notes Instead of a tree of `irep_nodes`, the interpreter consumes the AST directly. This removes a complicated concept from GraphQL-Ruby (`irep_node`s) and simplifies the query lifecycle. The main difference relates to how fragment spreads are resolved. In the previous runtime, the possible combinations of fields for a given object were calculated ahead of time, then some of those combinations were used during runtime, but many of them may not have been. In the new runtime, no precalculation is made; instead each object is checked against each fragment at runtime. Instead of creating a `GraphQL::Query::Context::FieldResolutionContext` for _every_ field in the response, the interpreter uses long-lived, mutable objects for execution bookkeeping. This is more complicated to manage, since the changes to those objects can be hard to predict, but it's worth it for the performance gain. When needed, those bookkeeping objects can be "forked", so that two parts of an operation can be resolved independently. Instead of calling `.to_graphql` internally to convert class-based definitions to `.define`-based definitions, the interpreter operates on class-based definitions directly. This simplifies the workflow for creating custom configurations and using them at runtime. graphql-ruby-1.11.10/guides/queries/lookahead.md000066400000000000000000000055141414121453000214760ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Queries title: Lookahead desc: Detecting child selections during field resolution index: 11 --- GraphQL-Ruby 1.9+ includes {{ "GraphQL::Execution::Lookahead" | api_doc }} for checking whether child fields are selected. You can use this to optimize database access, for example, selecting only the _needed_ fields from the database. ## Getting a Lookahead Add `extras: [:lookahead]` to your field configuration to recieve an injected lookahead: ```ruby field :files, [Types::File], null: false, extras: [:lookahead] ``` Then, update your resolver method to accept a `lookahead:` argument: ```ruby def files(lookahead:) # ... end ``` That argument will be injected by the GraphQL runtime. ## Using a lookahead Inside your field resolver, you can use the lookahead to check for child fields. For example, you can check for a __specific selection__: ```ruby def files(lookahead:) if lookahead.selects?(:full_path) # This is a query like `files { fullPath ... }` else # This query doesn't have `fullPath` end end ``` Or, you can list __all the selected fields__: ```ruby def files(lookahead:) all_selections = lookahead.selections.map(&:name) if all_selections == [:name] # Only `files { name }` was selected, use a fast cached value: object.file_names.map { |n| { name: n }} else # Lots of fields were selected, fall back to a more resource-intensive approach FileSystemHelper.load_files_for(object) end end ``` Lookaheads are _chainable_, so you can use them to check __nested selections__ too: ```ruby def files(lookahead:) if lookahead.selection(:history).selects?(:author) # For example, `files { history { author { ... } } }` # We're checking for commit authors, so load those objects appropriately ... else # Not selecting commit authors ... end end ``` Nested lookaheads return empty objects when there's no selection (not `nil`), so the code above will never have a "no method error on `nil`". ## Lookaheads with connections If you want to see what selections were made on the items in a connection, you can use nested lookaheads. However, don't forget to check for `edges { node }` _and_ `nodes { ... }`, if you support that shortcut field. For example: ```ruby field :products, Types::Product.connection_type, null: false, extras: [:lookahead] def products(lookahead:) selects_quantity_available = lookahead.selection(:nodes).selects?(:quantity_available) || # ^^ check for `products { nodes { quantityAvailable } }` lookahead.selection(:edges).selection(:node).selects?(:quantity_available) # ^^ check for `products { edges { node { quantityAvailable } } }` if selects_quantity_available # ... else # ... end end ``` That way, you can check for specific selections on the nodes in a connection. graphql-ruby-1.11.10/guides/queries/multiplex.md000066400000000000000000000106201414121453000215640ustar00rootroot00000000000000--- title: Multiplex layout: guide doc_stub: false search: true section: Queries desc: Run multiple queries concurrently index: 10 --- Some clients may send _several_ queries to the server at once (for example, [Apollo Client's query batching](https://www.apollographql.com/docs/react/api/link/apollo-link-batch-http/)). You can execute them concurrently with {{ "Schema#multiplex" | api_doc }}. Multiplex runs have their own context, analyzers and instrumentation. __NOTE:__ As an implementation detail, _all_ queries are run inside multiplexes. That is, a stand-alone query is executed as a "multiplex of one", so instrumentation and multiplex analyzers and instrumentation _will_ apply to standalone queries run with `MySchema.execute(...)`. ## Concurrent Execution To run queries concurrently, build an array of query options, using `query:` for the query string. For example: ```ruby # Prepare the context for each query: context = { current_user: current_user, } # Prepare the query options: queries = [ { query: "query Query1 { someField }", variables: {}, operation_name: 'Query1', context: context, }, { query: "query Query2 ($num: Int){ plusOne(num: $num) }", variables: { num: 3 }, operation_name: 'Query2', context: context, } ] ``` Then, pass them to `Schema#multiplex`: ```ruby results = MySchema.multiplex(queries) ``` `results` will contain the result for each query in `queries`. __NOTE:__ The results will always be in the same order that their respective requests were sent in. ## Apollo Query Batching Apollo sends batches of queries as an array of queries. Rails' ActionDispatch will parse the request and put the result into the `_json` field of the `params` variable. You also need to ensure that your schema can handle both batched and non-batched queries, below is an example of the default GraphqlController rewritten to handle Apollo batches: ```ruby def execute context = {} # Apollo sends the queries in an array when batching is enabled. The data ends up in the _json field of the params variable. # see the Apollo Documentation about query batching: https://www.apollographql.com/docs/react/api/link/apollo-link-batch-http/ result = if params[:_json] queries = params[:_json].map do |param| { query: param[:query], operation_name: param[:operationName], variables: ensure_hash(param[:variables]), context: context } end MySchema.multiplex(queries) else MySchema.execute( params[:query], operation_name: params[:operationName], variables: ensure_hash(params[:variables]), context: context ) end render json: result, root: false end ``` ## Validation and Error Handling Each query is validated and {% internal_link "analyzed","/queries/ast_analysis" %} independently. The `results` array may include a mix of successful results and failed results ## Multiplex-Level Context You can add values to {{ "Execution::Multiplex#context" | api_doc }} by providing a `context:` hash: ```ruby MySchema.multiplex(queries, context: { current_user: current_user }) ``` This will be available to instrumentation as `multiplex.context[:current_user]` (see below). ## Multiplex-Level Analysis You can analyze _all_ queries in a multiplex by adding a multiplex analyzer. For example: ```ruby class MySchema < GraphQL::Schema # ... multiplex_analyzer(MyAnalyzer) end ``` The API is the same as {% internal_link "query analyzers","/queries/ast_analysis#analyzing-multiplexes" %}. Multiplex analyzers may return {{ "AnalysisError" | api_doc }} to halt execution of the whole multiplex. ## Multiplex Instrumentation You can add hooks for each multiplex run with multiplex instrumentation. An instrumenter must implement `.before_multiplex(multiplex)` and `.after_multiplex(multiplex)`. Then, it can be mounted with `instrument(:multiplex, MyMultiplexAnalyzer)`. See {{ "Execution::Multiplex" | api_doc }} for available methods. For example: ```ruby # Count how many queries are in the multiplex run: module MultiplexCounter def self.before_multiplex(multiplex) Rails.logger.info("Multiplex size: #{multiplex.queries.length}") end def self.after_multiplex(multiplex) end end # ... class MySchema < GraphQL::Schema # ... instrument(:multiplex, MultiplexCounter) end ``` Now, `MultiplexCounter.before_multiplex` will be called before each multiplex and `.after_multiplex` will run after each multiplex. graphql-ruby-1.11.10/guides/queries/new_relic_example.png000066400000000000000000004704141414121453000234220ustar00rootroot00000000000000PNG  IHDRnJW iCCPICC ProfileHWXS[RIhH #E^%BBP# ]Qq-,6"u`CM N9g3fPefAĤdP(@DE(MHk֒X:_Eq@ N89  t@\ XM @%8]5$8U6~{@t%YtGQV ''ͅ>V99 V"Cl]LfaY.R!Eroa-C-YaLM *\8"?f l0!f|؞-B{4+ǩYh ;"\gy/dD16ia 3bd<3!eń}fEFMFl0h^ - 束,yQ\ 0X]rߒ(=V3v@T3{5lG(\rbep?X@ [*2sy6trͨGtD1 |Q=^}z1&4a07bqWǽ(hԸIVYe5EkVYjsƗ?86m=O{vNN&:L]oLuzz|z'`|X٬J֠~X~AAACazvA#=FS]377~obj`̤乩iii}3YYusy. "â%jdɷbmErXZݲYXX7X0mmlm^N033fO̻XձqJ'''$NH\x9I+ԒLJOޕ<45`ꆩ}L9tghȞqlLC)=)ّZPjHAg#כy=OH_ޟQ1W_egn|U5ݘCI9*Pd ҝ5gVwenInO[ކAapMcNLهk1wgqןd~" S/2\ToqKK^d[T^vibŏ DDXrk+VlZ[z̶JK?\ȪUV׬!kr&kZZ_톙.VLغQ2eѦ5>WeTݨnܬy[[xߪlmmmo5AQq~֮]_u=wwߣgu n;mo>}-ood65כr9GGJM=-I-GC淺6cV/>>rܓO=n~tg<v¹s?qBEG/\jtñtl|˵{R^WO]vz7"ntߌyִ[=ɾn{>P~PPa?s׿Q̣{9_<=W޳Gߋ%y18Jj7oN|>5]λ4?Oφg&}bk#9##l![z`Cx]= *^RAdE) gRq3J l`/9zzaE`/E7‡7:Z"22e'${y;D|\:;G'kꞍC pHYs%%IR$iTXtXML:com.adobe.xmp 842 1134 1 @IDATx\TW&"UDPDcM\M/ll5)IM5e;1լI6M,FHQ@4A6lH s{>;chx 8}z\!        3 7$@$@$@$@$@$@$@$C P"        7$@$@$@$@$@$@$@$C P"        7$@$@$@$@$@$@$@$C P"        7$@$@$@$@$@$@$@$C P"        7$@$@$@$@$@$@$@$C P"        7$@$@$@$@$@$@$@$C P"        7$@$@$@$@$@$@$@$C P"        7$@$@$@$@$@$@$@$C P"        7$@$@$@$@$@$@$@$C P"       w"8[XYQ[^9     8g\n nhS;|}Y5kNI=.=wGSERkEtn9!`1юIq٪M!     p _?V՞r:KLtfN1BsBH!p٪se8R     8=.xZ#J\/˶kW'$[r+Cw      pY3`"*]퉣|mh:>Nç_玎MF#&xXG lj6ۗv2x^}͸Z!v;܅3boCEJpoU-IU.nLIljL|],v(e&AiJH-b'0蟧gM&wѯV}}Jw;#lvspvLM1HHHHHz3ӑ4:3  ,X%VFlZVU'~HoX_~/6G`.N>9RO?rڠ'GuT]];95%GDkn¼iZq^&U{P!,M rP܈Vl0o73LkcՕ׭_654q?v h^c8~'1l#xpfmx/h_&OՁ|+_8A<47\Oxo!Smb͚PigいqӍsO̷n޷+>T7+/OfyX \b̊ %(Δؕ#ܥAQn͘;Lü9[o~D]1[q뼉u봺.e0`O} ;q|ۯ1wBK13 ~gOih#a ;F=V]cA; ²dx=d Oqp|f=?- SBXXgDc;%ɋOࢠ3c SE̡e!q߬3myǤKÔ+vL80"t%%PF8,1a`͹aFppDy[jS>9Y@D [)6;m,}gޒ%Z6d˄V_?G2b<9箭pbى6b.tzV텫oƲvjF$@$@$@$@$@x]F qH>L.9w<}$BĨp?eDz`~OVhNnIT,+jwNen-WuG4̾qÄ[byQ'J}_b1V ˟BkƟm$U/Ҧ78?c5oWVZNgͷo2cx2Ncœbx t͈MORV>_9!TΣ8BgO^@9w1GƄeq4>{^_C'1O,?`4wo˳] $@$@$@$@$@$K&u EuMe9ގK=>T/sb$5.a?ߍp!H=tF@0h{B`yxu6l-l4:)[xpq^7 ]|Z+UKDz &fRӁ-XU1/}L'HM')v emR xWXW,mp5[{TkxDjwe:s"ҏKuhm,|Դi*=/K>Ӊ6ROK(XL4?N; %7Kb.CAh#e|GZǯk/@Τ8Kv{=tNRs%;Ioƚ!v8QS2O\mDa0GFA:x ,ܴل} X󲜿.X]r oZ@i=6ߩŀSŌ#yj 4¿JP-4rޗᥗowMt}Mt)҉;&qN`bO[TMbv@W`֊VyjP\NTZMZq;b#e_(%) ,M4ǽV@ vr&9n1!ƗN "bgޤM8(M\T&Qzc#DӪ&`Sfa5KZ UqF_-ZUϷIHHHHHԧ쳀dh oq3@glQ/SK;iWmϱd՗m<+a=F/aMT²hJg˲Ē;_Rb9ۥ&k|x#>Y]~āS f5Sdس~z]Ugky!>+9x|NP[I zR؟>uYc$Xݴn'R.P{g:quNTl9~Z76C$@$@$@$@$@$K tOֆ J_ O=R>1_5U4UcW ˃)#`r؛WŠTwXS#4XD%y3vQqu3"ks%:1Mk06y}.ƈMRaEso 0&i,^{QqB6:IJrIkg"0j0&E)ej7*SIFd+}:%q[xx@5i& 3Gu.'Ϻ\w+{: !-ۋ;5gEVQ,}F      ^Ng㖖,}J Mu=rRŎe/I<2S}@W5бQNdcr~VN] ]3{3TֺE2u2z m XQqUˡTV㝕iJԙ kv#yf]6t\c"R-r%VA!'x?ܷJd'3hzb)_iM/eti8`ΖbLk$6r$dah(Ūig|HUa QڅڙHHHHH.t?(UaSv5b:JƆ#ʶrwk+ZR)v"ǚ!&:~x竻\.8uP->~?D/L~b][s4U5~H ~d;JcXT XO0+KqHdٝxC:BĞ=+=N*@Ӂ x/8B12x0*󄧗|p+Wd7>QTj,J~.¼h4זE     Κp# / LZֲ]in|BGw"*|KpHҨK#־^=߀GJWDcJ3w%Xw6Lcљ9<P!}1 ԅm<; / u % /OSMe6G!n>.?읪E$;o)߶m7^Q-JK7\?󱹎%1:- $@$@$@$@$@$@sNd0m9ܕݗ:lBzS;,,D hv:~0-h#9a>7.uY՞^Yaڱ7Դ fV$:O˓|GbmNQݱ&LЉ>݆LؚS-T`f)ĉ#vaE5j qDihnibFtveu8\9 7$gNHwZMUVK~d;ꮩF0rZZ$t[j~psNF\y,?rܴwoDW+lݭj.O{$խ-9S3՝Z Q-8_5zcf{"\`uaA.ۿWӍ`,Z0Qsn4 n]٘Rh_ k5Z Fa+xM0HHHHH~Ϊpno yO܄$7zQ%"[X-]~κan}aE+^ qXOGAʺNG'#ODbmrhލU%b}AJ[4|\r~$ ۶(و{QMU݁ukewb)='W2uSg %=2Fuzޜmd3GцP|7FM8VOw0IHHHHHBM_ת/޲/1 .n]_6t&h芨'6QIvx?Ew5;\]Ͼ19|U~H7XYb) bIL[u˩⽭L}%s'2r^dIH3˔c EMT-XCqI26U@4&w|k*[6%\z>Xaԉ-A曷+|gq2-HHHHHH.ܜlES M&d49ۿs߃痩R/_hY]՗4_{5V7>N~6<87ӷ'яv* ~Iy >houSo|E]E]3gP؀L|ϧ?|2 /Si?ݷy)k̭g~4R-Mq+Ȩ@TT@hHTUS64 qdja?׊H*`Pxg 7!wNfTm1'1Ei/'xRג%t*'O@?iml8$ɛ)ۄ+"1c022kn>[(;26 8ˤ,eݑ0s>+ҋ~0CN?gc}wq_֏1b5T~JfϮ;L*k?cDD ?q7v::p]GxLF$p?-MJ?2W>h߃wCҫkPT$++/$jC}K;D1# \D ;nKJ&(zz8[$ ,ӑ+Ծ I]K %Xs][F\ɸco,8_uzfO7jHMl|o(&4aǟµNWl{:q{r>}Vm <I:وYU.?Ig$eJJFi ;V9V7 )LEHOҜ[([|];if#xvuryŷ蒋6⻕_c߿VMӕHoL<~>ZT"!Q/Hڔ*      0pA;\ճ@ht"nZ(|o7O_Ёꉫ!7{ux!C:g@f^7{J{H0u~j4A2_KC@?<|U^%Q?q< s @nxaIHHHHHHHЯI-8HHHHHHHHp ."@$@$@$@$@$@$@$; Pו"      (! NnzuHHHHHHHz 7"r$@$@$@$@$@$@$@y]9*       ^@M/ @$@w^WHHHHHHHp ."@$@$@$@$@$@$@$; Pו"      (! NnzuHHHHHHHz 7"r$@$@$@$@$@$@$@y]9*       ^@M/ @$@w^WHHHHHHHp ."@$@$@$@$@$@$@$; Pו"      (! NnzuHHHHHHHz 7"r$@$@$@$@$@$@$@y]9*       ^@M/ @$@w^WHHHHHHHp ."@$@$@$@$@$@$@$; Pו"      (! NsX Fl>.m+n~:JS21@^xQ9$     KgUt#`tL~;tv ͧ$j@SK2 PGԝaU0$JlHHHHH؟ %P.^[w؋ʡ@ a[~TU4 ;5v_R 7ͱ U?#RTsHN:-M"Q)pF32'     nhڷmȳ) R GOYksWK)tHHHHHHUm(l*B)M 0G* EF[{`8b:'v @gsUg}(ˆ#Qdd!"IqKnًV'!6e4ܜ6҆┭(jlI~kg KWqyoP ބzi;`k}RSv87{E l݆epٽG ;E{ch3 en/9 #O1ؗTlfPıivN+S2&\XFr iW~#qipֺRlOMGf{yaPxiLZZPg(     -J,;II 2!l3rO&Zgꅃc3@1"VclbSZ7H 7@{}>>_[xUhqv h]㈂-+6.؄Ɔ:=YZY駾`#2y1FK 'w >vφC(&{n;,wzZKQ!{ĄD"TҊ1+J+4d#2{}(Ns Ap/A^)m51aIB ?_]|u @o Pcց !a GaLUHqV3hv"cT׊6}|+ٳ#."@.j}wGdt4]4>6)Nh?kЈMnܠӐ}04( aAzkmk'đh;aa70.͙:@lfщJmyiJX^.9ޜАF' Kgtoc~eqHv>ﴸ9|Y; /@S:[5 k`M291ynHXg PBXjD&&&5jxbh=vt<&mk1s\u뺣Vͥ%*Rd  PrGuu09"bpqyl,>NGliZ6+,bC: [n[NTS D]|,|,o;SFK{E#>~(KyE#awT#\¸`ÈϩqM<b4 [>zY 1z m_MST= SXǏVƒ*.' 3,bWkvcZ判<Sch#7Vc9`ᒓ"\m<"e+![qÌ$h#9ɪߑR' Ka1O`$.})dhc_DYdmPY 6!cՙ7 3,"a S,K[jpP^>h#gƔ)Xy=]x }aHHHHHH$p"9J $j-__02ɋE,1Kx,ۣ1I,چFBͻ'BT ur앭X#5u^p@xU`[@Yg-$,TVl\lk0Im Y_͖/b[#%D*,ٝm',h\Od 589@JA=J҂JUqYV,Zպކ5eؿ/iؾ}Ak+TLW^>VoDMcy$ eٝ'b ,GhDyZXK[.Sz?$ԥV}}Qb(])Isy"|8lFɶIg M$@$@$@$@$@1Qg* K병S?4b9v앖Cu2Ń0cfkU[auXdaiLryb5VgG3X}ml΅Vae3#rlb{Uwˑ^aN58}Nø0%]gO]%А+ҬZ-WB{15l.>^81N9hY&U'G6ͺC[rU+Dn=abaɇMKl܇[gZu%Vϰ5n?p}v,I$@$@$@$@$@h.ڃr*VdPfDi x&^>X|1DY9]Ae8tB8) WtL>ˊ,]:fq _pwBkm9A;vQrݯXBz%Gs])*Ejxq%%:J&"S֥MBLDl#JKNÄrI ;"~(Djt΅ݐ$|ؤ#tFd.D(-=uݥQ묘HHHHHdŠ}G7PRq+v<ʳP֖pdʋ-ƭ;̵H<1qx,A|bˮthKi^hCڪSgcRxkvZ65ƉO`,(^-DZ/m'R2di>VbѵAճH7Z,e=6!fk.mS_Y: vQFΙpXb5{sE:;[Y7Z$~_R/:=na^l~2̟=aSW9qrvc$@$@$@$@$@$ЫcwZ;k"ɱꩃ_ub‰6s}GS|a^ c2JݡiT/6>t܄S6chpM~"FDp⸎:Bw.^EΉ<[!yT.qZ1Jcx,=SڣT7WvXh޷m(2Vff(܍?f}>(OQj GwH(Dy^>C;C}=0D^vHHHHH P+Kql9FXj wgbKtːF' F,GU~.k|wLp-V:/fsCq\aWFsSj^t{NX-#iR^} m.+JON3p;xY!fyuiv[ Ř'&$vHk8 a){depɉqbd_P-)؃BهnqM3bǷwbgc4FJ]׉$    q#3WN{8,)AzXZB( 5N񰜟,M“D-ƕ"FTV.$L9ĸJ+6}=ur;J>9A޲(ҀZؤ 6`xτEEU\ƾ&4F?i/9g@þ=FTi3?J)-gWMN jlւk+\B,W".iqs.(      ^G,nON9 6q$%Yi7-B} )~< :8j7o(Ήg٭G$.VѱaNѽDD sTtHtb1.zcK1syfIHHHH.l9r8_ɠ ċP)Py8&Ic-;a&>X'5RPj}o=Qk[;Jמ6͙{nZ0 }EYV Bm־ Gɗ'aW-gjN,Pw,Z^sNP8u/gGK\xI Qۓ:$˜1r(EfvPlP̨`:NI!R>xzi۳)z>Nhm JjG_kCE1* (_,kB`ܚO8iG,1Ȱ0WDxܖX7Z*tpv&D8<Oo:ZΆnCu<^z[77d] KU8MGó[Q:tg2uRvnO\9`q,D S%֝'e;dWr-{$2" AmߡԪ㉡]}6M$@$@$@$@$@$ GVS}tf:9#>ݼߜ#,q9ܥ?mFTWF[imB/ ^_Qlѓqy 6t ZD >h?ymD9lU85_(Ϻ Gk,wmODѱ;.m;K]X/ov i8P~H)=NE)Ӡѣ#&6Vx %tpưpbuR?ꈹTrg&^\-3^kpغ$ J46@OV9.3zL      5<5OPxv]1$У 9jlS *ʎ=1<\z)^ @x"T;M)㍣75Jbp( W0u{}R W|,ّg0Bzcq1HHHHHz6xOU!jģ qpju wNShF@pkz"L8;P575P!4U5 B(}+ 7?'     8oؐ93LY#pLX|'; $Ϝqa=Ƿi 72 I@,ڗ o>Va@&y/` -;}&ԕ!;, UCcYV6qS`HHHHHHHz:'A]!       - 7Z @"@] vHHHHHHH(hi0L$@$@$@$@$@$@$@=t1       p0 nz`WHHHHHHHH@K$@$@$@$@$@$@$@$ЃPA]!       - 7Z @"@] vHHHHz:0^$phCS}xK](5ۊܽŕ8z\gAX3"k"05 uF؝\ zbhIQ#YHHH:%^_oEV pUl/Zѷ O$}t{})6ս_Z=1*RDP?!pG 9˾_|1l_h3Ӧ%8/;h:l w}^Ǻ>B <'7,R{!}mJ)wx Iĥ z({9#xxݯP}%Jrq%qPҵxx&d܈?aŻxguÁU #2Y=aH?yydDax{I  83-hTW8,;܆hMs6lH3 tM΂ƪ}-,,tShnFkVo(1z+Lx̶sA QXX`n*~w#E]iKt5?;-.Pj i.jESzM/c>tbxk0Ne,k QMGrq6Xy~'X~ {B@[}>OcÔkOq#jI oT0O,<ޝ~&jAc$̞~2'!/y$bϑEk|q,:߰x}}WZYG cI3?#p^/c[8;ǜA0Q_WJd2jhd2}swsXԉ]dR*j/>y®mS^[hY%kuxwXzf^6&3cٻ:{¼ 8r y;tUX+`WBn;mbYZV |;Ghfwv)>7b K4X{ctBn󣨨jFD?RX +SB2] ;񂗛$nnb[9BIܕf1y[v!L YLJVsq* KշkBۥ 9t:)5aGj1bgDufvO`4Wo=*vP> C/8+9emy`S~i$#lx$EHJfMf` ؔVN 4Jy<;m(IG7#0lׄCx!fMX'>\W^:1@hߦ9sk-Rׯg~1s@{/>gX%>[ǴEXr WcN(8|pR<> 0Z CK?}2 y~)+IꋚߦY}?2F\ ׎ǶԅpnDaVMG'Bm# 0.Ƶw9 >A6S#ǟ}pS-\qvZ&k(ݍ\J1900 D\' h9U{IwLo>CbNw+n7LO> U+A_cs)wk 6W[-mbߌiôI36ю]œqMhs8~^&VU#G2V{X4-ܮ   FM1> SEP`&UزOs8\X"Pe0&̘[.SF;weH-ݼ08 c&LBl;vqە%3ZCD{тtlEU(kPrhcB4D/mI~IvשU8gݺTXv$2wFAa9MVqHcuMԡqs]!oIGImb=#/ͤkhulD-*pI =SXEH5KݎERlD|I\ +W]x$;SsU=$vxiJ+*T`x4&MJFKxlLF^x"XlCqV5]7ҜW?䴓2ɣf@c24,&"Ktk}R2PZqyb^Lm-Q8T0&9!CyqɒuP9083s2C`_]ѵ>")LS'.fl`#~A?6]=^w>Gf iguHj+5**-MUA)RImSʹfҝJ0UK4뒁PYi+Ei*ي??`#$WYTzEB ä<#U0ۑΎ"?^phKQ|ZĹåV#Yw ;Ԓ@~ 0Dmy#,։e؉x/!UcJD,~!WsBAMT11o$[7 CZO.YBqp|;z;fru6 l_^ĮOr%-XJ&'݊=x9s&m[/r-uO6on^sv}VTRYZ) @&ЄM 3YE"x`v\e.=#(Yu?ҳl4YJs}uSeAfayY{M5C8ݲb 8T+0nmzN򷽲o=allK~;=Ǿ{-q7 ҉Qe yTrX=cp31PN+L寒',MAԈ5e5N\:%x&+'x<U-8l-?6\E%?+J&4lo5rmC8N.5<'1-sEi˒>'qNQ `ʯ@Vc:JE7jƃH53[a>rX|Z59<8/ nt! z\_HR9\~=4hBKG1JQ q!$%6 1%7R*N>~jDYz]W"6ѡh#W$&\ XmL~6rx7QaY+fg6"q`?:}xC_S.d4Dʩg#7{]}7ⵇ^t.h 3#>~e6jN@~vڅhPo/\Td÷[ZmK;mٛ>#oFم:m̥xScmZd̓HH.(hg,VSm0o[QyЈ|Mzn>MԴ]XD"2HWkm#,"C p0~fWqM;h#mm| ,4 jr7IsU*\ HGAR4ъ\h?C v-Qf ˖ZbAdj=|ߵLj[F-xF Y C^>?0h՘*?bĕ_67k{m)J~EM_#FPFHBIQ\Yxwܸe85ߜ,嫢^^o)Չ60hS{mD>_m|{j9}uBU5?"ơ`Iags~Z/ Cdϣر#Cr49$<{6Э$#f߆ϟ AO1QÅ;5bϬpI9H ,ñJB"E0>V3O?b5DXluٺyOg]xbI>ܡ{ϳxDY!~$4n<ld=xt{9^1a9}W`J:Grxfzg^;.9R􀾷Gv ]M^̟HEy_/C8M ap{=6)r?ʄˁ؆6gY~phez֥\Fy!Ϋ 6KP|xTIa?7c{\!p9hj(‡[xjppfi[QF`|\d&r⩜5(h1BΣ00I֜y "E'(]$@$@$ Xx28=4C\,l.4]AneA3m߁ll;8ߣ [nD}Xk)êA'#Ua"c5|LAikuʏȗP\{ & |!Qjs7#o∳T_`agf̿q%]G7oϑtOBxlX/iYWa^R|mX&/Yظ? Wtb]2"y˓TDAI݊$R6 *H:e_)*IGӾ§d.>=Nϓ}|1 /Zr7lф1oFY7Wk3,B,ZO/EӖw=5>k16i5i0 T#(/_hmKhzYfEUQ 9$%ZJx/>WGŮ>T@WyuѽXT}u1č"dPS kb?;:n=tuxm,P\ф!"y'גa* m|Y+BhE @"<\<ʋcP#֎z"&jy/~Xu|| fsY&L3 wF}85xcFt[h&dt&9Ҋ`vgW+݉BQ-Mi p[AS-ҦC)|m=5p0jvǥ߄8˜A p{k>cE]ɒ#,u/W:F뒪e;1cLBYwzrj@\{ExW,Q5u2q-wbR+wb|«|al8q eNWwfS:b5>.V&m>1{L!V .Zw-8z,]a< (^+S_\uU׉p0Q+uۋ.ì(}gMFDFp҅F[xvK%YâƉ7?Jրp6kڇMiZFz/C@ .颼 ҈I0ZJVa^GaƌDIs0Q-d-|5B1 )QĔޮ@Fp=3XN kꄳIJpC 0!&"]t9Bl oP׉6e-~YغݦEL)ÿS_M@[kbG݇Z u#YYD㒈:Z<?ڏlGuO]C5*HASR "H'Gp8g{\~xjkk j[Psn<ZE);w$YB&=%lN͙x I8"?^v% FH>LMF͊h3tL2rK& >A/Xcql=;/%9A%m]JނZ7:yxxQü-8T/}O vAĒlkH@h#g4~"GSQ-XH]<^C]ȱwÐmtWbDɸzl?(n<&it+6;`)?6 rsW{?;T6$"Ja Ӊ6Rt e8w&Q d !&\ (r((ʺx*^*zA7rW & zf{L&H|TWWvMOW2t*+ժV.:5%] >͕21zX[ɳ1( ƣ)%= K,m;}BO\q޹Wv}2Xe1u y9Pߕy%DAWrm4.oeT)S~S[7~ Ø{<ki,M0L&otK[T L ^<;gxu ? sYbE!~d\,_ҷlzR-Ea(O%~Q:`mEh(v++fϛL%.ܹY}ceg{wzJ/A v6h1!HHH&.|g݉h.~GbmNѳkV10˒zkP?,sޖ]h|Q>&|,q> 9d9j շK&r)/Z:$V `U)eGZ6er8rJP?xۖ/w/u-{b]_jhԦ,φ=F}c-8j:z]"˯\^IQhR_ ɘN5W)RS+NrXw:[{f&"M۩~+k;Zaqj 6;cDȭ@oⓠ?2-t|1y,(m-*粱0UhTԸOTgd%Ln>2 񅙘=>-T -e s WTDw+W>zE)˥rjXhTlg:e:wb,[|(۱{;VNy[/O;o$EP5nR ]K8fdJj"F;/pooŶ>~uh>w1sM1% 2Dh׮˼F$@$@ Wb9αCmxmJ}O`3ABjR4iD7܊]{hRHKx]_PJn7ęu~/;-L **O+yIXu&g Ÿ8 ڼ4$$@8թnUJ**$WIL*EA| **%iƜa`J!*bDy'ل6~y74soׅ?A}[GhGi&Yk:4!ئ"Ζ~(m|E3̕EF~/3M)ֱ&̯҂0դk^Q5XY.,cT/ćrmCB m$m[ MuuwF]7ꖤSN߆W}¬]V~]M7w{ǽ缤1DpR[jPIa-ZHH.m S6'";*l#Kzw7;'Yըm_mS_ԉ)((ʇ㏰j崿4 ĸ@[]Fx=ZJz|ySP8õk{ >IJ=gDMa:wwJ\DVs=[_D#ٱA.9)4Ϲ8 vsńF1ScW#_,*ZP߹TQ1*;x3>4^vOPo"F̷gcڀGG־%m閯'  PUFCSe%A']+ : ׉%,& PCmg(>-C!2zNk0y/º-6uÿr4Ā=X/;n=kZO v]#Cb؝RZ+''S+}W%-}y  aX!GO1Dqb#WQɞjqhа@dK% vGe8'x胞ݒ}ѐO-򹠖g"3&  ߋ#U#8ƚ,*Kʛ(R@A wEJ ].}O*"gAHie¸MW|ǝ ۨ;1gf>u)׹(ˤR.LzrN(^IeGE~,2g~; \y۝_S=LKv,\ &?~9=eײ-y. u=sx)؎lNhb%ޘp+^|oϝp؈!\qc):u|nګFRmW,Gp{`sc=+78@VbͨG!$ p:)HT(HY># x;P&}Dz/6JCNnǮ\{_~T `غBJe{mL<SU=.FiGdOgu_}re=_m=2 ɞ}.!I1$@$@$pA l_D m#iG>i{Ӽ+"/j ^򆼺6GSE`;+Ka&_{eg 3/[_#Y+cT.y譸et?>g߮|S -?8cPS?>Wj`]+-ׯBV`يNUbUm͋|Sǡu47!&KOH(4|[eя'5Bf$E a੹9Ss:ps$VGc8;6 B#bڿ;\)9S ?JF1x6ݺ]Bmz溩; t Aw!_Ա4-ˁ ɷG'3Mp1NuMb¨@]-AA'|ڰ~Lsl=G7L~y+烬jg w,~}p 5~?m[6b9X[hZo_}vGH: FѥS;i)WaiW!1*N`={ht֜ld:mҰ%CFclΜC^SO'8<3jېaԸ<7XH$@$@H0714Ŷ]3akjliU]NjSjjcһR maeoA-q+,1<'b!bEԫ8ƉGe\X$&zXda.ﰦt;BEcع"^{5oeIb(kIFc1gg|ܖf!Nb쵚JY_q v߅ϗ9\^-nֵ5:LN[Q O`/TmQ]%G1k/iDZۑb9U=FUzK@&(#B K-"sy(@Z[U(թSS/L16.h cw%:Fۡ)I̟!櫉ݓ0{nc=6u'&x=}V̘(yfc@֢0dþ][aҳg܁ycW sS3]=)7'ŪИ_Mʕ§-;}nTnqH~'M4.KěKTOa5E(\MoXYSOR2P5\X8Ja-qk"#-,ϔ\֭>gnӟ*"2 _0tͨh)[)g_nF9Ex4|eZMŚ~q?#/Rкɒ)dfCG`C[B+j)7όbܲȗIgn@v8Ҵ;Bºim4C֍e( Yѣ8pՆ}_G  N]-4BZ>R@xT+zAH( %.?NwXZ`kY+ŗJZ@ |\8< ^|wyJ'$լ ŽЙSp~} *$`=C|(v-sos~%] [wa"i_Dz QF$ EzM ^IS]zx=>]F凮 (&7~T w(_ɩ>xpe,r$^_5rRD)LqhLS.::Ƹu"NoA=%sf31gQiSWC!lk>L<;7VpfFb`<Ꜩ? ~r6mu=Q6q޳Ŕ)n+n^v 4J^KW^7}ѩ 9UA0q,h?zθIk?귿caȭ&eR[YNd  ho 9赸ϋn8s?Ɋj1 n{)n}_H- pҽ*xFUOQ}<Ҫr5$@$@$Pl_YHk-MDkqmcxxDcjw%z*HIJD%~8*it4;XW{(܂A)[؋ Hvŀ1C"u-Ax :\y brmwO2d\8 y*;[4jzYW_X .e3,wp9gW\}JE\p7Տk J+WRv^*Zu,zYQק2*]UaP1RJmUAr)D:/7P,s"hVR>abQcFz?.XQ4WSۙ\ S>b(Y^DdjXDVқ57[t抿<1 -zDՐm:fylKgdoĥ]6雹(0 su,` {&8_Lx|g~˵hV7arO|o\1,?1L<f2dza=[|?y6nEwł0oҪQ>&tϘHw\  ΜDFW"r⢊9x ( 3W&J+&ٵH^rVSQ K"q̵Nq՘ @x.u׶ˊcRpYq ªҒ?+VD,˕]TD(b{Ğ@pT:/{X7}fOjn/y\+|5)deGXUe9g@)+|tNmjo~^O)9JHcPÏ6gfyPʟt~zrCl\\1p[EHQxO!廴;ݱ͐Bmkq)`PQ]wp}}Cz'"2&W>sq8}}[>1tvb<5ˤ?Vۏdmnѻmu݉-uYkg+ge/nnWyW "P%ʶ <dlP:nߐB 넔uXo_~֤/m1YETyej0T, ru8.ѾSi#Wv0/*_ªTڛ. fle'.Z+jW~Ca)ɪKQVRrq-Qs\xeg Qh)/Tvy0Z"S7C)BuhH(\j=}C6 vI @'P\G짌W"8i%MR۵"f2jk_9"dl *J9Lt[A~0 qϦ 5ԿK& 2QݍQ<+Us^zK/L be`Ê͞ RkKN$@$@$@$0S\Wo v*pO`e{\p/'6ќ~#" HX^T[ƶ}g<(NW&bOkѪ)FH}͚ CX%x2O*$߂;_@ P \ԍKRu^1(Y NY1p."HHHHn@X=WSeV^5*Me%HPI+m9٥G,8o[@U՚B|P2W^¢5MQTRͿfqcF[[J-.q{xuֺ8w#n\/&"   N|h}neZ֙~$p&;{t6oۋY'a=#/GGE&Ih֬ǔnDz 7SꪴR7OAI {@rs{V䜕tpq4X7GnRY Y͢bqF_n.3UfEH*nȍ`5HHHHHHHHwr's       (#)#7        wTܸ9 Tܔj ;*n܉HHHHHHH*nȍ`5HHHHHHHH7DxN$@$@$@$@$@$@$@e7eF$@$@$@$@$@$@$@$Nw"<'       2B2r#X        p'@ō; @!@M ƝIHHHHHHH⦌VHHHHHHH PqN$@$@$@$@$@$@$@$PFPqSFnA$@$@$@$@$@$@$@q's       (#)#7        wayzz' yPobIHHรBCx? 8.K J$Kf/iEe@nnn+F$@$@$pqฃ" pq GQ0JE$@$@$@$@$@$@$@$qB$@$@$@$@$@$@$@!'@MȑR 7H)$@$@$@$@$@$@$@$rT܄) @hPqB$@$@$@$@$@$@$@!'@MȑR 7H)$@$@$@$@$@$@$@$rT܄) @hPqB$@$@$@$@$@$@$@!'@MȑR 7H)$@$@$@$@$@$@$@$rT܄) @hPqB$@$@$@$@$@$@$@!'@MȑR 7H)$@$@$@$@$@$@$@$rT܄) @hPqB$@$@$@$@$@$@$@!'@MȑR 7H)$@$@$@$@$@$@$@$rT܄) @hPqB$@$@$@$@$@$@$@!'@MȑR 7H)$@$@$@$@$@$@$@$rT܄) @hPqB$@$@$@$@$@$@$@!'r@ۑ_1opU0dXiPi9yq()S*Gvh?6(&?u H\s͋kF9l>AX&NA:~Aa+*lvJh-zgKad&_; _o4Fqm)+^]Ygܮo*^( z -i J,>5F*oB}-e=3KN)۹,맅QQ~|]Q'*%wGc_z*mD*hcOq;#xjFIRu`TsY0FS9Nba##1YF]]iKVX}#0!\% }β_?]OSMi\$h}s?tY|@B HnҘc@MyS@ѯ<̽;yvl:'XZh١z=ԕy`w–5p:2ED~C}jپ";W-ê5RMbFضso얌HoBD5knul.`ǩ]',AG)`zu%+IX1MwK9K/Asz"맕hE6bظ,WR1``?4k] [OXtRc"cϿb,sp雭RSqYj'ĩkgfY\Mg@Q%GH6̛#6:H?tRW ޗ?%jCTB'q"(}ߚe?-~G-Y{1H~ _9MiMW]KZy ~LBo*mڞU;GS{8[1zٯX/ev-$4n-'쵍ot݅ cSp8p&-Sк] G{g?o?")hK$ke3aRW]#/`k;.җvykJ|Mh[Fr?۴gk:DS}_-ZE.JSShխ7h+<5?7`Zs7s9zoހ+VC5_8j'[ Y+EGHQ+/ 7ՊFOLp @[Q~ʱP+G4FHv4=wwã-+~$rI{yyX߶9D\V̗7zV]>lBتyk^CSu"S s⋽y"t9.nMD?.F;Ek92%SH֌ bw}cuI2*T13o4o#U1% Z'uaUoO[r=1cޟ/k?m݀YS1gMζ4!OqઘxRmQjॅ_-_Y>^OpQdqkݱaf7+zֿܳZ@S v5޹'!m$|U3UL{қ:@R_,tw7Ch,YT;n(CO;ނwe+67õ~]O0@vOG%oka-)r;}c;^QTT^DK2&uzi|2 @o!ښ9`TdgŒZqTgaO.gNO}2BC<;uJŹyzb#ݫ,c~;wrAi׌S[;󬊛-bP0ա5u̺ʲQxzJ^i#ۧG_RD9i;^i4ad0Ai/O{Qk^!uRWo Fנܯh ^46)m?wm7@>)mhOgaǃH.iU:Uib%0xCrb yv&O[]g N*ԤXӷh1CsiuOHsnS!y$:D\Hym^gѻnܯr< &C!ӯx Aa<˄% [e!zto)]kiGV'ʤtAdEvax;}8@?rH}}m{N[qnݍ>u~9nҶj)|L%30(rEb;oL|[1f/ށ#u Τk~kо$yXI<'Hel_TJ}ƯfYB7*U7I^Ocjޢ-npl^簘X?oX[)#ØuE;K+?;?x&oBVȲ{_ʙ~Tx;sYJ/&3(_tf4O?TbEkVEFbJgbŤWX}!<1 [U8q^gƖPYJ@ؔD%{F)VNy<W;Nڲ&Ly>VB-\o/?_eIƃЎ;# @ `Ft@oc$HyZ>M!pbyJhEQux/i,:'mՅ{DXn÷_ut/飑2j K5{Âk_Y7բ( 8^™jΟزh6a g;+W^th^9uzT>eؤes]p $۟Wđ41<%{VFGJPKT%K8:bSZ4}yAi$j1/?4P˷%\t^ƃ/>;MMp?]2Nd둇`+ b< 3^_,q&:pW˪ f;S1 hrp Kfc=Eb;1jq3~v(F%)=S8RXvĝ$ oG;gŒv.!~oP(#Ƨ12(܊y m[wQ ߙVka5DZ9O?Xq;XT+g%su5(mdx\1 Gg$FrEv\*$itWYosM,H{@vK< mՉ#Y$}ޠhuع7S @ݣ}C+Rf\/F{7:u7V4Ne+˒2ߠť0==ՋG6j+~Wt! |)K*Xmz{׊N] WozFټjZ1y"~9}1LD$@$p! 4>P{_r)4doǔ, U/jp5VW [YK ;㦻3'Gh/Yg'{^V}sG&g# 7ԾɆ <ˌ.n#cqc1gV+>«?΋ IԆu%8"QS(6dxWtSy5pM<?c9Z;+o"Lz2>j9ޒĠKHXD8,wZOJuR*3TR} pi?` k7_@kEQguɽytmIR+68%6Wv3}$C(E痱.=<ժ7.p,e#|g77>^LG:LCֺwѐ4gtrS| hб`n_w'>Y`3ʩ/a嗢:ݠbUC*7y] :xpܧ?O/ UAÙXbۢd8.< 5qQo]Sl+M}7A|/Ûe쐥[O6 \8x۽ >#gl״qօkR_ޑe{8S1?E\ڹlLc (0B17 ,{Y;~^YCothf$ jwe̽p ( jOX욥 8__+J-Z2$Y ɭwjլo2gSXeYE6ba5Yx, n_!h6շ-&<;CiSgDٌu )S'?Սr,?>ɜ%<+^ia#` Wr:vĒmuV^|wנv2~[S]m&L|y ٴլ@<3<>v+/]ﴎ;o@_%z&{xߣ4,ʜ̲ݢ3Xqu,lSM?ք*qvԔ^ <ͧcd-$@$@ens @BYrR+wͰ(ݤcv)8xx&~# <nmzelo yi\yh?fsq,MLID96Jexx"rc\ _9f b4Yɻ{y-1†ϖ㮜}-Q.kSu ry|>EvSmݡ7<ߟ])TJ'xcςo7J9mYT!J4R-U(R8[6j01ahgF @e ɡVXs<l _DD۶>+\[|gArF>VueFTxtTNUi;|+{_96ՓU&bs=6Q/ e=(HH|_/ԿrwP. ޔ6CVa 0Y#\2lYvC?}eeM {8_~ju z`/ȨJI U:`kڷ=@S} 3@Yt~} D#dT~b8~]tRR14rVyxw븯좐<7wWp6`,M卌 hú#D`HOux`û>~?tk_o%JoS`[2zp^s:V XvvY$϶LAkg.=㭙:js\; hr1c'KR-M0e>W^6~CR?Y-ñͳ%Zg!w#uY/wӪ#N\3{5Cn) Ȓ!븟\ʹйԮ$1/ @pl#1}òO4.FM0*mqQ)c: ?_&Zea[ӵ.ΥcUa)Z/f݇jF//3W}/U 7+;/7^Y%daSlWٽeWLzx {]۠8Y"s̞6G +zx^G(K$WP"ո*ſ5, FͼRa|1)i/' ,C㲖Xώw_+vK.I]pC\S~ plB8u&B[1F]">/m #A`E3Ox_,V6+6/ ,T]i ,`ۍqOP_thؘ(bCXq*M@t~x^N٢݃[ Ej-X\v7& onOgPT;2>m?o 0bE ꛇO?Y>]SZAV =i3!!/msTi=#;~T;e%ړ=,^v\{㓾3~:>bc#pt"r$]RGRO$@$b{&pK^nc%]vÖcӰ6"#eo;[VfCwcyg;5Om2It5CS縱i]rozb͗~5 G`[k![{_H8J\O8#qD \1\6([v-/ #x1Rv3ax8+U_ż/`;+;s,)o~l-7ב4~<>܇Y2mؐ{|l+.!ōs4lu\+OLmy'8 Ob/IEŋ'uM17=)fO} ayhkζer8ĩ+qț''Ls(⃉c=R:3Gȧ}o>?5CЌmxxT&RjoWX,?nho 4Wb5S=}Y_9~kI4;q@cO;!m_on*'4.pgsϙPkTQAW|%c< \j=[ah~Kd=a\4kM_No׊ܫBo!EiwK#~ޑ"[^q)~"M;)Kuq;|l흅 '_h4 es}!|1])s^UGgyY;yRAצc0W{ѫUL2̈oCbn{v/&?L[2eH\9#~O7 ŏ2q?jý($ .piit9>O8xw(m|Η$=ce9եrUv6{M;zίED_tKbMsh/ץKTtհiP7ހz޿#J[}nE>͘aqll"U9Ks߼<"{n޳fn%bO=>  ߚbp8~np/|M"ˠxqUv:*F.f_h,pEOIݦG'{ċqza"lGv!͹±/wt(Byd<}@cH$Le2yb*# ٹU*(&V """`PYAdP5V?%f='0957[v:s5-{ %U|LdKl,{ۣ  ƥM2rOE?OEZ)}JB jF r`[gkEG9'mQmX<'rr٥̈@lt$UK̓ZY)3Ta<1f@8'}(/Y"ﴴjCEeE$@$@]ȑx+{=-ʘE `.@QS_ ݲcƣza _ 2,y{c_yx^qS/M٭z޾_3 S9β֌HHHHHHH8}7\LrabB? yH$@$@$@$@$@$@$pPqs 4]*o9HHHHHHH8_ 2^MqW{[e_ WoFAΛ_2N#      {䖿{ii1,gIHHHHHHMTDHD푏p4nLXlv@$@$@$@$@$@$@$@JUTR8 $Ő @ PqjG$@$@$@$@$@$@$@!"@M@R 7&Jy$@$@$@$@$@$@$@$"T܄$Ő @ PqjG$@$@$@$@$@$@$@!"@M@R 7&Jy$@$@$@$@$@$@$@$"T܄$Ő @ PqjG$@$@$@$@$@$@$@!"@M@R 7&Jy$@$@$@$@$@$@$@$"T܄$Ő @ PqjG$@$@$@$@$@$@$@!"@M@R 7&Jy$@$@$@$@$@$@$@$"T܄$Ő @ PqjG$@$@$@$@$@$@$@!"KNzzK'2G 22m5 \t8n)u?K'|*nB]@i-MM$@$@$@;4E󢸍!kCP^(pBy &ޢG$@$@$@$@$@$@$@$@ժU}\ .>n\$@$@$@$@$@$@$@$P)u,HHHHHHH#@MpܘHHHHHHHJ7 @p s @       7qc.       (uTܔ:b@$@$@$@$@$@$@$@&8nE$@$@$@$@$@$@$@NRGHHHHHHHH 8TǍHHHHHHHH PqSY G1 :*nJ1        Pq7"       R'@M#f$@$@$@$@$@$@$@$*n\$@$@$@$@$@$@$@$P)u,HHHHHHH#@MpܘHHHHHHHJ7 @p s @       7qc.       (uTܔ:b@$@$@$@$@$@$@$@&8nE$@$@$@$@$@$@$@N KH e˯̅A\-eHJJ kL4g`JăOߊad~     K@h7y'gvl۶32gce3 i&6Algsqlܳ+v֯^+7`3XUjavH b(0Y,[>-pE"_—mgj,^VmݺuA t{dyM*1ݙ yq6|{yN$@$@^  c,] {JrztF-KI^d kZ;2&N]{sy'm+eA аy{ IK6S1ƭ;kEla6Dvҥ3V7{3'cfl9t罅(Vˑau9q~ c g-Is !fhSeB̦-gMC8:-Pv\2e.r27m[疿F3_UaS%(aǦg>,u$%G#^ڟjCMRUjKKThuG$qE,6f,x>3gy}]b-AGre4\w7*7N[ Cxg'YL3w!>\,xP8`5|x[锍8Muv6~!P"phwXէy$Q]d@&_`L#`qSՉ8kD.}гcY.sitq$]7 z ~ 5s(H>2!nmbo]И^ۡuONMعh~^ǜy瑸06, ѯNn毴qLUHc_g<ܟ=_5EXh$DϦ υ拈7m׈Ft gr/wV˙S+^Orl&r?|pG Br$CMpԮSj~-a X}"M8 $v 5u2~L 0&@."@osڔQ:6(;ӷ'e)NBܸi4j(,~K09 TA"E6yBۧۢVhA!~dL^N{H4QhS[F#ij^װdxB6i4m:W! >>bi^͸;KY|)] ]vf’?vI=hT*+š3Jh|W~9rrqc>!۽MkICX|DvDuH._b̡/m0L>H tp` yUw;9)t0MCD*D^`zv p&`o.:?mٽ񴃴iP&(8>&`L#pllsx?{53(<Xwx~1}$L_r#;eGD.k W):{`[ZA¶tvHǨTq,O!kP4mP]严Dϥm;~,i#4?LCPGa A/l;_Z1l/A 5:T]1?F2B8"vX#WBW;^ .`ѤO^ΌCA1?R=bZԏVK#mgo2(f_9g_?}U/гumX7HзuU",׹`(|dXJ b $׳ZGTn" *,̚376`?39% 0&x B!$(>rHtBvA߶bx;21xXq`^yQޣ}_5GPLEhc }Z9d4 "|f 9y6WG ˪ f?U YF z 8 mTuKoh@' .b]h馡Qɝ%`1]uxyZEgK* O`l[h$ʷ:JًhIkNŽrΦ!ՍR4Ou5Th#e97QiTB1HN=(̆]%%ۊuq E%BUZ)"3! >pG.&"5զWk4FúFiE6'2P{ n޺ Wpb״հof|R؆L [d|-vc4c _C~0ƳʼnjXĶsM^w=dv(qLx;rr&IԹؖM-]q9D5h=Kh oUvcx+{7a톭J} jfMH0Y!Adڂr"șhkpFF"v SdG$gnGI(Y+JT.,i']zm(Պ+33w>c)ط} 6܏KẅaP.S֚&hdUh jD&3qkg| 9/.Y [5C-doӱ$ +gu;~j\]2V! !L6L]wDU+k{Ѹn#*9RYt5K!>#ФE3✹-/MTƟʱm֬O QRx,3 tEi7ƴfvLÏ4үW[[ zX)q'~],!:TԈFk޲Ȑ7Y, u;-BjV4䒝b؆2.\&آO~uZb?'X5@yjv 12@;!܌l?:3Ww_.0|91Cv#}6bi<^ű(ܡklbb> ߵҸ1]j4k>O~Ccz'[9/T)ZVVN] TtHچO>a[̍jx%i^./I;}&/зy| "rk'1eJbR-QԲ*YĆPr!},)$HFMa=!B@9=U[>ff#1e(גowPuJO} y3֡a-Kq (@]DUjzYNkѨD'_T:]#f s>Q׿+ByT{~wB}4!Ct]hM|thSp'YoҺ\ٍ冭zq,[[phVpi<.:5:];jg6^PHKib˭޲ jС$nuNsm꧟};!iL ݂OEJT>$]xL9A2Y#8=Df#OǪ6qbU*H\&Q'-LMMEb!I;U@lIN<ȨA`,K'~d&)%үP2۷FJԛIv7.R"ۑcIJ`T؍9m]bs|ԫt#G.<0Ah#]2,^WH· v+B"]D8)H쇳Ͽ.WmWQ,1#!u'2?QJSz+j٣lmvҺQ`(cIh#('1T,'{= {Wm 8qDLtuv]Zn( mla|Q=d,O-rr$_Hnq|]J*];m~eWJNdAhcXObӪ{*ͯd'0$Ƃ7GŸt]vJ7^hmjUS|RWGcnrkkh-H.>^ (ʐ%a(2GfTY]_E(e녃XB"Ty3Jkro ߫-ugFo@rIBΆi՛A3'ώJrgWeS;28jɸ'FM'6t:P]c`Y~j=]@0BZHNڑC=JNRT\VɮPL'eL 0&%tu[̀qzo _ ع|M039q ^rXF E|YG%S.zRl 򖫅ί;<è_׬ sJ*RϹE)]JҒ[G0`DT#)|Jʔ\X1m,&w›v> tn$fn,J5tZ{`r\K ߺ3EzĝTqnk8D6BuKCN%N%ݪݩ+: NJ i1xr &dis`֜" ΒG7]W}֟$.%.CݪeAQ -\ r!͊tc墵[tpX>khB5 i{A-ds9+rO- wr5QcNˑ;u͒x Tl:P׾" 3O'J0([.Ttj(zO/Ƃdeb|ه 0&EֳzY2n8~!1m\(Ez|Sl?:لX`l1:4:AD20y5;q.fC+BڴGv{G\O8]:\؎9FhlZV0 EBZc݌Y&k&N[lysUԡZ\_<ޙ2Gۀ;&qvXzVk}ij^o΃*:U!֋u^ éo%"iaQe-+aΪ.C5ULJ׼pVOKzXx12t9E!"$,;]SmA$iֳ*"ZڶܻNʈ-'U2ڑNu$햦:uu^fo%kU!~9V ZANg` OV:ã9oւ%3~,+t$:BN4;$ ^/kgr} 0&2>%?}GyZhSzG6|?c8K$;0I37-q=d|`+\ϴ.}^'ێ^BB?i:GԮQQ# (68]I ޙW/ cʵVr %hVLSlzBL].J\ݒ@ #=[flW|8UΆKV-2R,}dom[]j[ [c]P@*y=:itY'*fPbEDfC)eeә>dUWH3U]ʊ3e-YI2&oiD+p\U; .A}̓Ui +&Ihb4ŐwcxYAfy2󧝼OEhcfC^SmBMߒ^ l7[ C%}CJhJ;ZLYwa UkX(q+m5Of5Ƃ%mn% 6c@kmv~n,s4Nl |}U[&c]Vg*/zS06JE͗[!E$cL 0o@ ӟ![eߵPnl8 m Z`ĀaK9j'-!;2Ȏ0O۱r'ڽ{m, GOfY5o nl)(*)bW*kcŠUQ^̀܁#7%[3a;l5kh)8#.;~mNQAsd%3JczzڽNHCvXb,A7('` ޞŊ_X̓;UȍB*ꠎI(WR/ݡQO+QRA,pvzAI 9ܽ_5OKJbe![)iw' ̒tܯe 0& 7,Ȧu nPfG\k|26.Čܟ? yHp]~Y6>lyr(=آpgpI)TI*%”5/d/E#7kZL'1Wg f4BW# ݋l n@͞k"r;,#=1^3Vt@MO8sHٴtH3lR@s/։FP!:5cE4t_?GPHWq6<5DA4c>#iـEm-$ӯ*/iw\صB]k%([^Q=p#BF,۰sN6?V2v<)fn;m'ysfFY*xxR&pյ`LWvc\AeBBc>ykkVh m3DutX ̔&e5Rw\fN'Hpc1aBW=1"]61yA0[p|6qX; uG67}u'TZ7apM2,sqsoqoIL֜e?z:-kP{_< ?|u[5Z<+Ҷ$ư~?D߄c=:_v|yV!vۖlG6v,@q7 _W }ZQ,o YioKA|ԅ#Qem cTFv=wR]G"G#_úƤ RnDI/*ZUDҖkU4dP^&⽱Qŝ :g|)bGhP1A0_OCrb.B凤`^Q_$gciX*WNDsMѳCcax0ASNwyPC^9Q"$kB٨6h}ikH>y:jl Y^wcb`[U1ڭt$xiXƭS1wGXT. ZAHE['Ǻe_-_U4vDj∠%XY$|3`4*G2]/k;Vzr9p&<! 8&Ӷv{)n/Rg=83XY˅Q9V>e#ZX?'6x2ϖ! Րh5?:c*A:ۄg&싿d?c. sg1=KG0Ybr6:p_P>aL 0@HW% F.st{` ٤QYi.ïZ;~ld`㗣N1/~ztK43N"X:.|R0`jBHK؅_fP?cmX7J1%n*«]B`/v%8DuRYҤRG8 //ܩsU}hSݱ$?dJ,V:g{ IXENֈF\24jC5ڵw/!&$r?)Ӧ~k7Eka효T]Nu GYb( +SN|?_!hְ* ƃ?OTw}Az3-nFkx뫑XӗX$7vz4\f}j&|bzn-[#Vw>ҟ!h9ZJ~z3r1x&%qK=1=Nꚗ0j==DixM|{}1[sTfOʵĐ>tF "浧j}IX8uiCΗU+7`L L)޽Mj/o}T5m&lmv1=z&:&@ӧ??.8?l"a\σՁ пQ%1?MSP(=!.H/;hc(EsU 1]PM{bi|&Kec{.xûD=4~%Awi׋3X4Biݨ;R"V !)N"IhtaXkDacї6zR7M+>3lO}" yҖqw45 %|4gBP\N r< %Uq&CNddG|1]${ ./5pjtvƈ6MBUᵝ~']0QXը< C˕Ҋ@W4;~c'NtGa~K} ;9r:ѽ0"aJH6 f]*E* &`9$`?N)W=U;ݟԟAٻP-5SnN o`%C0tY7v*yRd ae'}mm?uƓe:Q_ZdN:%|֐f.otC"i+f]g_fEjevoyd+Rr f_xZҕ`ẁ.,thuu-2LB$%;8|`AJOjGIp3?C4 ̎߾zeӵ+BC+㑂(y\+pv@ɘꔈy!,mžӍ\KIe]QIe~HUE0-Akdʻh|#"5p!|:-o羾ƞœpP։HW(I|:\탂dv~,B˦<]8sihpl}eD,m9SAܨ0`L 0&`LC,Խo5ZUc oTDܹIq@_vL>-7 ʖ۹ `L 0&`L 2YxA>{}[6{${Afq=~_$ɽzqc5&FqoY*ν߾‚(? `0KM [`L 0&`L"p n̞lۛ;+!^])wfM| fq ]u(O% m?'L 0&`L 0&<߀r]\;]y`|ѵ-nHD-j!Z"+8m-z'0&`L 0&!P*uoPsL 0&`L 0&NwzП0 0&`L 0&`vW&`L 0&`L R,ɥ`L 0&`L 0p`L 0&`L 0&@.%\`8[L 0&`L 0&` &`L 0&`L R,ɥ`L 0&`L 0p`L 0&`L 0&@.%\`8[L 0&`L 0&` &`L 0&`L R,ɥ`L 0&`L 0p`L 0&`L 0&@.%\`8[L 0&`L 0&` &`L 0&`L R,ɥ`L 0&`L 0p`L 0&`L 0&@.%\`8[L 0&`L 0&` &`L 0&`L R>zz%g@!qu3`L#|gNK!m o'1;I͛w2z 0&`;x^+|(#ϣ>,tx5F 0&`L 0&`L+ƍW0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>x)`L 0&`L 0`W0r$L 0&`L 0&>d 5f,|A ÄtQZw5=N 0&`L 0&p?j/ط NM@M5zC(R$UCcP>WKQd[Hy^E-n(9nڈ6nőQ@O @uѤIj$.vE c@UW>{ryͫUf3n6x2\% irK?fkk4^W:h0aɨ0oS|+(VgN:U[OX3V~ST)y}1&[2pv&lX?))[u6Bj?&va瞃8v"nK㎛@ ԈjUQ~pܭt/ҹ!muB1Oi)v#=b(n RH4jrƜ%w&͝}2sϡb!C4g+GdL,qV9k׍xqėhT7UYᇕ{r~*k 1v7J~3fN"%:|o!7Bt%H ^=f<5鸈k,48M1Ih#cHmAI5жHMԫZWlB6`L~"~~;&}-0E>.'Xү4.=z€:#NXDO؊Sh=qz-zO|'gvUP;&j4fX Ql_!5zѺD I'`ߞC8|WT! )*݀ZQUuf\qذiڤ∊io5$QޛpdKlۋ׭hYNzP8 ~L0$31q_kF,6a( ۳6V]O%jWL 0@^<! Gu D9Y2s-> wvvUfuFiIJh`=yH(D=eʸIs#~j9.9cPzwy^d^xK{͜Z2/P6x{΄6`z.̚'JrZM_C^c{yo AL?0tԫ$C[P%UYJZE`,M_Oh$`}z3'J"xqGh㲜ԃ*W]tiبcx1ƮdV!x@H=Z{TyˊȕDKB:nI;0f7u 8wvm^)C\ԃOm?Z"A9ub?\HK^5 K$uc֠& ԑ{a58s:(Ӓe~LvoZ6,Nf}4+ӑ`{&Cpg|+ԔI{e,[~}" )~5Q'،:-FCﷳ I?_#=uB:Ixm^U;c\v.)MkE[S3ϫS7d$qV,GEճI?,=YӾ+$av^_ls(um?>v˙LHk"!xym[S`Ws!3.~ ʻH߀Mhۏ`nl//4{ovJ<9K #š֥%=VWEN&Ol/a E]"\ǝ'@椑yqƠ%#Kړl-?,1S],jHwLjUB#ʅ mh)/BM2J֎4U%KDZWJhYf 8L6K3b1d$Kzc uد$CXO:G'^K<|P,̨sBѵ"fv@ڽ@DqY:D]r6u sG|QA ڑZh]{$ &{V#ղ' 8kU41 䗅cFmgjj_{0L-V1P5,X֒6EVCWqZB~k(*aᐷ6ykUǧѪʠ \zcs9T)k<Цj5kGgsSy../?Ѫ,Ý6FT$Y?2j v 1n|!}|lDs$o9WgmjOȴ^{U%!Vw2xJ@qҡ$ T%cjCa_3.Jh& 8a/kBû 2vYjd_2_B4,^EL 0@:MD6O첡F U0w +ۄDTR[d%c$ mp1Cd;Y4΄SF'a>ԡ]T%wg? Mę\=.<Ҟh2endMiamFgj K(\(ēR-:M[ ZhtH,v΃j0Z>73Iv*w;#X]t .xsvQRk:\΂헖=QW^ko $Wqآe39lzᑞ w7"1KfDZۋ0op,1c Qs\caZEZ+s%H8kҳi\I6.-IdgIP%f2iyE@2 O&p!=Ҁn#7eװr.|^=G BCY֥$-rof=0F"vļ1ǥ[OVw*dL30QjPoH>|ݐLYz#SjS,sMܼ,ϓ=RJX2 j%6vNw+L 0tsY+%Yۆcg8u&, 4[,ByH%+~ؖ Kҁ^fuAƪw&XEv;nƫߠ>[|=[ZT<F/+j="-M=֞dK&@&;dHM._-~HiEO`Eeq(w?خ =}8R.aa ɇUi@K3AKRo;͒7 E XĢ1V8lmsX*h$a`zpX>g(wqHCD2"~o8үأ1qCYG|QZ202 ͈|5wy FM6d?d0E ̠/6{izQm2\ZoFm[@[yݽ%:3@V加-d\\- 0"*(P;T9|K%B"?7D~Jhc+s4R"#E^\HTSSZ'49 $X/tMX{,68ɷ.T%f-'BlHw2&UÉ1ЦMΌ[MBYZbޣ_} mDoxP(C>]$[ry@ZZDvƧSԜ Y$Ąo}j;F^cG%tnQe0EVꁞ=_ū=o M3;OT1;:gytƑz!L{|C[8jD8&W"`qpVQղ+:'BY2f= Ņ Nh鲲)`)~ߢsXrN3oFq޼eGY4eƤgh1U?Exxkw~a83? [$!C$>ZGI{IY¶i@VbT˫B"; lWh&9?!Z:EG=:TuHA$֮x*vG-Q\ڎ&6HArɛ>TTxhʹ$d9{:_zyٹZa1M{`(ZO vl3m@ǿL 0F=_$.Gߦr5>Wq{.eUFBަ.v?!CsmwW(^8LXxa񙁽rl9x=ɖ-rCEFhFj C vӹyU-qe97صs6mބu!Hvx'ߠfi755w*d1 nߔC;Wnw墮g EŠ9H/⍡YջόKWzP `D0JvƼ:NYgi_D,??ūF`pQd0늀+Ib5E=#l("iIiz8N-Ӧkt+ 7iҥ)u-^FG[EՌdv{k AnmQi*AV\:&\J>g:l\j6-bE<.jdw^ިJbL <8NKv GOi?٪Л&~'_Z.#*iG$uO=(WTiϮoLԺ$K*w%TT6 oN)IG.+˙ߋ[@y"4~ bK{<''E7/R/lţ5];/"aWY"q'vhCr__"_}ZwZh=zM@|,iqND+C?}綒&vl[ZB]#%t7HN:{c Š͋9 >E wYe(zb݌SCI*8PKT[;)d%?hزhDu2˯IoNuH;9ަyhN#%a߂ؗI^:$hzy 0&p_H2.M4!|H b?Q>si0i;ǝ~=֐WR}>ވHm3ެ]lfY~h¥p0cSq]8JWܨ{0r_ۗik5 Щv;MZ1$n[+ +LBf2T?b;J_'SX/n7k 0g2Fo\w I@귶'E1781uYNB~Ѷh GvÜispʾ1X5$jNfgñ!!%钕KZ|wfN[r;S=Km}K4d;-ڠKRMGfF+PLJ69h].߉z{0&33F Z,(ߢ7.3=zڇٶ:OͿn+߯)viĈ7{E?YV㮜?2ɦ=as&0vdm"8nBw$[7n,4Ù</^6_Aj`G]6YIɝ0Ʉ;AQwv7+ÖJhY-d:6ЬĎH?Lɔ:Dv%QK2st K-5Vh=YNyR}QۧrRڽJ Ҧ >ݬM3lT xHʊq4#ṃc.CzKhHzGT"#Cfo4yivh'UemPβLlv!}-rgrXsg8WL 00)D)w$GE"wK.dA@?G1 B?DrW!ugKpDAgc;#{DN MC-\h8Y>+r ٗJ̠W:d͛tGpfLf-ǕO%¾M(U/\d]w?Sz %&"95/"G5ݝT JJe-td ˅m*0FҦyą=HD_D JK 2gg \nm_J|ž]/kW_mXiEHDF[אm1O>O@ wٱ *~3y^O1}{{iRNWﳔfL v9maT^e#!A*̃dG@oIBpo zL} >]#Ee(Ew$p*/aH]tz,/v$8{,N:'̩W0Ib _^߶LHnE~,v~'JH㯿>_zn!Ĩc*),2 )?_9MH5ib%HR"++>_= $>InC?Mi|CQKU޴ Ɋ;֨S{-J~ev<1y|>a vTlSE)$<~$v֮]-vu3fgq0-rhٞTA6mP]ݿQ.IVٶw#|7.?~/WێS:C{/cL K~F3w|~3JQ)X☓Y${$rOƨ9{SԩQFqЯ4Y%^~.e`p^IET WV~L[*_Dxy)ʝ{W{9A|4-=Gbj<ƵQL}-I س=1~ {M0蓼(JF;igfwV#:X<oǠϳQQ#)9Ln6o׫@n0NCX4/HuÙY`MFtQSPۍ1޽yo͟$Ĺq%lyfϺ!>'5L? 5.!AѢrڀa-Mm06Fb;?Þx6,,R}˴}>V.fʊ/0Z}mHl-T{8"Ql1~qbW.5ri}fT-Y q)bTC(v;PKoDamW,7gT:tt[=W*2;(s)@B &-4")RDŮE" !$!rE?{wWs\Bnnv晙<3  GsnUVCt~,.o^~k?ld\BLV(B\ц>/LI[>{<a!bl&o|mI1TEkMU5Lk&m|"v?ɃFp.]/|6羊 R옳zs?d.y©F?Úa(IǭƎUoS;[8{j9QaGg28;5|+T_(S]1/ ta"t42 ʕS|HPHq#XJrlo$ oJ܃_?ɖMs<پٶ_Yc̲L1ahV_;FŖ<[c?4p>=Q0J:5{#Nk*ͮG$6O/FL5; ͞$!Par(sn;8IQFw"33ƼaQ9Xi+rIݟNE?vL_+cV^VҩS䥝j9^ם,wl%ryIl/Ɵ5Iu"͎Ijϻn:< Ir]}p$.wy1s]Qf  "IqOOciٱ[ W>IJձG-!6,IzN6址c[ƃr;L{0|Ŕɘ(f P}=z"J`!!P_'^/R`xvޝ"<')gZh^Bb7ɪ),JG`~ʒ)(Ab!Ho]ia:k>{^_Ē2,+'uR;jbkbPڸԺm2˰ ˩ïTNdccG_bdJW?i{lC/=w=bvُA:*kS>A&6}*:9F}]D,_ٿMNr0h4|>u܉uCxuzriQ@V%X5{ڪ֍3JD}?Zp,_k.I˫@CZ߱&.& %=l<ՙbZDqm_We/ޜ*UgNٽůFUX8=@k-G*MCOQOlEuat!iv/>\'M*^-a  0~w-5^z621 Ϟ&_^|3Te P K_ÝB}Z=,I#b‡ƣgSK⪷KèDΞUi _'8|Cz#H&5l)Hڤt:uj)5l? sӿaMQ'|~Ybx4eP18gYj gI n!!H^6,?&&݅KE,u=kbdGYYЖ @Hc+S,~䇲bLKc?ȼ,ƲĈ60 yg S\N:dGAqd ?9똯Bw0 '#!p= b.wR/f -^# 'oauSs9_*_f]OEf4u-KS] y$~ ڀ._ǽBI$@$p Hؙg q?Y(%2Nbxp8)CzƂ ~G ŸfIQ8FXٖ`cBSt?"0\)n oճHeITn#_&Ux5#      ׂr ?.^+?qMxHHHHHHHU0ΚF*vIHHHHHH"P0,f5p5oۏhW;L$@$@$@$@$@$@$cwƴ?I8i/-X $pW*nt媣eƸ(-oDw;e #rW)G !       GΉ _F$@$@$@$@$@$@$@$`"@  RTҎaHHHHHHHH       ()j 7$@$@$@$@$@$@$@$PH PqSH;"       *n8HHHHHHHH⦐v E$@$@$@$@$@$@$@Tp @!%@M!VHHHHHHH        BJB1 Pq1@$@$@$@$@$@$@$@7cX-       cHHHHHHH )*n iǰZ$@$@$@$@$@$@$@$@ R~ꕘIq[躆"  ;;.eYp<ݝ_?Rq(@fff~l   PCAqG؟wD7>CyW Ǐ1Ùk׮9f Kvqƻhs @&       7qc.       wT;b@$@$@$@$@$@$@$@;nE$@$@$@$@$@$@$@N|GHHHHHHHH;TxǍHHHHHHHH PqY xG1 ;*n1        Pq7"       |'@M#f$@$@$@$@$@$@$@$*n\$@$@$@$@$@$@$@$w,HHHHHHH#@ōwܘHHHHHHH7 ws @&       7qc.       wT;b@$@$@$@$@$@$@$@;nE$@$@$@$@$@$@$@N|GHHHHHHHH;TxǍHHHHHHHH PqY xG1 ;|/-o;F!6m)W9iN/a_(UC\$@$@$@$@$@$Pv~OljqQKJA %([6kEV\'H!ڗ̛Vصm+vlݍ/)_9TZߋ c͸`m`oCzOƊQ o6Ί 5 Ӈ4uU7d1̓0O^ع3 ܥ)Gi9  -궉E2 lȣM0l/fW/6җG\F\Źx6"y   7ekRs-FTsop@.d>@ g才x湊RQBqS+O:3__ȔeǙ#7Uw,y'pa_G>1}p |6'(2lɢwc7Zt6T9*6}&~ؑf9U JwMVbw6DGG#"%쑄"2P)|‹Jh4iFX|Oo;`LU5(M Nd aRB~:3F8ԕ   pF@W:YtqcFѮ}6*ԀU^Ša*'6R£ȓōܟo6Moq hAvZ)t9yM"OK?2q{t`xF|>w~~g"'A⮉pBx[6 _2ZBtkĖM/ Fz1s7),@ԩ R%r k^[O1t9򹳱''2? n/ Be\]˯a |yH[oM.lsM!=](Y?-ٸe5 <ZM0J;=7xZuE oaX& )Tj4h EØXdpnTSb[VeE@pD4Z6 ޽m0>A=`IHH|I@ pzeYKXyZ YsJTB{/]OR?> cQ/Z6&4m*YJ_9Jo _)6H4qTV[Nn6eƳhm1IU@gWߘvE\&0<@]T3ߪLkc쬧S 8ҔA TJoC24xp$mžY ?*>'EX d]$"4>~q5A.C*yh1"4v7jIq2_ѕ:#a2oI9uu˭ fΓF*TK~^)Bծ~1)SsG 4( {V*CۊKwtBe-,!`g+bri[s;yk(3a >xc 83 be7&eR M#i(IF?܅'uQcUY bW-ABq#ɸtoN813>"y:|*zu|1ߨ[y[IUuI5|6#wAm7VD7u:޴]XjNqyFj$$k2Ac8qH   @ yF}.2X/eAq1"ehog7 ѥϽQOSZ߸A5hlI QuYV21EFcJartlMj Q\ᄨYspǥDJYXRTn 82*#mfU}m=iت AR=e`;bo q'GG@mFǾx6ձns]'%E׃duK)ʙb>H(G$M!M$~vvjGI{6JUСglm߆lPuWD#SYamr:-L4U4&}FiT*#}Ǐ_   UO^S96xdLfKF*Aződ)O.\rRw=E])C8mءcCma!w3dbEu:ۻP+m$^]2k>w "֗jR@\"⺢yZI}RW1MfpzgӠpYv5QDv`<S_jDR1ELAiM)99 c2eePDu!32('KVt pìݹr?JetصOYJXS6ӹ!D%vc$%U:! Ns6aC֥= ®% n%{SwmF10%<7ä Ft,cZu$zh4^5g{!h;m$#IHHL f,cT#0x? Z<2 Y+Lin)SJЈy1}+mp3O!Zic> mbw{٩(m|:bNJ('QH4eѧ22]ֵ|ٹ}9pX+*os'9ѦiL^WbO*ǥk-־ֹI<[Fic(Q)$%=*kv,W&ǠhƛͮR'YuQHu {LZKՍ $@$@$pcӲJk lErm@rnj)H;p]VJɢL@Vƈpr3K[";n m6{g1z$LGW߱*mcWТy> DW7 l+]e65 9/x{ahwXS]n`_bzƑzڔXgQqUv6׿ZaV;TP*]Q4'Kzr^ %>m&Uk'Ž :ꭍd~[Jqx.tlФKg,e':H~  ;#졶\_rnB(F)*Ur˸MC*aYדjWyGWlH4%mGHv]Ϩ hjOd1 AJZU/PftbkUHy:lUYC ߺKV9.K)~0l A@H1VXvi,UvKyPi&`5'Yd'9-;nk:E=SqH.vI W/bo_GkV:{u_a'|ͭ\V.jN aV0ֺG(n~rVY-ROۙAiQ5:!(Um5C9UyR3WcE8F+;$un-+l:XNSݬM5K~xqZe`d4:K Al?oGKe3$@$@$p8/򁮨셧Mɹw T+K˔JkHXSq>-A܀T~7_85b.9.8mv .7Yv𻚎7:b':/gIA!Cpϭ m {%5H'3.[lo^)ntJ#n GHk[$Qo:~TjgtEtuP"fvqR)Ӎ߃Kd1LJM mz5Ⱪjaޙ{&~kki poMT(,mv;|[Ji;e<^Y)Xios]wMpw̞qf"  (ROҤI:B@HS(%f1nŒ ߝwL4)7o,,"VHs8*vv w ǐ,lPϪF}:Nɮ=Dy;潂9w{kJS^('4^(nKw%^躅 9Hw(FqLrP2YY,]Hs"N:Ρ"v ChXq[ݩ$mז?B *SkOoKcl}nz$b*8VdfNf[T|n[} QQr> F,7J[;V1$@$@$@w1+/]Kk0|kFIT4ii >DE*CI@ ϳLW7ŚLïuw]w~}C6+9AH%%cg'7u ٘v^+g]" ȋMװe?{[@P6.TUвQ5v6/2q8&(aS | YiQs]m`ڭI삵q,{R 9w~*qVLЦ/i+ Nyd!Z je>Lj0UslȆ|`E 8xݤ!l  )쭫'.*.4N(jOjPX5w*(vꑖ8tŜOz]$w+)6 NX5֤xh".帓/K#?viAlcZLN )K@]K`b" ^%fŧ}wwmFho$ M3ԖG=0p1q\eXlIK`5Z HX582^=o#'A4I[:ޤ-{SؒZ~zA$@$@$p}DESVntL~_6Zu"n ԨF}T2?i*5F)mhT-f"3Gxj)bӑa/Hy֗` y:TzZՕB5nR\ͪ[ /6`wg@:'8&L WN(>FKoHÄEhDP?dߺõL\HN=syM76ᕷ#kqWؐ;S Ø֧„2OmIu&&9&=v|e^xgnTms7 et0d_ŅSbfn/~h5u=нn‘`Sd疜tb?! !!e{T[ ]&|>} : ym:l¼)EGy&~ :ףL#"L:􀆒"wG1 fæ6}з9-nr4nߌ Wql͗)0HHHv߿Nݻw pRFuzz9)N >ǃ<8?nO=vetm ֈ\þˎBR :"% Zla{>_dm4B([ߓIرa)+5U|d!>)UϾ;kx _&ܷfendFxΫ*SՆ8*} P9;bVC;K<:tf;o,P3g6'ei}dxȠwT K'CUV]LŬcܮ[+6,)D<0|*^xA4p_߱ǺR )/1xbl l,$Z7=VL<@I$)1y6+lZsZq$6)Mi\qtcLj^/|\N,ӧ`wZI;Zy/^6o3ύWWoL)`f_'·$@$@$@ų')v B.er .ouZ1?<6_Mz:eS31siEsb(]OKƧa7S/fcg^M A$@$@$@$@$@$@"kō循2䝀p]͛_g]4% ZTڮXk*z{7*" E6 Xkק=_C _vBv{m|X}X @;7O7W^ #j{HHHHHHHRq & {-K~5X[       JUvE$@$@$@$@$@$@$P pW= xLJy IHHHHHHH` PqSY xLQ1! ,*n 7K#        Pq1*&$       %@Mfi$@$@$@$@$@$@$@$1*n!@ōO0R 7gJ$@$@$@$@$@$@$@$T# Pq{H$@$@$@$@$@$@$@>!@ōO0R 7gJ$@$@$@$@$@$@$@$T# Pq{H$@$@$@$@$@$@$@>!@ōO0R 7gJ$@$@$@$@$@$@$@$T# Pq{H$@$@$@$@$@$@$@>!@ōO0R 7gJ$@$@$@$@$@$@$@$T# Pq{H$@$@$@$@$@$@$@>!@ōO0R 7gJ$@$@$@$@$@$@$@$T# Pq{H$@$@$@$@$@$@$@>!@ōO0R 7gJ$@$@$@$@$@$@$@$T# Pq%S?1iᓰu/0A [,ђmD-ɛ~l!    ( |ZɬT=r™sIHa4ה,HԬ蚵Q=|[O[0 $ݸ"^AMxȐmֿh5*UJCPAsƶAr9cx Ǡs9{Ngً{ϣxqgm6--W:Q1Y"c+1bNgbpJ6=,еk?8KW )ǰfISŪls#'`H[Ieg?MaB$@$pp3'pṸ#x577⊦/@nm۶A`L67zVyٷn|h)Yr{q`2=b~#CƮp%ܒ7ahئ O@=ThS]>s cǕ{P6DǶqQ6ouqQ I҈ V;ut{E`dҎay9@qvb)› eE|VirHJ肗F>Z.&]Yf"R&ōN ٿ8]q#[`hzrrB*nnd⸤qd2X6*4_m5Wu+7jVb2  :$qIrc1w9x~-R 9+2Ys Uz-{Aaca5$5[}3zr5-|-ŲQPW [w Aʮ0_Mu0X5 qһkJ>:vV#3_OTtθ.?Ekxoq#.ޏ~kks>ގJ)Tj7W>$5:slSEqo`ÎJ)IhNxSi&hUJqwy@{/1/?s=(?`P~s4oR:aI3мUsԪTT,{:K㴸EmZ@o)ox % }.^3O'>bqo^fO*m,"#Zz-_7/c6ADKV<uZO:xՒƿV/9s>)mL_=̇12}Qo@<0u6C~$Vicڤ*_/zCYYjuFǣs?-٠(ǙQ9}3x|4-MC.mLJSl5kp DupNU1GGapW M; L;k $*lۄ?7oGYFWm69 yѿ7p嚩TXӰEGĵv K(.:԰xc;:p;>b"Ęvl(2ӂol䇨qOsQ_[4Ցu uGALzG^ ى_*T#]dQ۵._<m:6v )HΙ7>/#],9LvkxIQfDUԉzis)G3mӟq* N, uP<ٿ`p.W:uue%U+ (Vn2=3N77=-KG4Bl8&;kN5f˛M}hT[!}&>#GNrCB`Jh)H\(GKH}qlێW 4=,Mh]ӱŋ6PlݧqT_QcHy87VJ$aX ]bc}yBY\9{zC]A (2eM؈`6yjx z+D^ p2EexZДC^ ;KtxVnT u_p+7Į W|y?;r1QZϚŊt tn15؏73 u:"U.q5_ow\ju١ySYI`/"Tu:7_;^;e*X-E#TM&U&߶#俱HK2NeB[!JYa'`baM'tJ'7i pPi :K SѩCMH1I|f' ~݆N#:)Qy%i▼`fH5g0{UEu,#[(n );1kNx0"!)j$!3J)NӿPkYO&[8>KZ_ad(nZQ!x3/ T7bXRB2n4t ^ NjIo"^>*fzes}u=F(XoDYׯx Fp2&9zW$Yhzǎ᳥lDH_[~GxcLO.){M*!] f{9[V㍗@AƑ`!7N͢c/0c%wvZJvt)?bߔvYs)YQ7S¼pvwq)?[Ƽ&c)NklC$@$@EM+/M F>O_\..w28u<ۘU -Xbh/sw0Ϫg1wxۿC)l,ĸ qbW夿JT6K{Chy<ʙB2~Z*e<̺G7)bӆP)mU7Ll#.:AAk4Nzi$ڛo/w\Juߩ6M[Gɮ}śxy##k Rh|&(p&9[I[ϟY/!tD+=8r3&\rl,y$Rd$H.,V(GKK0⩗J1Y[rTgHHcZ.~7<-VCz.L`=tX>}W+]e0XehnBޓ,}5՜L7̽Be]UuIL َܚJg4s%@a?fMNkfbN`+#1p>e3!6)[_L]@`^hZ2 [0q⧦1q N_KBE` 8R8T歶Pg7ubϠⳣ?<ӛ,xd-ڑa\ P!A 3IS儓R^hho%$dj%33۱bAP\ȼ1Ehϙbhz]<3i[QY)qOBm(i0h=ZS,:whPDղ؉[uk:CE3+-۷DtSϕɛ1903Šq3]X2s ~791`ޛ?mC+i|ҮMS-]^@;ԗbsʽ^h|F`p,$N8HLܸ zfa_뜿-=Jw }I8tS)`܈)s??sj;8&}>gK1*THH 5l,YrҲ8v~ZSe[I!6:wS-?5k|sx-x|7:KLVRc⇒G%ǪV*u?w[g%2xrpEpWrtj|zv̉)YƨƠj*jjbs1&YOzvJ`lIq˨|)bMHIVRfRrv}ΔZL3Qi;:(|C^nq8dCh׺c{tR?Ugn7F(mdh]o˱>NZ6_bWf&r8C<:ƃ;h??kvlM;h%kϮ: n4TG)J˷L<=8Td"`x>fK+[Fw,ej7%ٲhԊXn )C8^,C;1+^"'Om\Pl6S6V {?a'&[ &Tڍl[f]ZHV=q^\n~{`/XLԳSik4Vq>˖'F:'U-ӡ)[9˨\0S -JJor>tOJ̝ VEؿ/tǡV:I#d2=hYy(U斏#yuw$Iqd"z>j]¶yYwqvR46Yk!jU$ڵqYkǐ W8*9!vrvGHoVqFq$@$PD  Si ѢeGɘ>e<YӶ|9ucQsuרjWUBRbnrJB: Bas^i#T_U9AQJb H/%u?JJ9}ݪ̹VW zKM @3/dgp U"' b?\]Ēez0GV掙n&5㴥} `/1ypTSm+ +c|}вE<$0jQ羞_ [Yp\*/;{$TAO]١+4:iv| .,/㻷&`Ax̚:cK9MHS[->rN/?mI-\)hjE8Ռ&"FXX&k:;?(vJ)^a9Vy`zS&HPlZkll"s%dv˗;) {_/u˖P mHHЪG m6r}'.7oHbx P|N@\Ե+bob)S8cL\1'}&bVE&E\v!jlb[?N H tjKIhd|Vi UTԌu ōفMB/5_V`,,c.vr(ؽzyB|5*xi=s-Wym>tu7 .QYE9X<ٚH%U#>GWa|?)1`M8dՔqpQOaE8(Hoš/+cش hUJrV⑉~ :%mU:[Nqt A`c]ip8WPewӟdErPƜm{-Q["Zt{CV6+7m1. =yQ|SO =N§s#Nv-~F狷.?gyxip'%YڪE8*c.l5H UF˸lOƖȨMsʃE!>FB+P(}0ŧU[셧 % Wbdžyz=\>s"UQ~7Q\l" v2jIuKg|'uߍ|g^( (!/=E((CՓ~,_U?+biKq&Ymdָb01(oQ:t#¯Mٚ>Xɀ/o,Noh۠|NYx  y(9K~͂e_>zвe>QpKKy"0Te!_WU4ZaM3('*Üi` g@үi)\nX!\+{v!X w+ui8ݴ}tX}Fr#ri%G~^Kfhӷ|S#c'I(yN^"mJ+}Q׉a  F@T X풋 473<3w!].%,nXJ4{ (rӈÇUn?Hn/RżƇϳ*}S Rq#nn_k~+3ycXmTWlmL:iqOgbg4GDD>1ӳ+DIΘ^x#Ks5} bJ{z}JWʽZ3ܬXd5_IA6zpϛ\&\T,5 rj묔uz""*ZIpşa:Bg;(Os {o/ AE]?lX Wł t!JHBIH9E={w[] jٙgf~3;;L;qW9#v4U7gW*d9/+CYuy4UM:R~sč=LQkl Q_*}_Vc:ɺlui3K6S(R?H ]?WMy<)k~җR65O'.Y2,™#dFzg-'}xu O=Y.^_[o؎T?}J:.R3 "liOJ>~ I]PϰL#+VL{īS#j>W#F 95ŧq@TVWhw0k03rZ!G&cx[ˋߢruOoDM2aO20w//7jW2#[p6FoK̦ꛛz]k:Bnd"e6̙K|]#T`M)|tfrk1  ܞtZ2n^mI$\CSs~{b1_P. 튖)Bf!yF02,o;m)0MNXFcs;qc:Sdlݺszz+Z~|g =״CyR"{ח1aqD>ڣ^*d $lc.l{pR67ڷ#$-68 TSW@~-G/땼?b;qcиeci@QW{4j+8f. }pgu[{7bgh{xc՜$wFGqmۚPdF@dxٚ볠'G;cE)bxjhSx #`\tG݃ {bb[m=˃W',.t{W zuqSҖ|{{qHٺ-'o_M$UY6/罇/?>N S Z#iهoCÛwB\hPKrqV/ ֧i܌R-0Q)wBDUCXLڱNCVv\a̷iG:eж5Y.}k<#vY)F[!;7U^91gLϱwճzQHP+&B|6u}=<7qј\`^(5]7폡T'ëfb_f*lߊ8>,L?_e?v*J9uϟɟ/_fuO/ X/^c1{0Edžngyq y9|tˌ+4etD߫jCmj6Ey=uMظCr u罤)gVgo7F\;Xz C\U1oGE %Kݝ/ά'0mzdHu~Ną:XPkvKw _K;ɯgƛlWϯշ풾}-fɟ=W(Y!HaϿdRHXmq~'JL_Cx ȼe~SXG`#` ѫ:uїG3SWvV^X u8^:mɔ@1TŰQoGn:G&؏v A-ok_>mn%WQJ_ 8+ƵԦ {T ;5Xk|cGa.Dž[01jXa{1D*OyF{oELU-4ǰG_ï˝l>xW]-{ ~n#&tǧ[Ks0z| : *~w yc3@u7W<r 0w7:.ǜ#q]ޠF)>zZF˛ i{5s)RozKC뿾ʵꪸSxHHb$`A#S={{F&y]^ڴƶ[>zi lSIr-MZK' T.O%>#rS>~⪷H7Ę߆J[~)kS_&v#dkHo?%uSqU _eπ;ƇS1[=pS\EP'8sy3ӳ6 "jfj*";*GW AdE+a쏯yC\\ yB8\sK^IgĩGF"y{xg^-EPk.h(MOgzJJ,=̆b("C/j2,dۂɕ)aR&,>v\d, 9SCT|հ#='`ٶ0 /}&8*{HdfT(;B!gʱ )#R>%zeNC?%ς$/kX)oC/  K}mdvE|RAtiHi^}EQL| ヒ"P,jIUڢJR oʲPT%OL eˬ}eES3 /ʯ(SoQ/*B/xM qER:EjSMW=(oh% /=dc$SM_u9ϻdt %K&k#0hcFqTH$@$@$@$@$@$@;e\R $ccSVAٿ†=k"aK"L Bl*nl9_!0E4CO6" @TXCmrdWhv@$@$@$@$@$@$@$@%JL*UD)HHHHHHHD$Ő @ PqhG$@$@$@$@$@$@$@"@M@R 7&Jy$@$@$@$@$@$@$@$ T$Ő @ PqhG$@$@$@$@$@$@$@"@M@R 7&Jy$@$@$@$@$@$@$@$ T$Ő @ PqhG$@$@$@$@$@$@$@"@M@R 7&Jy$@$@$@$@$@$@$@$ T$Ő @ PqhG$@$@$@$@$@$@$@"@M@R 7&Jy$@$@$@$@$@$@$@$ T$Ő @ PqhG$@$@$@$@$@$@$@"KÇ}== :PXnK]0B$@$@$p`R祕M Cq mj 4@Dy$P233KR dd~ tB;5KV#=zf!\6,YTo.1}!9 @H; [7z?,m ݃Z`U+l(6+7ErMG@Xl6D6뉶1Lc5Xr#f'r]7kMO% mn]U4GWItein;G"2 Evmйs' 7{dKKҵ5?6ٵ=9?Pw_}]+dڵ ij GL}~yц{v&䔃?ZVl%XÐ#8b;乻wF\CIlKۉ9s0mTژC.P؊>RgFLl 2lbʯ2m wލ~0ᙾKux!66 & {@+A /%"yGDgc- i2KI0gܳXoF} $c hxkolmԇٖcJ~ZzMAA;y_}9{O]­b.Eǽ C>]Xp‡/|}5Ka_%tc;^2ǠIDV4aߖ8!^ O/rҦFc5aW_M.IëcThfs+-]כb)nNlMi?]5Y,ؿψGJHڮ5tV_u^s7Cy(mZ 7lpt:$IJ_Bz!q{~"]bR3INݳBcDǴ2U*G5i*>Ӧǝc0A1sH{&j=dO.2bY6$Eh8,+& WJaB˳'[y1瞺&K4{澴Rr40> o;#}y4W GV]'J뗒&|ʷ"qc⫩n-qxT7B8^.e02tN$P*bӝu.sB*n,~1,v*m .-RP c&1oD'~p3No1)]icİFbʝ?pdHC~7E]D!ċ`jAZW>Eznz,YocUOQUͭJZb5aWd_%y`C=eWjՐ8ʌ,O.ԏ!ʅ޹tͦ {T+W+S/ۆgY/fh{uFmle Aޝ7o܆' S04:zΨ;}kvkO^Z[V> SܸvYغll~z;c Xh1vC(2e#XSREuH8WҫPJ76/Ym pm޽{ؤ,Ph_|4FƵA_Ψ&đd`=Slo-)Jhvͧ3UAѵl%"ưeݖ]bڍHvnnTAr{|_)KvqEǦG}3?cm1Gerj@_ԯ̽k@&*VmjyIwXeɉ/HܲKZAm+m7/OFli˺o#xM[60;NڇE+lvl~zlǰbW=W]{|ݺ*ަm^rwY*uN6\0ԫ͚D$\S?VQ69z( z*>\iI$pQ܍5*tRƉ^nCԳJe!o6e:#!vh}8k/nMahLZ&!ƖyEGj:7m;NōMU2*TԪbv\N \qzu},6ZXl/=,&xYцtxS5y ~L2$^w~9݋6Gwa؋йcmk:,! fPYaLdžhwE╯܊iJgViu(״?<8wV;+VL%yrpC]=.oQS^neObwLGMqgM# %@z[84c>1cX5t@ Ӣ;?`57 h3HX(:lړ]?.`. j*i륾I>fަz?ާ >aTmzziTfl;EicFnqЧv9w2N[mW6aAiS9+Z18a 3f!ؤȟ[`Ӥ9 Ss'qQTA *$ÝTNcrQl}vM*k;^3GڃbXX\\8 0ea Nϥ&/e/6rV2#VRO61RŜ뢴Y%)TuMY(nV~4(mjnvڡe^Sngߗ |cT:UiS6z l#˾Se-۲Z@߂ Y(Ӓ~J6soFWH5LUϳXvuؤ0p͝ Տs^F̑S*^}N46S]jO`u}'2wN O>EI}p| J5Nsq] xOqg7Yf;/G_[ϯ%߆ƼQ3[aXU9V.h(YqӧH!`Ŏδ{S(UJ(,ug"MMC[BARl(^96s+%I_pKZxc@O'˾W=\puf^<Y.~X=}NMx2k{g݇)^ LpLn2Z) :gb,: os=SjIg/yv7k+GpɆ=h!4FKaYfJ¨ieUq'. ^_d 2R:rx_=,_[6ALw|7I᱗ $#~7g6Y{Y։3:E>" 2zϖ1[$tbt%ZOtʭ$_ /jf=ѧQKA)W%/}tO- 2B.ޙ,g9:yua5Ay_F }2 )zLӵʺe ڏp0q/5(*/t!뗍?L6nx!9Q(X7:e:2WK1|/y[!u:2+bP6Z 'L-JB};tgaXSlN m6w݈V*i:_݇{ٿ,r 8|[bj;>A6)REɩRYk+G?qW9acd\:؊HKT<ҽZ%N:,,/a*Sssl;qw N9e/Oi?Pn՚GDmFuDHUb>QJk~=6S: Iq/ȋm&Khx0; qr8tF6%}9(:Joc}Bp_Yrφ%cG^+mQ?w>Kfs_w4 7p7x?L;2S+nawލr6؋_'fHΟE1%kZ1UWЇ=&SU#f)## JŅb neZ%%͝2mעAoxCjZL}ԏv WT CMS ,n\HR ,(-{dQ6J:sI`W G&RkK_Kץ,^t=fɾ==Gx JV2 /Shs b4u>є6 -p'TŎ߹b9څ`Aa9~r"y 0(u .ͿoU-_].,_hadz' mMSW(!5tu[iT_73qݮ፮n[)eFGb6EmUJ vyq'KG#Bhl9ө ( z.UcE;#nb賏K6߻C f]|]jqY=ge4)h{NEhS.drtjmTٍ78* e{Y_Z/رաY&ܱաU03" uм[4}~,'8zq/NjnYBsuq^G~Mt3Jg8Q|~O|0*X+ma|HB@Ycoqٗ[ڐlhz $Ǝkja &/D(k~E+|Q4'4\R2=_g/1҆!#]~ݧJDqE~gU{k)/))FM-ۡƌW61Q> ~9cQ%|#Uk/[rth}ؐyR]VѾ:Ҋ';M1+{Ѡ׬Wo\"Dc<[4!Pm&q=-vH&kf4۵kK+J_:5Xn3})2IV˥D/sH;ziYZȖ^줣^QU7i8w1s,ŏQņlEY>*{P.1w1j_YKqL%vn]Us흑&GO2Xxc ôz}@Չ.3x,RfI* 3nz/N޶i%B"`bTNOR]%߸4yg.t,7>0x~ aI$p> 3d0G4׊N-gƑG| ,yGtYѳUX<%B]HٌVkM@X"o7,J/Æ̖~k fNy?ָύ\f3xmltT[vmpY5oF˄ )~(ykA,O;O/1clk"Cg|84ZWoX;+\DCd h6FF' ݉tq|5z%mJ*E؏bփcgaV>E>X)=մVn}WtY);;_V=zk»W!~_K7 #>Y 3T^:]+ĥzէT7moL{bzM t. YYgbYfUـ^D8 :b׉Wމ7Ob@;B'뱍A= KBڨrkȕCqWj @a\ xUi*z`P#If7i 2ⴧ/e.1mt\\Xϧ̴Y\\9/N=16|;Ǥ/t'eOٷٗLUs}EiD`aCfIhaw$6Ђ* #;eFbj,`x .h׍Uqq^"7T} z[ (2␝-8 cx2Xt tl0"Wb]y¢ EJ /7j#u,ΗkM6v^dYxqVwړ>PY<^ʮ`9^J]? BlkN-?IC] jkA ۿٟzS- {p \^1o"ʒ#}Fא:a \`ᔛ t n 13Ʊg|Xɲ ,Wx0~Pâ,D:iSfn OwO1Fs Ou򗎟&LՔ2$U5]7#Aw~*M?3߆dɔD4!^hǚ9uم_9Av- jWf"Tѝ)ػ2ubȧsоQ~f0gP\N>fEL?#@$PLt-aߴ6VC +jXWX۬&xX"uH[j-Dpʭ:\|0Uk+Mw.p] +,〉} xz[bh璭Fjm$][f9 gXj_GߖE"(t 8%[ gEΨaOޢ{A静ʸ~=hV-vbg1eb(n]S#-o~cK?KS {Sqgg|#ہMM!UdwyYiNڲ~/zKɧnst!o\w! *S1#*qcܐfrQ,A'1nR)oE8ubѿ1MΔѤIUuD3sK>H%߹Zpy2FKElzY'%QIZA>}  Rͥ,lY*V+K;i̳L~0\ݫAeRC Z vDo,[1l9`3EQu_DޏϰxD-eXr }uW֚ۤE9Q˂:=۲>=v)S"WSB!hZe?WOFǑGUY P;pYA_nBZ'bDMamc-+֯N,PdW>\/QUeJʂdauO>ƻ=\t YTkF79+l勲]) qlZA2Ǿyoˆ|n?ðD`txkMƧ0|h RO#o?}[Z-h֮Άop{V] k*+jOMi8O^rqo?+^t94 \ZnRˑ3+0V.vRG˥_VmiQ&E]v,;hݘKJ].ێavP+| M(BկOC9 zAhaI{æW}/ES:^?r/Sj咚6m7᠏ hJrN z-?~ˋ.>ht`F8ml8uő5+˳Q)mfWgݨsʋxRV,rWq/KX8T蓻̽aa<{t_;C9xe ^Gg}MԷGF^n|5S9yh.d\~| 7m9| VDSԟ*ӛfDW>Ч=}5?ԉ;^mf1wuW*ȇXIX|URUB/W%S9aN7\r5 IS;+:Y]oW̒e~w껝-{]"űx@$phY})xDL]ѻZF-۱dx2o'sG0p᠁-/1LB_y0U>.&{ssƽE\(4lKHǟ>n+SݎcXZ;e@qeLG84M[T./0ya0Y_G8%k]];ҫ_ikJ̚@A,6 6Cv0voߍG0pwmn6-݂:&mzj- &GQNe!]C1͗eZYB0hHV9$g@Ҫ!C$rĚSHMNv9·|+G!Oop9-,KY(Hx2mីPwԒ<"g>ui]vU(72G |>~ z0!X~r|6sl-p1 WÅ_1~XCf䊂z2vl[g`Ibri+3 }>3ˊS'f#hu{xKH̬FO[ i mzFcwuU7vMT\/35 ;6r9|ܣrB#B2H&8܍^܏εqFlڅX舗LA9ñaeP]mKeZ A1LGCeЭh"35  16I.n6K?[& I%ch?nj-3"رG2QKd;U,WΔ/ki{;5UNqԏT+%AyBoOo`Ok89 wk%˗Oa_ ߺ$p",Ywk]6hTVD¢rpXN Y %Lθ )_^Xqؾn{Xw4rKҒ_`68ҚL6kzuFkI_0fEde$~U{ft h2Gu˯E⋷FU~6B?MUS؟(G޴΅`B7O(;b)ԱCO % DфޏMQ^24!voy'B$}CZ1 33(Ścچf}xdXLSÜ8۰%~Oq\;)Wxեnwyٹɟ.>(wOs4lXD,qϽoe~ Y7e+_.v!rK~["j`Nݿr_:Q;#J=65l ހ7ƽ ox w& g1g Lc>˪⁗/|Đɞ[j7 r~Ӥ4w0#+ebdc$3I0*>M" $@V7M6ziZw٭]\TRg|p Kflv)5KJ5_D+ݥJZ1˟UCLu `C7oнJ T3)!XD̻q[Vzn&]z{YөyX }{}捞3jm2c^uk1) s ZP6?"FuP!_ػB l^T ,1?Q~5nVh4֭zNAxwXgxa45\c0Rm緽JEx£H#h㏺%s{}+Mѭ=7V1(,x˖W[Šs}%fR>s TJR|Mܣ>2c]0vP Y>㦁ݽY4򣁷7'aH+/}|?xs6iœM5\qu:㦞QՔg'`JKCLyry ԩSެ󵳞NGVATŖ` B*ҖBc֜ TpduI"L`YA! ?׊t+d)\Xb`$&}" W.ٓiM2`Z13r Uyy(H :c+x?1of1֬tOCda޵,8aE!:-i`'޳2M^r.(F_lr4# AJ9Vʴm%ud8Ks!Xݩ ĜOՑT%G./Ww>3 c^Cw )U?1>4ˆ`fr-pڑ \k$\I:z0Xdt}hYaʃ;tr{fd>etife~~ `Bi ?gN+lҞ=#1 J*y.]7E = %KqӅ|(Dq,B&a @AZqS1DnHHHHHHHHA@P        (TܔT$       @&)[AM<?:>rnGB˧$@$@$@$@$@$@.\l+(pTN&`C@GG.u 49ISJNTnPTWݳݦՏeq}|< G܂NQ/8y l&19{K$ % +fm:kFnB}li`gWAt/ON'b_;ClQضl))e өؽu#6lށ=G!Ȕ1Qк[wul ,phٽeb+jv=v!u Hgpmވۤ,<;r(g ÀFfLO51{qD{) '(3A+(:MwC=pE,8*M=FruΪ6s/4E4!̠(W>ZY)_JSxW ;7-j 4ClQ܄^4tËHH@ yTo:& QKk;?EpQW kR8"tv\N7M£>Ͽ)YtōE񐛊o>ôxQyN$a<kW=ohW'$̣X3[FɑEb̏1es\7Q)\`k1񅵨^<} y 0w<,zٟ/>+[)ݨ(nlYOxhUK lވkP^z=.sɠˮ/:y`yݿR-ď) T"/A+_ȟA$@e@^*Q(qC1 uP`tX~fwqY0` GepMU\}npV 8>_35o:5i{d>3~!iv! Nvo?<}E۶Y)Z#+Z׃B1~h0`( js_2Rc_Qťtqly_4a.PeҾ+g|rhy=w,84\"BRs"'8߂&i˔_kѴSxB 蹞Z%)O>tvQt7}vJsLq Y!Q:\C<{ MiGr?>|QhCŦ& (Kd,fMv1Õ|mtw%T~ٮ.q @ͱgȶ塜נ},hLpףs"9XnDזA@IDAT10٩g0١x|y` M$KM9_e ?uΪ{xcCxh'1]4+ƵqQ\%ʢs) EaO@o4_n|=+FEAxxl@+R_0wS4W7^K`/0_f(]r}1^Fv_,߯]HC9㠼XR<L}HwtV/9h]i?:Y_[(5/W]ڣMZ^ҊHHB5s"(ݼ^raZ-C袜 PiS,JSsxz(,Lz柚|m^~]_{'?nJ{mpc NCf#K'`AWqIpU؅Ըu(P؎,ƔV51OqƇċxj\|$8^x/Ll0c:Xo[3{6ڕG"*W=zk(רk5 gD4phmm)" S*OJnlݼ;SI% r54k q]竅uLє K6";&NAF{ q]ai/I΍UG:hڤ%ǰzECz ~mqH =E2x,KƊu0hQi1)l_K>e#RuթE*>ɥ% X|$ v%e Fвy4r[/5Y.Gń\ݛV՞+3n܌?ZNڡiR'nd•=ڳ IxUD?h +cϢMnLl\7~~ Њpe}}H|hLV\UOhs2~߆H:FEЩ1?T)િ-,BM+#M/]Ivf :6Fm?.Y {N ᴣaPvnuR3aVRIBTŠ+;MsQ)sE)2f^Sz')2TjCHCF-؀_Q+؄< BitI? i.f,9Z)ׂ.; \:l{R5;ՓVC0@)^*eRf˾FˬCb y^yqV6Dn 7] J&5| @Y&LwЎf z9ޮ.a)E҇/f duJƏg*Hk~&d3k-1"k FZaf=#mC]%zLST={Wzmʜy塑{ب^,Us1WށD]Ӟ u s `^ W-\Feuicq[l͍6)nR6qc0}?_>wơScO&” Ӱ9r$G'8l>­ \tj"e <ﺅfJ~af\ͰdqM_,|$IDt/3^9~FNo6ָSBC))?2Fy Ca߸5l,վ 6}98/׌vmP2@ ٔJ5 UAѫ}mxy2NZ\ AS\65%]}fԘ)kC\i*LNώ "aDz6袴V9J`۶hG8iJS)]+2F3EiӫN5g('veɊsW6CTm "1Pon/Na?(m:Bz+մ[nM2mjF֓(u:HyPʹW*97ET˯25ҟe(mLk*bw- Sf-Pᡗj++cD`fuZ @#W.=c߻jC:7yGQw $ZhI0@! A4A.*XQQ;v諾" { B !@9E޶ۻ\;|N<3:3R9[b"t}qQc,hUxA^R'Rtyvg9 `L!plj]cTxOrȖ>-*c&'QJ,6m-룂/]\wj?S9h@M'`6E(`CC;ɕA@UN %Z"jQ3U8JG8Z%j ڄ+\BzkA qfl'Qh#~R:l6EL-(,77}6vH"0"H=lcO8mD*1!jvCY'Sjz !8 sENd׼_ԊB m/Nciׅ֍ deljb9Jt(KWn ,j"V%*rvۅamUtls#/`j˽ɞc2Gr*GFu׎Z^.ܩE"ߋ=rFxt(Ղ '1 m;Eq{)xF.נ>hM'ץAue~k$gps]u|َO>_`oS^yXpTI֞ܖذL*@fw䰇L?\K1K^d=-HYoaUwofة|{ՊYS 5yP Aˤ8̙NI5w56>:Vv˥X*y25Ő_Z/| &ڙf *4ef؎.SjC{qx^ڡ/9_vu^t.* Fڞ{T-4xyT4#-UEw՝OO|HIOVeS>-Gn.JhRH,Sԛp ">A98i֡\ &1s!vZJ;e]0gniFF\M>8't%B]@6~N jV2eۮ~!iHShv +8v Rpy8BZ.#؃}E@@ٝL)K6A:gTh=='ۙQ OQ _?+QIm6&ُ{RU܅-MQzW,yRЙB7{p&$!#|0&W 'w!ݟǭF`ᄠý/]Yq՛?BSs|} rVmPZ>Bg\ϰxf4#L‚س'5.f21E5\>wN}eGwoJxNQ!^m3|ȶ 3f)e+v1-8YƜ0{A%i;ڴS.⋒diT\:>gcx/T!K֊Y@oc(z̺fJB1}Yg|eqţ }b&,>µDliV j,ia ubuWvZ_KZ>56$œP=墨!k_hwnNy6KݔhCM{*k `ZXqZ5\C紻lC%&-Ҹ=II4|ftxuޜ)78#4\vA7K/>WSo)6l^=)_L6]v02;2&b=f&Ma \n^߆CBBY=1|\??fi gKN`ìjM Կ?IX uTrgyc'"j͠ \ *)G5Qh>}^^rٲx,$qԿ돫DAXr3UaIplexx/~m7>LˆF UP68aOoLÖOdWKvݧ*WFhPl_ ?  %?.>_bת%cTlS;?G1lKhﴫZ2'-MBQOb(Icx`pa҄4 .5a]kC;I[g uۋNa۔y|`mm>/S&x6( CNnN?OBMlƖmyQ`1R 4d8 A]0_۵AMibѯ2 Ur9,[!&7OE:mt!<ZPjF j!STd/w0-`dMM+\B OIjj,^q:M.w`-Q+Q8R,S$~ eL5̰.K#ݩ<'{D =4"g! yS1-u\v˨Dz;FxtQ:P a1 hQޮBZJq5 }F|=7f؞).H N-xL}ߖB㍡)~LtѷJ)c@';^D6haw/͈lg=^[vD](S+cTCBi;E+, n2R֌T.ix >HXxc]럻:hLFPOs(%VmZ3 ͵0YG0xK꿆8_[@$ko꘭V~1brB[~WhR` A]øw/sȃOԵBɼnd9~P}9K5Eh#v@:RqlW>$duڸp֢U`EvPvy;@ghT=u|"k($&$Y:@v<[ZY[%S <-} .::ďUefCD0tXu zXܶ>;+s'z&\^牅Z'4#ҦwO{+Mж:ύjOS[M ֣XcjdTtM'E?v4Vn_!\p_ 'ECy>}cBP}g֢P 0&p//simN6.o8wO `j$n)4]E6G}rB nSɿ$Tw(sJt:+ {ڜ 1Rܬfި3YX-lup&}^S\CDֿa괽.y q- vA4ETѴLE~O]̑pB$*RPZ&Cim f>[_]utZ jWnttc;ɶ[MWGH;3 /H`uJ!=qʠE<)]i1YpqO}g ǧSz6z=Ȁ}3wfWrxaN '@i꘭tޱC2E՗OAHhsk<.iD.ՙ3[6b)*pi\ G{*f~f7VńU9߼LRm^Exzh D4eҳږȉC06tV: MloCCӷSВ?S5J롪]}Zqrgi/588.l؏-WN[pSۢ.ILlQ_9AĂcg,nx4}(z{؞)b/;7Pom.kyTpg t9;mRrQ?hYZ6 r 05\\;A.V|Q(?B.υmkgsY%mI^7&G0)ꍶH-_]ǃ8\bO9Au"pBs6/雖a!m1iJs`nۨ?1Uw`2I391v6>O7zjf`9#[ơEjV9˴7o+&VJULHOƇ_a;wmDNxʠQ#\J#NBAJx8kRH3'[q&/ż;ͤ&͘xQ~_(@Tsp=)Vr<ܪ&ZX#VxdGiv$^#SKUítiv{u:2_+׺=?C6l|O]nmb 1,842=A?uhOȋLIiܛgGItu5 8j5 VJVvld4 (bYk)^b+k-RLnyW||G[4\6zSMZX ڔi]: Ytiپ)YHbmtOjQ1, f:uh73V ?75gjQs W _4aɭ !d rbɘ.OMTtvo'" BqtկXk3-8oFׁCPO^S\iu#؞wwB(ފI,AɄBEn4'T-C04kIƒ-߭;s"h`†mF7:6Ԧ0~ 5H+ڊax[ci[jƖoVoxE3Np||05o#6}tQ*A<:c`,Tڷxޡ?CaU;^ŐwN YkAIڎ[?t&'6[q&{{PtͦcNy('2S=\H:56"(] GOߗեdy=NbWz0/>/奁G?'sϛ֒w.O:&Jè R̵Z^wVt|K 6\WX?^m}?r)~%է?wNG(lm?C^No+/6mܟ{*X'?Gàw5꬧.Q߀ܓZ]t L 0&@hL٪qt|?AqG=z1Ż`nwT8M­'g-A \xF&֢*H֕vвR?$Ђ8AۡC=M0PcKL5AcHs)9.װ:T-T9 Iu : 1 G݁=V Դj?:J)U*kGKYQCnkʫIvr\Nq6i!F11uUΨt Fhת*䖈 )۫{Ւ)L\25x9yΧ\63 N@0ሣNSNϙ`7UhހtoBol{SZHlg/µO.[ .`轝zvʕ71Un–bq-GFD6| TUť~!ΤJq+Zp^ԟ7cik߾ĪĮcaxw_ 璪yY̩*DIKJR#8MԐ +pCT({@rBG -(W)?_Aiˣ6^}[DhN!.ݛ  0orQ.E0p.Xi/NT/ ˙0NgQTlJ7M0 L7lX9Yg|Wvp̶Hdi ? MpWIM:ˇEYbp q&/ !*!R~M'r$({=nt}}TCbmNb9Ozi#e`FN`L 0&`L`eΙ28c%Sݺ}.k4^&0&`L 0&.;\G`&@ˡv†8l93d_bPnk;`L 0&*'`L 0&`L 0`i0&`L 0&`L Xp$`L 0&`L 0&A7i0&`L 0&`L Xp$`L 0&`L 0&A7i0&`L 0&`L Xp$`L 0&`L 0&A7i0&`L 0&`L Xp$`L 0&`L 0&A7i0&`L 0&`L Xp$`L 0&`L 0&A7i0&`L 0&`L Xp$`L 0&`L 0&A7i0&`L 0&`L Xp$`L 0&`L 0&A7i0&`L 0&`L Xp$`L 0&`L 0&A7i0&`L 0&`L Xp$`L 0&`L 0&A ]"YYY؝ \q*T{4\ &`w\[\,;RMqzL <`L 0;T ?k1&> uRǟFQgΜ1rf7&`L 0&`L 07ʕ+7gq7`L 0&`L 0`MsL 0&`L 0& n|Ʊ`L 0&`L 0&p, 8b΀ 0&`L 0&``o8`L 0&`L 0&N7G0&`L 0&`L7,bL 0&`L 0&@ &9&`L 0&`L F7qXL 0&`L 0&81g`L 0&`L 07n 0&`L 0&`'# `L 0&`L 0&ƍc1&`L 0&`L XppĜ`L 0&`L 0&|#߸q,&`L 0&`L  n3`L 0&`L 0&oXp7`L 0&`L 0`MsL 0&`L 0& n|Ʊ`L 0&`L 0&p, 8b΀ 0&`L 0&``o8`L 0&`L 0&N7G0&`L 0&`L7,bL 0&`L 0&@ &9&`L 0&`L F7qXL 0&`L 0&8pX-A"#kaZ>>Q%.5w o( 43jiXc{_a4J!eC|}F+bw:mߞVV{ecz`|#YXM*i~ \9+QPt9I5RH/`L \G|X"ukfAˊj-оNQ D}X 07@疱2~g'0<4,ec`=q _" &?O-1}ڊaoIJ8vR\q $P,τ f}+ʛ.c)- =P^7U|Z&眬Z͈\ŢJE 0j )^Ơ;:_ Veeԉ 7P3*G"FԈK[s7c|ZADsQ5P%1k*/s+V._m{қi?ePF-oubP3%?}5ҏ#rcsiM=]b~?ߥq[jnQ:I*‘Xv v)՗Qh6 [I+fbgbhZn lY +o@橳R*UѼuW SzWc<\lٰ kqs| fTo ڷF\e?'0`ʢ}-]sT)ع} m߅}Yʷ!aH~dtֺ阳ถ! |:dzE0kAyj`cu lrgo !Lsu2A9pF&aY JGzxB`ڈ^>oC)H9:Zs4Ϙ\![p7 D{ѣ6Ht{SWIW-Zvt,8Ab?@sװנ5o/~j<}?e#3c'֧͗aQrE<0g41 \5>ב30gI]y]W3)i,?LZQ޾' nJ֦^>L5ɉlعW ~͸2NӗӤoQgHpS Wpp~4NBa fN_#~Rkjiӿ/3 o1;'-oa8^8ޛJ2ISP?`wId. \1;[Ve8pB gpn2w9{Kݙ@ va$WCs"K4fFx{,AoCUYj\Ifoq/+N'-vצ+=ܣ;/]xy<|.}oݿ\ѷgz7G/߱kZڻMo_{_E[{~c>qڄ!:6Q8UN߷4M'":%:uRJ?!K`~!`#&kB_c&<6:'Erwo}ϥ ɧ@bNh#rd䮔MAhzWnC91p>K~Bahɝ[Zi-a/cMhC٘H#qshՃЦt5HnCJN>Eۅ+lAeE+H_%ATc +iqKm~E?-3Oжֺ%񆛍fgH3r^*㎱ϡP>.Cf>&>"y fl_gCћ;3F;^Ner ioT۹J1WA{Té4: +Yϟ[/`qhWXCΙrwVyx=oR%/='\dlY'd?->}U|޵ su0 zb:."}2,ٗf9ݤ)Ʊ'g=}i\] UaW@IDATtES&@N~M%vE&En~ :䡧`4sv }ˌc |5sw .2 7/0:ѳ{wʻHm+NEѽ&`@KlF o[3p(җf&)CĮpx.`a9E' ?ʪrr3*q@-8+۹ZW_ v_ %vTA|hr._3h~t_")w:UixDZVwK R6 ZKaݴΒ-/|hc`*1 ykkZ:eUdV4EhE)+N"QhI:*]|uM g)~CWAwG5 {um[J-ؒN\63$ 'NjOzh1'-EG3䁍5hSGOίvgz1ao&P{f}%z|L b: /a=Z8mDHs8 [tF-q݋ aq* ·./e>=Ku/dK⛣kΈqY*'2?iiAOo 5aIz/XoV#f ">^ :QQ >y,A=y-d#[fphR ] ЪWȞH]]]x׬v]4ZD{gQ!Z׭ =akvqjxJt=I5Mώe̠5I!ZW?TuYh)[p\ ʘQR4E: P63?kV\ a݋;QpJ?Vk{W"oi(gm-{5, VuYdԦbt]4#8< 7GÆC PzYһ,k$Sǎ鴴UxR\˗9-ZPGHg^sbDd҇A0<ܧۺ(%g_y _Ðb8u6Imd+j#)CcUݶ"0K|OBPm{DKL\S=67I3{{YnXhϛ>i˰QWȸ⍵ڎIvØчXS0>!aÉK}Q;DT>uOΞܾO$Y\:=-soO?coZ-"dPzYlXۡK=h9R2F@-4hzsV}m[DI(Bk,'ɓ s7,7Axc;vTmNUk*1P`y`r*(JZFL[nWm"!N1$o*{#uħ–vaTv:0WFN;cw1dqW,zZ'= Lvv,CM4G}&HIs E.ɂO@c`I[;ޡuUAM[zWCkt2`, v;G5Hf| ."7G0mP뜾.~Y+^dcd()ykr1rUIƲw5Z i3oNՆƒg:e?픤=K0i\C$tNcW! C6BpCi2qXa,z<@œ2Ѵ%Df6QL6SzfGÛ /0E ~ +'Ƈ00F%jI풂EXb9AJmHp#Ÿ~1$skKds(kTG7ͺx][ɂ2:K6Z&6!O>p`O,Ԏnm#~p 1iv]I,t ]"yp4~#k5Ĵ%{3yR  {֣ ȤgfR~OSRhY:xküEdnI>5PZ>vjr0t]$rj2zR]d ¨ٳ0S8Y_a!L9m.J71&;fJI]ةMܩ+dh;ȨgFܿiSh )`ȃSmnJF`gb'Q' ʜq0 rs^U֞GL~260^<7 <eDF55ASo.arhBˢI1QB-{œР?rQ5w+uzwI{;⠱Il\| axdT3h#ywiZ6]iK< \i+}l"]wF&5sMZK}͍{G\] |} YŴYԏv祤"ktEW'[\ye li0\<^WkLlyaQYN`rDիHl}y}M{[/FSwRe_ nrja W10J/!u^IۀO^ԌeVovnF:r_õgb$نjHK5[7MejLRur45+Cb709dpV/ 'HeByfٷ mM2k.M_-r@9w/ZOԧ-CFaeO6qxNtDI٤1b #z&#JW#F/ ޔ]ދ IkQ%M6ah!EjE:㋿YZ]RHTfwzִr8)$# Nd, %q|oMc$_>ϯ mJע=j=8]K#4Ac;m ъP>Hu@y1Zf{fsژߔ_]C]Ť<;M'1~7DDatEptC}uTțs"d2 BwKSd,}>fVRT3-LQ  ݪ>TI$c]~?Mk?(j5/Տ61B6Et{-r ] mb:c#ē B#Z/ } Ak0){0ip8k3P^lnXbu}mPF/D5Sh,%vNrڷ!.}ʻDm 5's0g6͜ H]|LSU{7|i?5E`H)IbзM Nj4SKYh#J#|9'umS!5˥rq ʗ@~)5W_7%?5Ql[﷈ -v2@\E-v*m>Xa ZwܥhU aܠѫ"C.3J3.sp觨+FoY8Yz% 1E^ gݻaB]t'RRr{3 @ C &'Qg ڊ4U4#N7U k&֪k9yg؄(yw|c6Trww(ۺAHD@4k -_C躺T]OM£}Piy|4U@R)eԶ?Ҁ>}ao|w-Gk@˛[٘qsH|+%ݻ4&f؋Lr {ahB~q3$ ÞDJ&6.(#_z@]bEOCr6zn>Xl zhՏCX?>hlҷK"V=iЪ<ǰʕFu=|pšpyj\Jo꺟F -.tnxbi]ݚbKD6ှ1wAAlβ Nh8=Ka[w2i#KV EܕpBG[!"fRմg6=־9f̠瘤Bhiv4.h邖REdqraAktu} EK9LwuA̸Ѹ6!Y/`+~|6MSD-j[\Kz~|z?$ɗ**>O_ϐ[XIi&NXu5QXAhf٭c ռt`[^')FuITfvXA'~{B~y8-/SXIڸ?ڸ'.ARTn5Z ̯ؼ4Vt>tk靝..Q>]pdILK~N *udSbY sn,-7O~z~{8 }R½h6{i*õ&W!?.rq, TJ}'x&U 3{uM]AǞ05C Ww>Q&~Q6IѺY,,9}Ht@mCBڥ/Pћqd=TI UmKoY( m쩅DQZNN%{=Hȅs]EкLI-.&}oҦFD$>۹$Uazp,EZ@Ag>wavwMk!N64BjnU:\wIҮR&\r>3@/P%:q5 r# mܳx0H#Wm zE<+xoWͧ_3~ڧ ,b{|Ah# :([U-߇rw颏2IHK!`:]1$/./ w+=(0Z9>zz^/`7AhxeԳxvX|0*ݔ}ɮɑKIcu[ Uh 慣7m:7uxad_>r~BжBucQiXbxɧd//ǽ>-vTmK~x1xwH0Kz.DX Vm^|o?x' @(%)vv^ͪ^|9C@ƈI >FdZ8ߩ0~l4phWzp>gz5O[#Ah#Oa_h nD&B[AHNm%%r'TV4)փ[Tvv#3E%NBUk% tu _/ij gMN^f&*Jm)Ǝ 5@b#j~QU:4` ,m tft˧fƊL!ڵ=hg}6 IuٻGnOڭn'?)Q^0.r 9ˣFt 8vVPruDWi(>>y/*21÷D_5gqCn!qѴGC!(\V h+:- ئ1L?D@$ڰ ȡhϓ'Ǜ_Qq%r}R o2ȆDuPet}^FU{D}U[1 hGŶ-j'yߏZ(ri= 1v%~:/:4KUۛ&*@ݎX N۽/Zc8PetM ߫k07sO`ǬO48yZ}Nؗn&^ݦX .v%qW+Yl ΐ\uvT[wn-Tw$^PPe[sVTM:/ͨS7b䨺urm^%,#r Y7U$<Cێޱ YuT>4O@HSرx ,$ U+TӦn:4owrsw;_R<&Ɓ\Umt?GrXqꄣ^ 8Iڞ3s;NZU:3ZcFB5|FJf Ҵ\2Dh~XsUI@{H픛`0 BO}ǴwwǖuZ$٨jgFQh9>Ǵ;V,#kފ\~O`xW[C[˨R Sd#;dyh?kq9 HBmDdH ?WB۶_CC3 !Nòr/jB kwRcGgr!+RO'{Q5H KoXR-o9f3b iC%d㓓_V\zd¶/S7IBM 5 䲛XAfp;}LodA">:yD/s4*0(ܾ^h"F6O]6RZui86˨p -]+-]kHm>-/KJԀ$3Qr7oYxDMR4YO.KVEHU0=Y8WgιC@ ^kH_^|W#/JIB5H@ik[o9nF{$>Y3~u8XM`TV*HyQhƏ@ϻ7~\v, n':)U|#Ж^"5֧0 6-msaf%RK_ZQWq GNGVR(m[ޑmd67K,_ o_!mC<1Q#'ôHKQz]JrT2 L;XX)d_L“ԥ=B շC~]g<U?|ʳLM\O~%FߧO'tDWAvɘamqKC͸p~[Mvs7ektm˦_K 8wIz;kHwOF `7$q@'&s$'Y0/4-4ˬCp:",YBR>E/4~ߤ5E T}V\SpЛB7D-]a/'>1u@ڥw4AO\X gG+Xa|D{(jFYmp^?>V ;1U!4["ҏta}I SFSRuUS!F=ɱ)R*, iaEȏ^BN=YpR>YX(gNt52i;Fܫ)M1YK8SFo4=f6[V.ڌ;(FY[<\hOշ߼K~x=} I7mSaߦ$0$g\IUNBS0ah8v autfMTMNݑ5'Cۄ7}[8 m̫茯 geF;<96dh)L`@=:RaShap(A6a_tE@6Qyi1U+ł/9;IɜBIֳۨc NK ~>8ՍVҧMq*_:՞*\ѷeF2 hZwpdx.%܌pmDU3izh`EǮ*j.’G]'K{Yp`:| }i{|_Xp;A*_Zk٭:5nPE=\'l9Ѯrmb}.liZ$uC ֙c4m׫e@6i@͡bR5,ڧhЖ3N_؂rvq 0j0mb_1dM]cpKR!E4pligBoCzN'оyٚ-=Ѫ2-o^hXMԭ_`'@Gݰ%c LvӶzOAhy: tCOi!}}L!hEfɎ"ʔR4wRF}Toh ?͉bGƎF}&Ҍܕ2$DiW߼K~zϾ瞹KFB)i=hms㤮PTٿO瓖q8o7]7v YhHv54n&ŞUK0s\Zm\pҪ]b H nJd;ƏW ֶ~nTK݌y5 shZжrGS*M!Q"b 6d7^Ca_݋4lad+]">.֞mfLזtv0N`XFNӌ;`vȥ^H}fyͪDZu$z]SD4tOIeAٷ<ś& AFޝUQ}(&B.[j▩eiu߷+-oݺ[vgYf){(*vV33|gΜ3,$H+mexE'ˍE~ە(M?i)jf> WzXhb<wMX)i&LmBr8%*H)m\`kc+ TqYGIkj nB}"YUDzG].*FZ<_0F6;KxJo_3c1V9jnTAm#&Uz#֍q,0sZh7+m-إX n4h{AR?kv{5]O|Aetu_5M'oܟr寲@ΧKez2i7.zi}f "ȸOׂ);Z3O[4P>nNͧ)ݙV&zص"ѮUеRҦ33hs\'̦xX9[iF6Ӫ6@7Ύʏ2S|e:qB cՍbH?=V2#{^;=W٩ˢz)H(;* & a zַ:M&;1w۳&c)1#G_VVtq p4N/P]Jʗ4銵CɥȔھ*sm=8ڙ)AD)N SGv]CNIczp3^6,-v`.Ә^}̧;R㰇Ì9kfBېo1 U})6oܭ:rxϾ>R]ǦGbךUlғ?w2iX} ߚ;o,9¥s&VZ#guMiVLf;UCl e=~#߿z>_x?C-en߆Iwu*04c v+';i_l&ҵP}8aZ繿F~*ū_~_v;bN8lAu1ჷl.-h߯n{ع)Gcy5׷3_k^AuHDNսG*yR?$h󅔴у6-ҮK g∧Po q~H>OUdikThMJڸ;Aѹ{rJ<hu'}gTe^Έom{F XxߜG9߼1tX^Fg;Ɛa1ȴ<4uQz<\?b|;ue᾽q5"ǏOߑ^hК۔{]RJ%WYCzxzrm<[ȣ4Zy7du>aR<(^/bxI?DJFhiWDWiuRN }*&+ժ z>{!ޡp@Vl{ !B.>"TzXhy6Q۱\_=/?/>J'Kbٵ ._]a5\͌_gvG=TǤl_غdB?iW'/4uBdΫO 3*\GV.#)k5-/㑶s̴_kѹu0*J#C qsObݗbѴYs^};1rG4M=ͺ.i6ڇ Ko1G aG/=!s'o5<xsu*Kw9{vP\4,bHUjlgbt~>_5H{Qx-NUg܃w*G_tXd5#Z*tI֞? b`{$)Q&rf>G%;a_^yKo?J#xʃ|n ƅC!^҆9`=T/h@oK-X3vTut<5þ]} FIRoܟ;$ƣ8|u^4.(ΔjZWY"zs˅XPQSw2?]3Ϛ?]d(zQgc?cӤ}W j||uv0bTrš% ?pN~\ᕸ®.o?4++ ?U"o]Cʏ|Oyh.3Ue)ϼ݀V~dǍWCt w)0R>| [X2,=G3?/~n+fY||۶bNJ&~f Vۂ^QQ?۪$:^qn.8پP ᄎingCwi3h RyEZ,Rfٖ&ut23@ `:u ,9 Sz~|(ZWtqͣ)S+dqz.l׉Zb@b5eJ4.ތ~Zג}qj_s2>+jf5efLQvk7^4ֵ<0+,׽ -Ǚ:שLp"|ٰ15~Pԓ+?}D8aZwizW֒(ײ4zD|L<7|s!F~X"a A?oܥ5,[^x5Po|F~\)F\lA!R4G堣g⤊zKɆ*@mYR>x &Oyvᬬ~Hζ?PI{qxA%6@ҤZ4|o׳<5ȩ Kkjr[7߅w$5Wc >_psu=]YzgS37_5$] C7OG]"Z3msuDU^b7q~*J:$y9O?R7瘪οsΔXbL- 7MZm7[ۤlh)[L'FEHء}$\Wl,XM 72'jsr9.'=Ѷ!i5hfŧ-N B4OfFRFN+#U4)c@B ҇ Gtot ow?572Ye7.a:->jsۄrMߘa, Ƹ2Rr|@>Ʀ%إO[{ =B"ռ* y^07:r._0S RIY~GRrs+mR@1ʼ&ׂQHwzg^]'|{pƩ1o4>m5:(e*pnWLA_Oyǣ̦W_C,ﻹ|67\y̸g Kʺ-&<_gԗAXSqJ\F;4?O"1CDkO͆#RAͤH㈈jFUk BR"Hƽa[.UtVW˯-#KkI%q+˫+NݛGI\ux\,B,=Tzc?4 E'_ݙ34ܐJGPyz[g?V*(`yH{~B!C)=ڛOjUB[U=^Z,cMl)x) yl8>4 *a0y߻>~y"Pl#"\nS5ˇڕҵEX{T2ڎ" zTcJհBi\]z9~s[#}ѡ|1_*)ZB> V{Yy{&Ɏ-'?z< :?kحjQOzziڂb2ڹ)WFwnS)[O^y05JCsSޯAAޫT+uPlm[xkW3ΤN+ :tξzP{8Zp;27<[:GIK(@ Ptn3Y*P$VY͎R&vC߿<&M(/F "#IDATWQsT lqq5c9us.7L P(@V faDaSs r \ TRfk!눬S i&Eb s<"=ǹL P(p xqbnNËY(@ P(@ Pq x;pƉyf(@ P(@ PE@ P(@ P70[(@ P(@ P^(@ P(@ P 0pHO E P(@ P5@ P(@ P70[(@ P(@ P^(@ P(@ P 0pHO E P(@ P5@ P(@ P70[(@ P(@ P^(@ P(@ P 0pHO E P(@ P5@ P(@ P70[(@ P(@ P^(@ P(@ P 0pHO E P(@ P5@ P(@ P|[htPx6S Q8~8N)u>z4*xvo_?VpSرc<Ӧ(@ PwgqvFyV&_?!!!^=~J"(@ P(@ P۸ #(@ P(@ P`L(@ P(@ xE02 P(@ P(}no)@ P(@ PW #(@ P(@ P`L(@ P(@ xE02 P(@ P(}no)@ P(@ PW #(@ P(@ P`L(@ P(@ xE02 P(@ P(}no)@ P(@ PW #(@ P(@ P`L(@ P(@ xE02 P(@ P(}no)@ P(@ PW #(@ P(@ P`L(@ P(@ xE02 P(@ P(}no)@ P(@ PW #(@ P(@ P`L(@ P(@ xE02 P(@ P(}no)@ P(@ PW #(@ P(@ P~L(@ P$P3Œ g籔_'67i(p 0ps_(@ P%ElDz=GQzb"NHQaI:XJu٥Wã(ǪȒ͂J7~MNQ`>]+V!3_GQZh"7k T2sމz\_ Z 1Юԑ½-Gi U$.bּCXr32~ۋGNLwF6w\^9r1#pTXqN+ٹn5EȖ8u3y;sUKv\ߠQih}i&~ڴռK-1X$$ n^kvcѐ/ztŃ{"*>cS9i{80ꂎ@>"ه|JEnSż9mk231?t @Il3v¢9 BlG"˹j><2 vv=u ܜ:{(@ Ph %ڢȖ؄c؉ϳvM|G|v[4s^C`aR1祅eسu1X5 Jvǐ(G"|FNM/%s *BAb tP'5ߋf+8k͡$#/MM&ĔWkTʌ< sMLvA%^xy~\8[ ^yZ8{`B@BƯ;(@ P(@n#Fb#qQMxr,R5iƲ1&JBkj('aa*v31& q'<\mq(a=IFJT i4#\"][6%_[b.u[|U(zW1c֜zӴy+1h\K5"]7AX7߶:,)3y׸9%tge4 틤0"sS?5=F\!]SM;M ~+nmm }z3.Xq 9HP_c(oӐ (@ P(@"W|,2\]Pv]VYX =a#tRH Gm{jʖw%ڦ;X;ER FtKFɋyGP8P(@ P8[clrUKߛOK~fe?lzc.۳ķk'`+|6qPK&}5Kt'mW-[TH`.\3Mݐ)ݜ!$FM쀡bxK='1}xSZbOz[%ȗVԑ~pGnRLN/Bj]jʹ=^9Somamg~~?_n='_1$:QBq)hw /(@ PJx0?tC8\%og66IWnu leqp!]n c+vH07{ZVj'-=Tђ^!s1cV9-ɖNHVkޘ Պ4^})_㌖m^ҝ=cbAT^>Ahp5FjZD?ZbAnVjwu^+'a)n8q4(@ P(p c?PpSɐcXEV;& 1j{1^FSH%PYRuߌFpcC&y8_XjsmsO0"WYRZm){O$Y-oE "Y39lvۼ@Ġm5.xh0m11cf\pyUOHݏ+Yݵc; ʢ2s`Wѷ F]Ǯha-_ 3(@ P7`ej[01ܶHhһO<ܚI]g4zK6,TC'+{!M0*bmJ< 稍ZJ DGYIid0Mض.cլP+f}1K+c9rdIo\{svTsի+3-&tK@-@-@L}zy.'$ sk?eNm$ֆmԙs0UƔƍhA1eF&4[}& C&c[Y`GE1պ?P&Һ%UmC\d,դMB:G*v@(R ڌ+sW╹)(=_]R.W֘޶V)O\o&'ʟIB2؝G !xykUfrp(q@*;zF^?P!f╥[1+ѭg{ifv+y1ZҮ%)]vÀ|um+Mߪ^_XSޏg ڶg{=n0~Յa@,|5>'A-7t=['\k/xH<wwSb^ښ'7c٥qZ6345"7X rBYQMJ?z X Lٺ)$M;)yZö_>0(F-S) P(@ Pg@y)-Gٱch# EDD(d9J PcJ>B[ sFs:mrIX,*S mf#EEj^B) @0ky'Prl8f9,¨WJI7ǧA0+!BSj>;FGM w ͠fa(lJQA`HZWeZO9=sI P(@ P@d|[.M==^PGHN͹.Ne|Ev8(@ P(x7{?=VE}XmMHk1hxNinNǬS(@ P@ D7şG]oJ.)Nq5)#9XU4?>(@ P(@ ԣ\dd! vV dKIa[)@.ERבI90;@œ r0x00(030X228V:Teg(8C6U9?$HG3/YOGg?Mg;_`܃K})<~km gB_fla810$@$wikiTXtXML:com.adobe.xmp 558 944 1 v@IDATx\TU?1*!d,%D6>Bf1lպZnc=['7?djbankCm~"a af?ν~9|{ν⇟@$@$@$@$@$@$@>NrG$@$@$@$@$@$@*      VA lM$ -. /=c      h4 UU?⧟<ۂK IHHHHH\PFRozrSLO$@$@$@$@$@$@Sv BxHHHHHH|X_  @։HHHHHH|X_  @։HHHHHH|X_  @։HHHHHH|X_  @։HHHHHH|X_  @։HHHHHH|X_  @։HHHHHH|X_  @։HHHHHH|X_  @։HHHHHH|X_ mU(\~-ߚ*^sUׅ5 h/{vyhh|h<؎B[oh>uk2Qn@DǍEܭ!gq99?8i2&)[18! 8NrnEqtg:kSg" 75,[Kwb6e`{i8zd+P;s s1׉D呯GK[PcNAD?]J)+q06qXwqQ*'mou/4I1̤d c 5Sy|/F4"7$ѳg˝<׬CfE6WC:(c_%I[z<w5{Oa$9yci{6ҏ['`Wd|gSQFblT< G+^jhK*w}FO2;b`hU?aQI}IHەI! F`YDGkP[js}#S'dp~{DDK?GwWF\Od\KAb<aRP~io Y((%8x]/eG&fCJQ0^ޭtnbA:In٩Ll Mލ꿠1]0܈S !x4Y aU1f!}0{؆+mg{8n 刷LBMʱ>'Q>.26 $u1#`Dg\@>I1n$7c4T d\TDLsGלh̨,[u;VDwQbd!_#g_2i9$!Jf{O L[,ʒ1Z, )$9 ྱCuMhn] q̣t&^s7̌9%Z1vXX-[]ۋUq9Hۚ J5Gxx%2! T[/iU/*ENWHlK yZ{x<:d)` 7A,zbWr}otMy}le%C@q%6\)9xr_{]ѳ z%U[.սC4a-1!,|+ZXzQﯚo\ uHzd'>oJ} ,n< GuZK?Z쇤AgY‘3[DJ[z,f[Xc:[3T F?+^e Z=!:ׁ(Ҟ7IicWM{ҐYG^;GDLd&`گ Xg?g:#jk-2PL 0 } 'E>ʚaìKJI٫o8G_E"⚗6băpv'8\Y]ʔV?׬Ǯ Z\`lӦPS$D#ڌ,}VAsW 0تMS^(TS^{Ir~"#yR2YU:Wɀ\BJBDY=B&ʑ!MF *ZKc~I%.ϑ-\2C[;}uܙP~(>Wg]pFG@)-) Z~g4;dyYUv0Vs>27YUߗKN|@w)Ʉ!B$ ^ 5OwjYA Pq!PW`ɉMD.fsioDf:A\Dʃ#E)1уG+ENDy52#ʔCu_;Q-N@Nĝ%֞OeCd:(N5ċ,~H7rdOatjP2n~$/,cH{,c#I?xRd@w92GM>[ݜLƈSa *>7SFH*8\E `ҟ6@GoMfƀE~D!e@!tO6iט*zJee3XRfM_'QwEz˴o,}d URe-I2ˬdȣ'Kz-Y{З~_GY<# ZYzצ1CVN#JLR#&NCу_])f_ĈB8Pr!q\9g2vʂ^"ou8(7nd߾ g9\KPD?:xKO/?9] EP4y7I_c'[ē̿\,*' Iӹf}+74CP={1Eo.eqQND[9nYNE"ex$"|(Q0}tgd2ZǪFc]'φpk CJD&GwA?CާGaaғTw[㵱@@ Js܍e,`om&ri~%KF8-EU6V %2Kx﨡(*[)C"9EU#(ŀpM /&{t bb V,|ȯ ;G!ibx+by诒mTOTTz 든[7Vj)ʓ@+=ŵQ?:*kѡC _KM[oZVb:erS%A;RYkdcRDkKiGoCMK_E>H]>d`d󺥫MkG֨yIfelP8N\扼^ {oUW U@c7bA"Bs "MtWtG7S:6;\=mu tI8wݒ'+z\ ;kNe r92Yg_Rȳ%%7\YU\uKtV߃Nd>jQSWhE#S>q΍*ٷ+>1*p =)G`%ƈrlбԳ<_ f!Dd"Otٴea,iG'dSEEmAvF HK\v< ce_(* "A,~L֠ k ˓z60<KMBԺ'*ULևȡ5t4T%u?YOi%8!Q1&WhʫYywOSW\T#_Ô_w%0]#{I#RZa Ϋk7>2q){UrHG5DSf#_Eo}`dm%$Ξ姹#DQc2~pu_D]un_6'U2>")5Z"|W&#PzDͺJ5=dCA܌(O[^ ;!mdT$;Hr*cWe4fψUR+aɱD^݀)plrS۴埛dlcᰎʞII핳چU2Ӄ6ubyrX÷Fe\G>~cT 9G@ш%mbz Ol{@5\W'G`}dV~}jQ+k ?!;Iw(aXU [|jl]k]˭<`F\c'5iU.FZpYbc wVݱA17[-?.!v<ۺXkG'-_ۆx=FL(,$D=\ӭZ/M:ZT]zwؒb(Wԥ{`_4qd9K8!nk!m8tD220s<̛;sw޼ٚL695:#?jW(9xW1@"[a`_0ELĈ6]KbѪ*[Rd oل)Z̴+M.mϕBsEd5/)\#am~lR.[2:*R-Q* vLd R|{{7YxR2`/AiqR׉le!K&_4y)9-{L(7D;Z[T_UGB,YE(M-YdZ;/+7]ױ[\WK$yyWG} JzGr<݋?9^K've";)^<L"[K-?&mV*oO9[~Dپɨ!J1|K_.::ʓ @~%db͖,U(GkW!JU|^vZvf jM]W/nb8/k,Jx]BzdW1erMǪ#yy= Eo4T`te$ݯdo!MÑ8Q.+XsCdY%6Xr2X#XdkuG||-zc9c3E` KFK 24[WcePɜ&d]c6+L-ѵ5t\ɀQk^}eg)0jq_2~ߕ1(U9ȯXv˧%t4"2b=" OpI]21P?*묎΅bVA.ώ9hdRv*X_xSvMO~)"  쬝Af&I+>ydǤ֩<"N@IʛٜNـ~LЬP$?#1GEC6~KuU& C:2D[BX(7e LvOT}QS#q:$[6DLMEȉs#SaK\;G1ۑJ_ųeYEO<@~xUZ5!TY vo?W>QLG}1=zr.o25*]V׍e91:B<`Eȸlx,^-%NZc1C) &E7[a"XyNFzGgYbޗwq :8VhMAಊ~6ՠC4aAe3*$"c-haX[2,V.n̢T+Zd2qGwݵz.5:ǖJ(3+@ouInYe{)uP\ rEN R!\U:sf# udlrhk$k:,XοM뺏FmbmCz?&`r~YEjkz_s$SLi[g+!GJA t"FV{6hW$<-IxYin!Zn..0g'YUL\V JmeMF6HHHH\Tmܯ:4=1xD\41VH2 oL$@$@$@$@#;Xﵙ% O-6[ @Y$ ԓzb4      %@et      z[OPF$@$@$@$@$@$в,N$@$@$@$@$@$POT` HHHHHHZ@P FMOY h[ NZmcg@N3?]c'wxHHHHHH5h> bEGWk0+Й \[ji Ys1 @k%L XSþR h`T_kX#2T^ -YVxklfUɍ`5[1 4LC ba16 8#@9sFZe%βE-:,P@3nO 7bB\`k&s+{5'3̕} Ku|^}m.ƺe~=zRxx l\,[2Lm+& :0cIV1Vl@B;}bc員5XVUoII3,^ U {WcNMƀeA֝H=( ;K^0?|Cz +6Ƃ!N2Q@\Q}s&7ah|?j!v.ށGjLmd[V$/ >QNM2m&U`k\u " dه\>+4` .R} RwP̊_%WOƘԘg^2w٫04Af)#kv܁n o0n0( [\T'g%mK7Z~$bu6`  '"p-|beI/o5k: Jta}?V wX2rT] {a鰥/kd]檩IX_銙_6]~\ڧo)4˲CݶXGr^X,MX*+;M gQ_˲bqkCjn18ٱIXYbNrj{@ǣVF@z ~UjkUoTq6Zf* LJAMb!Eh2g@@'P,{98[`,]A8qg%i';Yk4eз/o: Y![ F.@n^EvP}p%LaB9(YkS4V[V<~PeR屮/Fmguœ۝A2|: a椾>GT)VkB\\,ep9tې+Ň"WNXY&nG?_$Vdm)SX/طdmX17@q=][Bן3Ϣ^*Sk]e+ 8BmjGe.~! t* XY9Ȫ u &k`k"}Xl2, ԸiE·9>v.(^l>eҋqQMb:a e,}K޵gbʸMjkɔlBVa"[6Xj[m؊h 3( AB7qizR`zXܒ,k i%# lLDqa4÷zCWjo8:sjv,8^q J6Eb{k6-uXEGҠDѿ ;34A\AO Wظo+%:KU  4xa,ƭA-C挝X'pIja  ڙ 4I|%^LY>q ̸KSg##vvP&32!GdT‚w!4]n]]Pl4u~tiPΜs&&@lb-\7`JYط, }b](*oR_AkgF?l2W`-I~D'.$ˎ<,Aʊ+|zF];ye:NyUq 4ajc Vdx1۲r#kTZ]L/`/R^UMN}K010^S+רA`6k5nÉN6wLɮ"vm$M]a(vORǙv X0uHNfr}IEmw!vp6NtW]ekۀE4%2Yv=".ʫ28dL;ANm/4Xw.Cθz{/D=? o$>#&ᙵwȄyPq*U FԚLniZ4퍾khW2y!qO޳_bS.)}&d鈹-C A̠Op9~rve-Lz8k:]T6lrC[sA1,FQ&J1>̖w CS+cۊTEXdAޑۚ|zeyO'k𥲩IOx eòV- xt*)[t$ܚV+HV S.4)VsqZw> ޢv$g2bt5 sGY=cLP.}$]46rLD(Mg8baKm'b"V~7CO1V9YG~k+dR!N5l6iN%̓k`0xP/eS4fnEUKPҗv`zCϳ,.rXv^Ggba@Anѿ>mú9 5\tiu(_tWkf )&QB#GYv@A^! _8( MâxzMnƱZPdv$'W;mDU-0La@(Ss ywuI%3r;,TA^sG~Ҝ#Jkռשx&IX$VvoV1w$ o=0t_yݯ(r:c- 3'̞5d@d !8 !K)У֠ /kiWZS6We0~ ŝw2KyLf^fmnj g+T^SaILWV ICm>hJٹSP᠈XQg_ MJbQB>Yd y]beh >|A,+_lyI'#:?+~ /lU6l0 A,JBXGMo-E: j'Z_* +X{fCˆ;~);gcoQwo/h(Nu:+9Y;l>eoWq _~WU *¸Ec1 UZGYu2:` e݇ѵgsA=1#% #]r.l> ƣxYvzL($a <6rtמ,yK^sߋl;6S;bWaŵ=tlz>V:+"niu~ckrncL$ ]8w~Z6[2; # Ѣ}yѺ&mczڜP69M[=ٕv6D% cH,9NxGx> tּLϤOhVS&[[-~6`h>kMS>Č~/ꂷkuڤ_[^`Ċ_E(.yиꇹU]Ye 9~͸7PzlQxeedϛ1&U1+NКOPͷ]/'.\3?[A9C(FKj8=Zkmr>BePרL BHmn6MܿA֘h^Fƛ7 Ypd.MG2t,PN-:g%YܡR0_DaK˯3*;W=lcM닟;^7lں;fṚ 0Ї)xfZi uV$    h_>/ f`ϖ`U>zBEinj䘗q^H<tݶȒҡ#o6FAݶz)vUlf < @"4_l wm"bŢUi8YצmjD21cCC~^\Z6#2 vj~ QMByHHHHH,tpL6߲JŖA6u@aMzRAҪMd E2ZW!0_aj     n /fHHHHHH l7I3 @{'@KO$@$@$@$@$@VrXM     h5:p;i^y^=]TEO̢Za0ЩS'5c+o2ld)okz3GY[7d2Gl6lknYhN8T`n;{M) *~6S=oyy9ѱwQ&rU'"Fh$m#1Y P 7dfno ) MQ?wyPuG$9>RMRھJ )r'~=(kmR&[k6,4_-kr{R1uرIʢ\5 FfRz@b&!@Yk̤ 4L6a4,xʗUT ?F /ƮsgIea2vmw4evnRz rYA+o2pVomޔۏ6 ֮Y P֚/3ood菀lFkyh#0$- U.y lHHHHHH)xr lSA$@$@$@$@$@$6;b@$@$@$@$@$@$6EA$@$@$@$@$@$6;b@$@$@$@$@$@$6EA$@$@$@$@$@$6;b@$@$@$@$@$@$6EA$@$@$@$@$@$6;b@$8%HXr N|׸lSOÚlԉ-kR<&   h*@{$`J߂Ud]1IHHHZQM֒H=0DFN9{%J݂rTpx`@TދE?b?&Q9nyG< WF#n/$@$@$@$@>BX @>Rw@gOFSI>ΊY5?y92j>%ʫ?güyS;kW Z`}F$@(bU-< StGUqB4+m+s~ DM1T{kqK"pbO:V}aڥZ {6NAT/#HD+kE     !@ VH@|g¡"1)VbզVWm( oj`>5}CI??]a)!uJdqnu'IHHHJqY 7k%lsLzѶQpDt~F R>2wpHꨟGoj31պUEGqҀĸ-+7ye"XxH$)G@IDATHHZI YU;*A]#&&b=gو~FGE50e4jkkrfgbjc8n ֏HHHH\VQOZ D~ o0W q(jj.G'K`*]~UWJF"   h̄ l=A1 gJ$@$@$@$@$@$#ȍ`5HHHHHH *>r#X       ë$@$@$@$@$@$@>B VHHHHHH=** #7      pO {>J$@$@$@$@$@$#ȍ`5HHHHHH *>r#X       ë$@$@$@$@$@$@>B VHHHHHH=** #7      pO {>J$@$@$@$@$@$#ȍ`5HHHHHH *>r#X       ë$@$@$@$@$@$@>B VHHHHHH=** #7      pO {>J$@$@$@$@$@$#ȍ`5HHHHHHwWIHHHq|Ͽ"ǟ.5_a̙H(/CH`݋F[ \VQOmul x|~ V"`kw[o++ t p[1o\.ePx)=|;e 4Y:'=꾹8ͧ$@$@ f]:B+R5Oa;LEnE4LC ~cbF`yNzrMgTyoHLŻlRG'\TۓGSf׽| ϹkTSb%ޝp;6YuO4N$*>i(s~$yMdÈg8j\r;&> yNT;Wf>?bW#SUCY^a]H"Ϫӟ|"nD_8g`Ȭ>TQQhyO_9OؘJ;:wb0jZLO#pزx*!BQ: ,r˫ $b-cj-|^b&^L5yD$\ʾɋqN h5Z`Ny$Om%#5I( kxe+n EYy$?qݍҏXbJd-= /Ȗx|\D 7ag՚W `EH8߶5\#uyo_s!Q^Gr5AA]q>?(صa3Olf+ûW\?! V{h?՛ UawVXҎ+[RMyt<#R.a2i96/Ǯ%e(\8D ۷ -#b坰yQٹÚau 7-i'vce!7rȫU OR|$̶UdгV6#e@)xo<6Zjk+?bU2}b<*|ِ!a&nЭ.7W-Ɔ{ LZK[L wd'+Vڥjq,՘DގV88Chy1/C+i-+*/>,]V+}E*zaaO[ Ù?B;0n=d#Ë]f9~(p,О<''W{|簞2Z]c!~duģ6lb  _H@3֝1]A<Õe!,)FQ .[AYm~6ڃͯ>w[Բ\m ?oa9Flޖfy .׶F aB?-e8T(3ИhT+1Jfwe+F^\qVƝތqȒxe8ybzBaxe`ͻ؞2>Z}X}ﱕkZ}OT6tzҢi >aC&_Ԅ\*q%߼+_+ba~堲؄1cWB!/J thy].Hh^5kVW¾3@,_gmןGTx15]&bWqZ/IZO?NڊX %?2(ύe?EI&'5 bWf&,6J0W;*ρm'm&%֞C ў5U>z>y*yv.Dw@-Y^CIam>vf  6H"щ_ l:}FIC be'b(p"Qx<[^ ;LS_*n_LHQ>K=|5Q2}s{S-u EhçpKe3,z-Q_ű7(F3L\E+oEMڶ1=eV^fݟK]s,K,C_6VMZbƫ;sS Jܵ+"b'aFlL6p\OÍ*&8+uU7<K>B'@ؕ(Dۇ8ٲrݕUe<+~* iiֆ7ᡩP2^=-<CCD$eܳJT$@-Jzsz_!wN 'IĴcs60^\Կm7 VM66'&޵Y'_“/gHrsW5S.CG"C||HȨAyNL(÷FVDђsB /УFt4zU ~k`-5+l!W-6Iރ/l 2E[RJhג4^><+(9^ʲA{P:U-k7* ׃Ud[Vq4FZt:C>iyexuf>ݞ\DkMģpaxHIk8\z øiRkJY_ -;{/Y:w9[P풥Bޙ<}S'M(,sg1u[gJmUxre MdY82vniDEb(Ou 螒͒^٧ڪ}|ҶVO5m YDqX)5Q>wX~eGe?ARdfK[ߤ6X!lncA9hͣ^6!Ю=$~.~BAZuW⹝bqWө8l|EٖEP+Y{*;ew_lKވ=48lx,c&/{ ӴWreR"tO+j%mY0{Sf~TO]mEҥD 34.{ ]&^4!PfE$@uPn[ UT7T)3f))H\g(Y,ye0+lѧ6o&Eh3IQbu\ـȶ$Db96I|7q.5 V%)abCPʋ2LAM4~6Z+Wg-}l$eV]q~u[*:T܄!8Y&\)'k|BEfoÓ$'=gy'{b 1}<ŷ7A2YلsfwJ?!@)KĢpEt~q ph-nw/];kxxys-CἝ^/CH`݋jO  F⇟IHHHH#}oxGc{Suߠ1}O; @= JʋPpQ^[ϧ꾹-] O$@@R` aZ2l3?kr_mB=NxF(2co)\Gю@&&L@t1KU ^Lu/SlFj! 2zf֪lF븉 X8s=yjԝ ێ ?kѰ< l=ʇYZun\ ˇ }FAIܨ[!mRۅRi_gPu|Eھ_з%@\o yqT̘ L0cF!5w )CCIerTnXc#8R?59/vh526BbQK]7bw~$I]zt຀ʛH7bUV[Q~^ wϠ2T&9=ֿ[!4NKĻ05 uY O. <'qt *-A݀o3oHx=X܄W_GG@lDŖF4Vz{AreK&fZӞ7Q`ݛy(_=a61Qk_ģVCևHMcCu" ɛc)0a4XNcˑB׍|TmҴ}O+ԴJYTR _QWP,;Zm&.J]v#uL6̈"ueݜMEM=_pe jD #߉ vc4|FtۦLєxf(?TWv.J~Frm HY]׉uҸ S6kI"x8S,-?ISs6"$i`w'\4 * NpbN*VT_{)=} Ma_4uzFXau҂ryE>}v&&Y5y~uM~M@^#O˪ FDˀ_Yv"^oi3Xoc!& e ScXgIHFOe HyEiR>Y &#q3h϶g[<;W[5 5^zQl8w?HH> }.8dշyM]򐶈i\F(m3U\u꺂mp)|wnBaz$z#(ωAw"@b{V"p+&s&ǫG4s {zD4iG!' { wR|4iqLMRij;v#aa]M)r#5 o }MG3j?RpY_7N-D`o]#*Vx#c[Uf̒Sن=h0X ]](,&H5"աfsRnBVua0~="P^aSv0ҁc!"CCI4X֡]@eG!OZ%/Z]E/u(Y't>RyqcZ1+F&JR`:^ >, G3Lvd={Hm4w̗;;_E+ >o' ǏcoQX !x,xđ z//੻,QABKNo^+N5py3#l@4lמ~ʍVK{,eswҶ&u0gHH> hПve++^\4(@zIzrWVN;. "֌]ȎZ_]qZ6Y;a3/Aǟx &"}$+ުȨey@.|:,@%޽hQ9;qQ=X+`ϫF'`:$Eq7P( D(킕]߄ AlK5ZazuycU*{=D܃=t!k*)y*WLnDr@ng; 2AzK-:gByo߹q(9Oˊ=ʕkFi v>}J-ZẪ 5Ea_B|i%c;aE"W𯿹d[~#UvrE+Ur w ["TϊArq$k9?{f$m eɏF3W7RW gybG+h︂ Vmr<+0c?ʸR洧o bm_gLc°AO>wfbm&2?+YUn4WyWvR!  1HAѩ+"_ڄ"DVR( P?ruԼU|,KP~Q}^&^D{ ڼ DIA\ 8TI5V2=Se>c>- chQ ajx'<.[Re 2vhJT=, b@ ~dQCi?y_:D"2o+pI5nb6۹pN 5a0NK}Zbz}˗hdB)`ݷ5-{yubZ';BGf7`fz&y^g@xJUEÝ&L,Z(Vlo$G4BmNV6@Q,hcnt^^j:p. ٻ`mz-h{5X U-ޞrizVk9XMKQ'*NmqLuW> ? cBkN| Ɂ70`?}ބJ3Y|%ٱO;r-'E[1 }K>ͳ=_1ĮF{#ӑKwGY~/KўoO^9)^a_JϽf@!ȓD\Tԍ"_ʗPK!9ۤa@ſ:;.d_qֺDǟFS}yu:gg5}X%ޞǡW^!`fFXUkuvv&Ϛ`J,0kӐ$n`YRO(/c/ĒCWpXכ[a}X0Ί%R|~"'_~JDA$@$0dcC;93`ʑa\LFouZ {0#Դu jĔY)Cԭ^#u"Bڏi4Rkؑl9t!Wj&%2GçvĜLAlT$ oz)C MiohiqҦ&ln$r Ү-h/}I G#yCzz[Nj٬q&2D-rH;ȠV9=TR9I)M[1CVyڎvZ9Yr(K&]3Az䠤["=Q]U]b"sВv:|FLã0i  p3,9a3n1S,9y Z[])Y?ߠ//H&pU$KW^",M@say2u?@VY  }뺌whc=ҍ`r7}yh3R*$Z1y+ĢwFTco}{S?S '0&4"߈D;[qhU6iaMbt2*Cr[m;kjW%Da [- O,~%N r8F9; [V][q'M"ʐn@ڏ={X*g)Ws@4.,c\+w jsT͂KPQDX;]/tatDP\T:ChVMA+(xg2%nN&Lj²|$D r֋LLd$U$<ԚD#{}I}g җ¦d@t% +_\2,mt/i.K$okp`Oho|El3DCՃ 3(PUV*$x^PL3rXuDao~"ޖCJRwWqoiPjE-0w k{Z o u}le\yQ4?Uoq3ZԃFtFG,^ c_ov<=E4: (q"j2.м2 ./l(y^tUrOHZ0D}7$oLϧ[4=H~i 4Z8 j~ꣅݕ2F:/$\$w(rI6I8|﷞$1EܻG2x {&La`g,Q㦱W3WQPWN-8B~t`(! 7u6'!*Ő !0&ؑo7Y=հ]Ht$@$@$@$p5qR(5a͂   z'9tHHH~ &v4Xj^W$@$Л`{c7   :9d7{&A6lR{^bzX g`sm$@$@$@$@$@$0#l P7"     1 !HHHHHHG1 @ P 1pG$@$@$@$@$@$0<`ǍHHHHHHBLl8      ;3FYKpZ#c<W-D¼9,N3ѡ+a8Sy( "{_7Ü}`OGҼ޸}ѯ0kxiܿ 35 F`JK݂'s|--aپN-5/906ۡذ EUh8.u9ވ(_'u9b{=p*o&"=p~{ ѡ˿36"7.bC$^X"tdA%6' !dӄwwFɓ8P떠nŷxW^N(-H}g{r ,*ه:4vMT҈F܊_]l{(M5gGKeq$@$0 يܲ&$o>o dhBk`m;/GJO\ʣMТ&_r D]YOY^}TR _QWPx;ZBӌ&.J]v#uL"̈"ueݜMEM=~;3-QTDL} QߍDĴHD F+75it噭>kB+|4׆*pM^WjhKWl qBX5\g;_&sIa@w'\>3CNk *ףnRW >-(YmSڼ}!}h)arfoqU|j<ǁzo;3]NP^)UoaV0ԵM^WC[| @,kIkY5%[ :%A WUnXVQ0N6"<]Uu3^K{[uB>TcGm8TBQc)f W&d4"TVf&c7Jz9 ϜǑ 1HӾCG:iss4$ N!륨` Dxi\F4mmhZ:JumSz~wnBaz +BR jE#Z)B>eRˊJh^Zht5; /s~>WȪ\ir]PvrUZ'a Gi‘M3PZ).]#܃΋_tշdPyqʾ$R/YegI+6/DaU0j-e.¢1۳iɝҊB1 vMX|="h_뢏e/ދƋH?X6il'M/ =/Lw#>}/-%nwM>{pb+>9,ڋ/Pj)>ں* %ƎO]H<#.d,ߟ8u/ ܰHxcL(2dpU<4(͊eD> aNCR1{wޫj,x+=Uh>~{bxSOoSe#b9REhuL1SO%^S ^T jZS-]/^zchW Ow?jd;IHK`lFGQ BUʄWV.0@+O"W*R:a︀X3 偿|ccDj/Ӣflͼr9=ᣢ4"Uڧx_LD I([XExrmbk]퐫ۮۢ Ca],2l.GFji WD+NAKn:np<+EǑ,Ag w:U&Ӕw,|l-·g- ,(aR)k&JvCJ3Xd[&!"f15V !ZMtgf fȃ'nt)^%8X?f?p%.r7=y?̻$s#L|Os\/M6=Bh%*,tho^]U5gP:xI h WGns/=G7Z[]il9}.løKt{/b1z[׋z Lș ( YΈeOСM=7$ROZ*lS#R$@$@c` ӟȉDP`9ތ8EKرO;r-'e:i`}JriM>!];}tw%$ g!x 'Iʁ)}̇\jGyqL_DDP)> Ц"1oQRAqP+w 0/-2j NK &cJL5vɻp1EGׅE\M X'^kţMW>e= ZNdFhg@IDATPbB#Ƿx $Q `Z>R㟂1,%@Dz@hޓP9h,4Am(1-ՠLD5(.-CgZ!o6.} N\RkQD ^q'<#oOrXSLO:]Ǖmyk2D鲎hwbNjvbo yv@$ײGQPղ;J4?2.j_8vGkN '.'m:QZLe ~+uCS@i^{rD?+aK3D]4ng'*9?b+e9lK?i@\J$U?'Ȓ}aGjA%uUZ[4|< 6REK;L(Z^E"qu(GKZC&yY1 V9M+@ђAcf{2}Ԟ/#"f\.j*0a~ ^_苛.C%4GQrP& e1(X$ )ZX-XKO&SWQ}Ct O9ywWg=gc'uw!4T+֦ *~@婟- ujUHGטh{weu^g{޾RDwEM@ވ_5D+aь%Fȩ%K1 2rO۫4 Y#\q8woclhs6;ufj)\ bAT[] w2$?cglҷYv~sēIF?4gBFG?t ' 1}_B[ViZhN5⃭& +!OwT]]9-f'މܢ1DJMl]=Rr aAmUנ09h]cz+sCWaW!ZDL-^^$[4`wmO(=HD߮KU,h7S\:X63+׼c-R!j?}ܟ @&'       RHHHHHH$@$@$@$@$@$@!@64Y HHHHHH 4(3K!     x@` pf)$@$@$@$@$@$@Hdr      ,HHHHHH P}@LN$@$@$@$@$@$`CÙ <  IHHHHHBClh8      $@29 @h<bX %:9ˎz|^Ix̝CL7   M౯7㻉l Hq勳V-X \+5lgɽuⶣ@|~n4,O[E0K\_JkOzl.+~ѺۅRkh \7Wk23 $@&X6hck\d0;Ab;eD/z$*ay%̛Vzr\o x><+7)ۗFoƯˈnjHH]cf2ʚSa4qDݰFl}9r(+F֎vU!q{Qn)7Qg.f"*tI58Q G;*)B&XZ VH؈I'hW K9NOXh׊"pcXQ\y)c|d|7<%p10zD28~%3 1'}IrD452iqސhXԞ{ -B$@$0& "&pD0M}~ muՍ|Tm4!2}+%Z|[PPQ Kq'|EN\׼ /h9MOcvU QfF˔H$,C4o*hspL@6!)ԧHDLDRڈl"{S& \&¿ϜҔf8K&-jhKWl7st㄰jۯi8y~؜ORw'\>37Nk *ףnRWc_Յ C[|USo QTRlE],LVdrNHڢikD(6>U\nA oG˙ 0-VqGɸjDĬDd䘀Zh S6}t]8pu/똻.<{5vCobe}gDj,C[:YEA38`E¼=icf-KEKY=:ՁM˝+kK$@$N!% r%iUHnLs擨|UQס -+hȇx&5z#(ωAw"@b~_oK+N\M}9OߋHdUN]4hq|PX[䅣d dl؅jM7v䨪o:/—V,'yqXr<%%,8,i(_F EXT i{Z}rp_ OzG4\W1g=Ԯ`db'$h ]}/S^D+J*Z(wZ,DK(Y'>_xA 3V4!ʉܜ||YHdDrXVzҫ|8ŠrÛx?@2>UNH5"O1}]; F:jܕמ~&ϽHesw)ܣ_6ܥe$@$@#06LgDjf %@&H 0Z)gw\@D绐#LVWxqMFK'73(,iey@.|u97̇/p•\k\KRo\v-hbbh3 0as#rus_-8w-O˳%B$G^"K|cV|&OSQF}b!pB̮0k%qEʼ2- pFo7wB '&vK[Ty4=g *sE| ,(?E)b}5\=+xፙ};I]ȷ}j"^xԧ嶠(a'Z,@_+I,yVl4Nsm͈CoqBL{sܔ~ˊYŽQ_l}͕-L dQWEyVYeW^|Б gcB둙SWE {+`h@A5o"uj4K_0SE%A)Zoy~aaF-LoqUH;eRנ5GT͇%M"b Q(m{O>R]&JvCJ3XP.[E+9`k4x~_4U0c@.Tѵ+WR#o@DGLwYS=% ¯2V:3 Uߎ7?I`ZV-`=}Re"cm}r`pxk \[]Ws!Az ˨%,MQ| EYŵ X!z^Rv4q7 Y?jl .UcG X$0&XD(rٻsq6jԾ{ڑk4&:؝"1o3ff@jNb仿VWkHR92 ߟE{Ӓq?]0Tt# K?ā|,Iq0S`R\k&.y4>Շ?Od-p$#'b0 RKE>?ՏeЄ'?}qCm!wz|jR'Bh8Ƕ mA);N8D Sb?G0]k]{:fe}CEO}Ww \# Ƅq(ߞ[UT.n~mq☯ OjdK_ ΪLHn9Df3D4>fTaMr0Q> t!WjfE zthw_~Y;1gv|~7S<;@+".QPhUzW$\w fqϨ}JhDlN 'EҦQzLگi(;r.qC ڡHkaOngEx6$;Ld)rA$u;;Q@d\-'LѲWDO vQ~ϭ(_ ֮/ptI_Qߌ9e[sv[V1l2Y.9%g?$0 8>ٌyro}$ yrS+ 'H˓XbY2] u Yj~쉗L(z2YܽM;$$/TʩqWr+mkH6߁sͶ<oCMSb1HH`|= x~#~>AErbץ˫l ЭOS.2*吋\9 e"P* %z?oY'ȡǛ֠ |[T;y&J6UzD$LnE[u=oiJlSߌ=zXv6cDhfw^o/IRw 7sLzRcr>2L<VojLD."*j=R/߈BlCꕐulbIT[] w2$?su_h}%wHhg:<訶Б o}_kMt*2 W z D;=C>m5xeh^z)~wQu q.% ;[4=y]=4SM)?ް6kPZ eT!h]s{dR~_ziq}_[ej#(Cq5 )72VȫlfzU{D8 GJχR`0Ӿ5xc㐅X94G_FHHH N7n_gC`g?=ky1 '0aXνg- K`({ Լ>˚ 0a؇y Wqy7]vw~ze,~IP Ԟ^HH`|;#     qC`lFgfCHHHHHHKp1 @H P )nF$@$@$@$@$@$0\`KHHHHHHBJlHq0      ;\rLG$@$@$@$@$@$R`C c:      R,HHHHHH`(ӑ ؐfa$@$@$@$@$@$@%@v䘎HHHHHH (7 #     . %t$@$@$@$@$@$@!%@6Y p P.9#     ) !HHHHHHKp1 @H P )nF$@$@$@$@$@$0\`KHHHHHHBJlHq0      x| HHH<.;_|`ղc'";n}☞HH`xoO$@$@$0 c#|qOͽlp㵹? w  D`L︡@uq9j AG$@!%g{B\ C~I\ktЩ?+S4X scJuGy.$X>x/J &?ArFݩ( ˓p ӅaNPm[H.cJ h{ouي>eCfI`pvfx&cOVq pwʸ_g<3p;i+uߨ;v'iχET|]1Ҽ޸}iX\q>/JD$@$@c`EH݂'s|--aپX8Kۡذ EUh8.u9ވ(_'u9b{=p*o&"=dz[-ҶhBoCjBu25a%O?#X0"O"֣64܃ck{x=b݌C3g?=L]KO8Zuu7 ^Zd1??^svO% 'ƆlEnY77Ra2h4!5֗#;h.G&&4r D]Y %ZՇڏnAAELdU[+pjPkGˡmz$TSK֎vnoDqS.S" Sмȣi_pe j`&ڶ=HaaFGr2&-/N+򵥠&d@7mnǁue)M]bjQY]z3qV DKRwֿꋧ]'V]. Gku_>2L KL/M sՆuMw(_E@]$˪y(~Tqj"mSVqlԵI~I7w7i)Z#5>J{"f%"#GF1r JVx+j\VtKisׅ|>1Dƴ6wxpY1? J5`wr H܈&HΫB6qwBٶl˒gCreĒCVO"u`Hss$a$@$@c`7CKQ}4iFWEH*$O7BimZVЅTUocOpCt s`Ղ$z#(ωh"@b{V"pM/wp77hBωWO8 D;du)gBVua/R",*kMD:GCϋm2jR8^"5^d^mGݎz$8SаI[nr0}My D,d _USEY(»uP6a +Q x=l]aȚH3:Qh0Uk=a;USHxcR%2dp o颥񢴩q0[gsSZ)ޚH+k]Dn{߿B} G|YHdD2Vzҫ|8ňưx?Z移PR- SˑjD-BX3z+*%UJ|гeғ XpqAگQ6Y|ygۈneɖ2.Ms;i#.{HYJ0nY*gDާyk[(f/Vr}kd @œ*K]bE!y*tZE ʰu1) G~n-FgDBi%ϘLщ0?43"phڥ̯nT2΀p<L2".j ?߷N[#mD⍨͏Av]0W D*yӑ$`wrPI,D9۱ڸ?ߕaeܾyk, "XYvKӳ`umEġ7:cޓʪ"`F$ f`D8rn,m- Xl}>Qim(ľ|MU9#+y>1  1C`lAtsȗ6ao!",NʩßPuԼU|,KP~>!Xei (<=[mb@XQLhBLΈjn%C9>U+5a-B X]}LV?0쯓op-ڗжS?M5QUb zuAp~jLj5$m͢$RM ` z{bEyQX^̒붩h/C ,9eqk VqvyGS= ]R|s~, \"X1Lm{XIH-FF)P<fX;o_5ӊMM#J2C#S-ӎ,FFa/<d19 064P=61ʒ]pɫZېljcn}Cr~Yרʾ@Y!vZu"k&x}fTaM;2-9.DJ1?e>!S>W_N8>)H .ێt1_~;)S4vyϟӟLIKpg"C4yaƺ|Ke z O#@1g=^ D{ZB? ^['*sԵl?lD;nWDkIeWA}s zЦvK=lzo'X P)yWS8njElQb^& 'Tm\e Yrp]rܽ }d@Čg/c\q.*m,޼arzFrcPBb(S>uXVjF2jqhj|;ߛ$@$@Έx~#~>lAơ-IW[lyY2*CrF[拎X(q]=ݟ7jPZj~a*j`2y&J6UzىH܊z8ފ՞=r*=|Y;H1"5HlD"zt3~2T 3rqD W2R J(HkP[2ɁH/D/CT5Sc?>SF9ɇR_*~- 鿋b6AN>MKuZ}O^&GN7K8?Z;Ō_!wz,&6gMxɇ"lɐްwR/4qa 20 S$DjoRa>8p\e|}rދ##Qv=$"Ǖ.U3v, Q,1z1ҧFxX6*LχR2o?\b6vޕ2ZqP!j?:h|ɠqH$@$0> Iv$¶O CKF";A$@$@$pN7n_7`a͂$@$@`];AB  =ib`yb$@$ LdίL̛HHHHHH`P1̈HHHHHHa02o      #@vP2#      Iä˼IHHHHHFCɌHHHHHH& .&     1`G %3"     x(>L̛HHHHHH`P1̈HHHHHHa02o      #@vP2#      Iä˼IHHHHHFCɌHHHHHH&f̛HHH#pفsn|Յ}s[Mwcb)   I౯7l5 h+G\y3_ks2H$@$0I}ǭaa{rq׈)ߎQ D$vW`H3pOÚ?tL@$@$0>=6Eyڟٜ' _GL?{'5HUը;P~3WH ˓p&a8Z_OEr\ܝ2gN~_7W[n!oeV#k_g Vm>4Ru+¢H4N ~D;VÜ}Eޱ8IhoҼGxU-STz:  I`"n9ehh>l_ KQrkd{O nM XTR؝uR^!g.f"㍃8-^ Ia޽%=^_!s˟~lcv (94{6$lσ8u8ӥ(?};Fl]ݍ߄ CMh=c6_Tz #06Xg+r˚0vQLL}~ mH97Qy V]j])s}-CGV& WU=Tβ6=m납%kG;R78,R)HXY)hT4m'z82H^W𧻓 :(&i C[|*da6wㆵƛ&^›Lhd=<|mvZ=/Sg'$A@ܶ2j|y4nVѴ@ *|ץQ©"_UyA$0 g1eȝ-#m\[|"i-ðFF=I=Z߇yV<ό~>RA w{|`r Vn,AuAWfyu/Pv]uEoK* ~Fz*&M2H #2:>f9#yb3"bpt`D߈aǍJzml[0i\Y1uG-XQ(69Sg{өἕ77`9%gV,@#yxiM6Ȫ{aF@2g_<evې ov:'NJDgDQN<ڜ_Wb }be. s bFXz, wb?"PJC,{DIK0!1w(ɖI2% Z1 iŨPHniOJѼ.P,>YBJި  8>!ڥ1rȪjOsX[Qvjj 'f`C`.?*7F,qX+5¬lBvD,R J10 S˽ V1tE|ܜo8H<1L̇"a ucLb'B7t wNI!zQ8,:6dz\x_Qi{?&AH͖IF(a4TVD7>T<)]0c"{`|,|w INJ)'@i? vW LX3"4Ѐ]~d3Q:dbpHڛX?{OLc& 1"k^)NG^#Pw(}k"kDJ9gJߊ^IXYK D/٭n̄璁"jhQDa8O,sV"kX،'KoUAsm02EMȒE0&EV7V?(KуБ?U9pua$@$@G7Xmִ]4jv0O %-WWDϜG4^VYh{qs`u .gr{L?e9_|x ~*I" goҖI#8Z.E=~7-FGC "}WJu^:LZ&#ay>ڄ脕^K{4L%*V}%9P0ZoE^ Bdb yK(:1+6S Y xI?0*=k"wx%Ci{X=q'vZQD8Ȳfg2dlKY4oiHxlS}~^6Ll1Qħ䠃  sXkGҎKb>*{w1B`Q>=L娐SV7/CbA$)\O|MuDujqh TS) :9m"?B&"bhĿɩC659>, Vٿk͆9RZ`Cu9Zi^EƉ&;WI=&t$@$@ݏoh`c8T UꗎHH{qkdݬ6Z[yM؝r4t0ں]~muܡ#k Lgג\"˥M4urhD=NW%P֫QarSLT[ɯQZ=Htrؕ`["~K>P3Oo?GӐh4ڗZ^ xoQR qt# ,u㴾7^䨍pm k]ͼO}CB!G镺0  "3XWFkXk?]}],uvS-%L'&7Ĥ>~QչիQba2Yj,X{rmC] qhChIWUyFKjy-  e*(seeoes~ mO_LJ;3ay w4Ab8 ta>󰞮@u!-jKQNwf1䣣rȕqī5Llgd@yNۅԕqHHn@[5Լ8s@$@N X   /l^S]~P œA% (vf U֗HHHHHH(z)3$     ( ̓HHHHHH(z)3$     ( ̓HHHHHH(z)3$     ( ̓HHHHHH(z)3$     ( ̓HHHHHH(z)3$     ( ̓HHHHHH(z)3$     ( ̓HHHHHH(z)3$     ( ̓HHHHHH(z)3$     ( ̓HHHHHH(z)3$     ( ̓HHHHHH(z)3$     ( ̓HHHHHHzy=GfH$@$@$@ Pk'}3Dž׳ޏ#𓡱Z t=.^t4$  L՟/3^Z_Rku٘G~byC$@$}  b|Q;lٮtֱ$@݉֟5꒺SoU[[1>ܪzz\%~n7ᵹ\cS^s# |J5CQD}:`c[e[P2N[Y ˱1Mf|W s;:4Mҩ}ٰҼ78_?mI¸$@$@]dD'.Gy/%eY[_^O ƪRN!՞Gy6#r]q㍿b=(:xoGpJčMF'}0NMt>XsV|D`CJNWپ}R5u1kbFWLX13CmHfixjCi*rا7ؠ )M;ۚC$@$ k)Fbzs&"(@>aFiɇX1!X I[4$h57fI>n5,P~.yE)(?-Zv^)90כ9[QO\\_'[/?C`v}ǭu!H`DGH DܼHu߱kZ"֯>Gi=]4hksŊŒ$G% xOƑOߋ*7:D10O7S `ضal|Xz ㎝)iO2! b|B5P M4iS %d_]}j* 7.BnCHDIJwm`I<NJCkV AwL"@21%4;S3 )&7Pf!^6BMAnNg@BR(,ThJ]`F>|`9ChQ w[+M:T>=,kE8t eH.CHH`C2)?uO"'Wx" ="'+V3UO {4ucphoZ(_D,T俵mѡ- |HHZҏJQ(Q -Hc4ԢٸP,햾Y*!5#!.%ԇYQ8i{$.Xǯ܆ˆi(;3OR(FR&tgcp7|;f)LPB1{@2Om~#gkؕ PL/ETYoDϊuX63 a-y>jcp-t$@m#`~5O<$G#Wo-0&]'j!9Ps{y$O"dD"`V*ibbjjk?8K$%o=7^$k )z31%>.H_+eR",*w+|X|)3b:   V:[z V?N0[$ghCY$/CԔI(g" p"AEUSq\ZQS)"x6UYd-134a-|dZ ʉIb'hET-~])-I c{py6˹ٝ=+@,b=2)ňXf&C T]y9cTiZDۭpXe._?T?2 T&LVސ aJ6JHLeZXCE<{z>p#+VUC;+R\.ɿ@ߒ`d7dRz5~%j4 هhb] EV]ȷP!b6*vs}u}zs!% /p:mu҂7d9F 0GG0&]}n51J7G#_@嶉j§qM#"YnO'rt5 !f7|zPew ɑQ&/AO2rY駳jrLÞ[doAjdF5qI>DTBG'ekNnstU[%چ$}-P f-#H/VgoꈪChe+?Wj9lU<nQ-͍AXu گRjj)y7NKw(wN9 \.V4isw%-߳G6⤚ KG$@$@>}±ju*7Γ`:cR HߌPiMNɖ8Tv~ub~k8 dft2䊰4rPJoDTf6Ŋ!{D!{F%YS`pOt *Ŝ|(VaH'lZ/FXDX "]/Ӵ]&5{aʎ32['TiY>uEȁO" iʘ&8U49H*9 n| b yI+DΜR^#!SZ$c0DisP.@w&`> QХ2Xw@4Y?dW%̎V9:P͢u\XHG\ׯ ;}&b«&iㄌ|:'}UX5s7Az-P"Q6~Br+BVSIHY z|)?).V;?XyjmsLz2_ATB^a rx#gzחF1絉|iM5LL O>(΃pM#OJ'=wd~`SV7HMx>rĄH+FU cddč,qToWd+Âرcv֯+[.rf4w ?>wd=*N* w^" k.+;\0G;FbR9 / @K-VYbawc#'mb>CFΖkĞG#njg$H4s,\ ]>$5)IW߇ʁ{O6tɲ\INl)}K5.m-Ɇ"V>y:Ҝyr9LY+ j3^n _9K[PY{S=|;ӑ tw=.^t DKP'(_N:u2 /#3 ֡Uy5SV_^}#1Y&u:w;1IR_zZ4$3=L)ܙ+cUn{⊰з3g6Wz4qmh_ L@lW931s~>)n?oMt [MNgoO}cm<[- zfĥ6G/zS$ >|FHtQװ&ײT],uvcf&5S3O/«_o.Hz R/«gFn&y-PV ^UݒƍEkxR[>OҟH3ӉbLoTtFjoeeb Q꾕O n>;}?'_nu=cVbD |RGb=]^CZԖzNy xN'+E$@$e ?_g7Mlc Ҽ*BG$@$@B |$@$@$@2'>qc8.\G'6lR{^24[X: t:`;#aHHHHHH#i#     V(vƒ ώ5'     nElzl, . Xs     V(vƒ ώ5'     nElzl, . Xs     V(vƒ ώ5'     nElzl, . Xs     V(vƒ ώ5'     nElzl, .^[uּgΘQS-^٪ٳ'O;:|H$@$@$@$@]@/]jb{:^++?w??DM-u]åKa6Wa)Ķc G4!m!4Jx.n6k- @#@=["e64tM v[/ͬi}HHHHHBl[h1GjkG {L =B$@$@$@$@H8ScIHH r $@fe$@$@$@M}ǿUL>Ua   W4!vk  li+ت)ͫ8G7IHH@# 8w XQȍڐ;Qdk\ڍB+&i,dVR85혅M7v fKϐl:$ "[Kv ^k]YP^{kx+K_DG#b|"łM^kx܇'w^/ IH)2!>E zl(y{Mytu굢:wLً`H<2Y&Bpuc7OW CV߫gMG/j){Y`z?fk_0}qya>m-=Z˗/J;}װZ߿}Xmm|tZ:u߷o_-]seea !̋YG~*yN!*q%S;[Ujf$ <Ԟ_zQN$@$M dqÝc.VFaec:Jc+-09#۩$@d-J6a90_g0}1a=UTGqz2JN9E̽Hz qNB15 wT8,u'DO_գrfYqp3*뵢VlqAS[XBo=QT4+NƧU(ݔ= Q)yXUv G 埱pů/bطq lx6>~x D]%6$_˱{|{NgϞ& |P={v5#HcfÒʽ`* <[љ;Q{'j!nl9 G6#InDr0O-rQo% ?g_7l'Z}&g0.6 {z bFC.UmkX&e_̙l"k~ R~l@JBA~HNe,Db30;kGyTt&>K@^v /^Duu5}_$<)^$ghyOd& _؍(ܿC>̺ZEϊ5,`=+QyɈ݀Z5P)uHvDѮlMF*m7|l2 oVņ<6 KT =5pW!swZQ8i{;Ҁį܆lF@;_q0 6"a-'1{;mf&v!Z .ݻӁ8%Ƥcr#b,frpIy$MݢZMzoN :'EX| ƀj!q f[ϋ@ FT~cj+<`ŮtJ|$Vm_ KK9 ejmȚ?xb 12D8 wi 0NXIذ /-LlX 6Y4mrsb ]8"\׻Y\@gjϞZDFkD%QW/6]a~ùK*rcj?oDzq{mouKI]CPķ~; b~<nixC݇ U+rǪ㡈 sP)`&v8^iMYeը2| l#UHon\ˮ4ɿ@ߒ`d{ʾ85 ^;3!  o5_lcԏ!4XLI" 1}}AӖOĉpf;aoCjP.t2a%kem2 '7j` *}K 1rk0^+f@b9/CĬ>VE*iSD[+ 䯖6/ G53>D4vEbJ5'巖EC\iUY7ם`>>cw1h=-FCwJ05 P4S7]wݍ1ԩS:t( } j4ÙL4i"A؎=ZbkMm|ͩ5s%H@`{"`m04 >b;vƖt:(ݴSLYNFR:1VMMa#NcBZ 1!\)j93&7⬗SCQ:@"Z7u]}i-E8̜eϫEjحĔYʀ e喯"'M$Y]`!DO)t$L"f]xyl=e s* yH}D8M]C;J Εw׾?ֵYhmȓæ@y ħ7pE/{5('Ȧ_bh4~j-,e{Xr߆{Y$bevl~q۪~~ͯPvq|G!Lw}h֍ǟ'9Ϛۯ<3qʜXin^끁z&jEݐ*/uCNmiIs2VhI"/v$!-L,m䰽H;Dy$Ȅ1R5۶ԕqIHH# 4?,:_S5F(9VkqF8?OUkشZ0Kt*H<ők0G qr\MvN=H@LIG|4?G9x shիJy //sӅˆ7nP)u#aNT.avK)QtK,t ˤ GvN",OBԎpl-FHcP=(RN C.B z+zFo)U,tBI&5 (~6>Ҿ:ةYu?uFDD_:9ԟ7`CR$^9i. zIg찖q.P d`P!gg!_cPNiOG9A:(E9i_z9&% 6:m+@ xOV 4׏PXdR۞l/o͘Ls_OƘ>쯓_kTfnQKM{ oHHH@hPu5"y[+}v_qRX 2l:op to]W۽+[O$@$@@& wtƱ5$@$@$@$@$p PY$ @ Є̘HHHHHH{ H      vfLA$@$@$@$@$@$p PY$ @ Pm;3      (,HHHHHH(S `otI$@$@$@$@$@$v`Ό)HHHHHHn :$     h; mg$@$@$@$@$@$@[E @'a .p55իsٳ'O;|2 =.^td\IZYy ?}]?bԜRNͦgO?\xOb 0 UWW%sπ^;3WzE#     2VW:w/_ָ(>t$@$@$@$@$@v`?P{^;l6+.FhN$@$@$@$Х .x8  |5ؙ>9UA] @"@ lz tmJx=P \z 3Ado*v:  &@2  6l(4Jpm mj{  p#̈́T0|.GayUCcI4ZnՕrތ])'6Taqkbr?cfuh_їmۗHDv '̥߱UHnkSS:idJ6%86 Qh*31cGkcCNڎWK {rCOA'  쁭 ] !zΊw7 5a2 e)@_̋CEzHMo;{-’<<<iqu7JT ["qqMy,EͷH J|`?"."o0lbw_i"Ja;F9ݧQrޓYOJ?-kց~QL?Ɗ ~da0"LA,'$j,[&Y)1+>Sbc;JyKcECv"  NGgX}e6hL$J45P.EuHajAFdeu i_E kl]7.IlC(v3مВi|D~BlA&U𸩋P5)wb#H9Q:<^*3%DYY{PlG!ɴCqL xD%B|ik;bS9k'"sH:O Raݘg2JH}x"Ҳks ZbD$7GoD ʜ}]K(c>4@˻_mwoq߿ly'!::&n;pJK1.iwR*@곳`yD[rŶDɎ{w\dDn@TNd-M:D8oHHHbO JF5.<p8o-/D>&"'^TmJ-d@缶zJ%z ҉roCeRVݷ*hͻ'LKr"LYC2`Moo9y?]=.|~QK_A-`KdZeqEs\Lj+<`ŮtjRDLU(LCIJ=H&O"'2N hנ' _l_Gg܇g_z=0:oUJ@UZƮgY+.^ bɵ̑fb.|K34?i\I$Z5 sf'QV~!&" y :m;bWZ(D $C#J|V6C0{JT܈03ג<-ԀA$@$@$){C/*M=3LRS'k>!bիqHC). b%yfh#L1zoHkw,&NgBv@%S B7,,ň(SB1W6QzbCv zt(j~EbE3er=Rj7~\Ӓ{GEJ%^ƹ/#7ml ɶTMJ}?̚isY̐sJX>UcEӄھ}' }= _O|12 $ }&(~NZVUC;+b!dt*u,ʞqSGnȇߪE?/;GzƊm" @G)b}~BYf FtrhW?L`ĒwL?"֧_x0E)i,3ѭklw<䟐i'll*?kSޕS<4ӄ@[WŬSކK₭2FmGޢg"8icNCه:_VWR.>F>)sm/;{ 1b Km|y;Ѩ?Xѯ\DŽ vГe0 G}TgԩSaN)=pƶRU800={ԟ`S !AR}+۩Dx ۰dRl-نϹ'jau GD6V)}ƊfғHH:=+!02tT;"aM1o.N\ Yeb4x8FVSɾA2y 9(IfSEHxz%-r ڒ5VMm0%Yt7$,T5a^BӢxXV1ѣB|m/r:yC'RVEپVy_x6+Ϙ} *X756\5('?Q1yt8'R'[H|ne%LҨO!m(Z˩pOXq9G(..wy'm/0'%N4^@p}:mbZ|{Ƽ#%`璐)4ٮ`:"@ 0a_6iXmfV*IWaEdD݌ӲE߲A1eKJ_5Fy+ڗS @h~q巺$Ey[Hp V5O pC3ӇD&% 6:!I]>9Ti\=,lzͤ8Pf3nʡH<*7B"HUٓ d1/4'$B?r6.; Wﴛ}irΎi[?F/Sg u fs! $݉fPHZ]6qp  z\x˾fzyNclhuM<9gN<%g^)/IDATnV+m"m7mK&Ͷ0׌Z,[Vyu յfZ*&\3v䓏ŧ.kj ruǭyɰf~cP!젊T2~:ڸ߽v<2}/ip}lio}dN{nR   p!Рr*F•k Z,Oa] S׭(:qu(msn)5˖٩mjO:r͋$@݀~N/A7vo0՛s;9o @=[_c^ ,¯mM꯴O ğ$@$@$J+ ^ Tw !  ׇʻk7!.9HHZ ХM[h7: Tu/;Ņqg; A@ ֘gIb+HH:5x"ޓO_GqGb`IHHHHX7h/sj؎.3׳jk׸(>t$@$@$@$@$@v]5:|ȾA;9cFMͷzoT&R +ͫ^&ĬIHHHHPڒ @%@np     -`}y$@$@$@$@$@$m P '     "@ַkK$@$@$@$@$@ݖnp     -`}y$@$@$@$@$@$m P '     "@ַkK$@$@$@$@$@ݖnp     -`}y$@$@$@$@$@$m P '     "@ַkK$@$@$@$@$@ݖnp     -`}y$@$@$@$@$@$m P '     "@ַkK$@$@$@$@$@ݖnp     -|- &Bސ 4&&ĶZԧ3юu  LvxҼ6^U% #  k>㣈;ZuRf;Ƨ%t` 2 \b3ao=fP:vB :i4D6NYtV$ kooU.}BK%6K2纎}P}lRXsG$@$kKFtrƒ(*ڏSySYYVx"7"eU .{Bl=gX"l$bGaZR>(LUzv%c" [Dz%bń`y6'!soDi&OQ'uSPXx#TSPc#xdsR5e^)1FFČt,ג[E0KL ~MmA)[`h|9ajSNg>6lY4lݛD+jn2),ӥHѴMܼ͚ίBd,FYO'.`viC372_10 7OL[tӇmkƶx;ͧ5 Qj.bEaFTXf$X \O&X M[ӤM*t6"~=vqmN(ܸ!z%+a~Wߵ /[%P+b^<9`ռ`t7tE&oRH]?fwgxjBP_4r1w6YF8=#n:DgD]9[^$qȚ9f`L*wm}^['aBPqD!/C]Fߏ ɡ~'c՛"w}E%!vS֡Ġbfk#۱DK I/mU2js?ÞI:;'>3yg;]qS1Y-}泲"=Zcϫ;^oc&C;1q _6Y|{w2-E^݀Z5oˢuHVwW6&L 1kޓ t>>!B 1V=vCЎ̃H$>aF6dsan E=K5+Ak>vAxb|7v%Qqsh#0EGL h@m"oFٙ$߈}X1?4YQ"(Ǐ E`#RnIFڝM6e#jACl~$ O"xDɴK] `eSa&ҊB yCʢ7 kbF$ ɻ(;ШѼ%+Ƥ#kDJ9gJߊn#jzZ۩D lR{4ؖx( #c0{%aɢg#}KMm ±O= ]K5 z!jvT8O %-=xҞ]=S8|YI8`e`}6W:Ӻ}d\G fg WqJҕLn#RiHM EF4}=KAȷCt.7["wx%Ci{Xhu%fA2 JҘ]"kAL?2#lLG5z\9>R K;\[]N(l욯KkV0{u`SH] L ϊ`0{\J @aeDOkOcvOc|-˵\AU @:+ E㉭WRs2eP2ܪ&0\.ֻڜ (@ PNk^S 7Y! hjGj1(ꊱ"6M9(i1%n C֜H-^!w$E{:mkI2PI@?,1ؖDD)mφ0%Uu۔(A= `kэu.|cqhF1kH{]͏dE##0 ӑYyBt2yƬ_[ԖCuIޜsFcԉxHX!]4M$k= f3Sջ%XdF6 |Z(КtF~<'<^dږ'?]`uk]T750ׂG]rPr &Ali]jZˬsݬxDyr"9a2 l}u(@ ZvZmɘlֺs \]ZU˺N+EHWa 3@Ro &{ik*]:7\%)7)P%ҍ ,w bN.vJ7KoO,w#JZk˭ɻw+/D=~R P [oAG]<5dM,7Ol0o)"^;J^L=6?R\|9ۺ:zK(LMzIN o/n^:W.gnV'+X4xB>Afv$noIܭW z9(,Sl6ŸE'Ysy|kE415F, ł=̋&nX5޹eW-eE0z$Nc[VC xXR|J͖f3%I{&W.mkZJ !u:W8JYӍ. P{`IMEDL ^gұ0s F5W1%f&2>ǐ&<#6 C9H Kuâ[ =u\8?7 z\W'm.\vqYּp`=΅|C\{ SwAYY6?x klnݒڵcYEE<tv{* ÖP "fzJ%C<*3{璘3ŤZp|\d]/c8? ybƼtC]VXN(@w[z F:w珃!P>+w#x5_9tZZ.b,5G `lu]Ւ{ڒkұokuKkq;׾N΂rjFzϔ`? 3Yطa-D||MZNK7rEG}1~I[&T&놏GL6$W@bʬZ ?܆(UVyE-نBGh&\ndfҘFSZH4ϗ'ߚ-Jv6f|*?}oc_"5u_ޒ+]YX*u+eB/A˶h_C\ҹq/F|i:{T=mɓ'pV)H^ǵϟǹstL"mjjjpaTWu_(6 iH5 z>k3Oܾ-tC~hc_ (@?vِd1Kk.Oٛok8he\8!m`)@ P Dka- _ [[,s1n[(x &659+"vu92ߗuob:AېSmA+0M52PZִX ؼrlyK"~oͣ*F7n$t=eπoG 59Ik%:̅ݑ FP߳DR@{p<} e0;92CX >N{7X%1%JQ;:]keqx>, MnIGīX6AZ'YonI **%_0bREqX0֮2h:\: NS ,[7\޽`Ĉ8q[\xQԩ/?|ouu0_}e޻w())?N{qe k>ǻ VScܱǣ*f ;IX3kmiŹkbdvL޻K{EՋ(KSb6#*2WE膍(ӍHd0'fүR]Z+60RYuS JAM"2e:le{n S6=>j\"#%c%$b yYz >)& Bςό'A$iI.%36cK\?yɓfNEeߢxpuCZ$=9 0oo^}۰i? z >YQ~{b۷U:bbb cǎo\Q]УGď=}H DFFl/xt,(j0ckr(baے,JU܋7϶L4tחNO~M铵-C&,Ǻn/?rr?(@w>хX7rNx@c@S#+~F­kw;]4_O !Mtؙ9+ݱ.! J;-HMl*@PRl/ӤV;~p7ƂM͗_w$r_JC?O=3+Okwئ%ܑMw䜆@!A**=bt%]o[}~nz?kWZ޿xnx׮]Vׯ֍Xu=Vhz ["kq 7 " ǢpKGZV7sEbymTs d@+!@ǐPܨ Τ˹Hx\ `yP[`<.e*e%̍lNW"-X%MfDM~Nx;n,~=RRV`ftNUvOlNpնS;ٛ'\m4f96SndГp`Zۥf2ĪgϞCkVA H&Lg+2 ^s>d-QzdQa"F%Oe0]Nj`jAlPqCA.3B8QZ@g0׆1ˑ^?$!e[νX9B$U71{+.2'aW݁-[9:UzԮWۙغԿM7q{W;nm02jX͐DzIݲyw|<ږ3&ަ~)/0:5Dk(Pw蛜[Z1N^s@un}cVDD3?Ptu o͉X qߥFijmcӑYyPK\Ц\,u,s1zU+NG6tVDbv`,^p>ԦO(@$ЮZ:KdñGF v=tK9\!E,ݳY*̨uN$W笐@1Vi56nݾ鄭iKuOgskyyY3嗗>27@}F Ve7¨rۛ#9y8fYW~,zy ς7qcG˚\ (@ PYgZ`3m 96 $Sng :xS91qnP#75tڗ}[攄Dn݈#vI_(@6-H}acڴ̃^,Q{qY.a*vv>9,~(@ =.Y P|U`Pk߱^U닃כ(@gL PW 0`LH 2 Zka]<[JV\\]}ʇ(@ P(^{.@^|p %Cx2ٲ' P(@ PO>F} TT&wl>|nêU?rG(@ PU1(@ Pژť(@ P( `}c)@ P(@ 1mgq)@ P(@ X_o P(@ PmLlY\ P(@ P*W9(@ P@k IENDB`graphql-ruby-1.11.10/guides/queries/skylight_example.png000077500000000000000000000476211414121453000233140ustar00rootroot00000000000000PNG  IHDRCPLTEĒ‚quyϛsدÄKN.ŇŲԦb1|"g)ڷ:˯ҤqԎǍxۮҡƊÃܺɐBx|VП7˼ٲ޽͚l\Esw{׮邆S˔̗֫nԇ臊ٵ|٥ʒuy}󳴶옚ƾrvzɽtx|#iM0IDATxա 0 K ?7@P(Pp(@P(Pp(0ͪPa^Dw4p-]CJࠁ8>16ҏᱯ]g'$e ` h(4'h(AC6%-rL( R@CߋP}5T ZC2V+ߠ @CCC?ϼP*b+ @CCC7"b{u:|8m՟UDJlbhh~?aL$Rй̭ [TLe-՟UTDB> 63υigFDO2n<;47.-S̫LzN Wr͙WqM<+Ȣ".kQ9z9ҝHLb\އ~mseiM T!۽W=uji}grg9q0]8s$>  mb u(7'DK86z7Кc-lQ ^JsO]*|T~D6#ppCɯݜZvyk=Lβ~dqz"YGCcU\0?I E _A S &S(u Q$lTR`2kXd8_8}oCr}{zk+t p۾(-X8*  U7M-\1FwS2:A{;#ovSf%@T=_\C#b̞1N*7ġKXCG r}Q)g;p" *P^h$rزv1"c_] yr|MI?Ufzqn .aeO8ԓ};")ථRmӑFb x*lXS[ĺÍrMms]:eZ3z+8' }g8q .<{* >(}dGq|BЍG7sލF@|5'ݧa1#oЫ;1Kz{$9f==N* ġK(^i35ߏ4pwcS >L=;cڹݤdJbf9YoI111\f='E|CO]tZ&O?Wh0qV١ͯʢЪbwJ5Д[6{s 3IJ;g{8T׈CYQ24zSޚ9P"rQ-vC`*=9Q=q ?!]'94-ҧ Fo,ɡӝޡOòӻs,.q +Ӓuj8A1<5EˡEeG.wYoNP6vXԡCQqy?sLW{MӔձ 1 0Ryu/="pi 9ph$s($}I;yh/8-ʡC{/8 #@C`PPp(  ! C C:$ABl%8@ٵִ8ƏA0@FOBA"HPRzx І]gazkܓ'',>O<Q"""""""""""*̸^93NZ+NVJĆ&Dt2Dl"DĆl(ʮgQ=J %bC2r%0gX$d7 *R?lɏ %P6m Jo.gw"Q@n$#'<2E,ڍP"6 zGgo^E~ǭiKcGm`"SDl(:uh4Լ~ qyN&0VyJP"6 }q [ >mJCet;@)8P{qxP"6 }q E;ltH4mtR, Džf8T{^ l%Pٟ^|8tzvYu]C 8pu!Yir:uBsU5n>;/e ctΰ/h}zJĆPWCCHfmo MZ4/]t76g1;Z5w'qgJ]dV6tnラOgCP6:/ckiJ0Xe v z-w$Xܕ8F܏_0ޥVA:ʜxhC/ݫ=omck;;mոn꒯'# &9BJSϱDl(z Q26PuH`)mhVmI7܉5""n3 !иVր.Xoϒ_bMmΆ~g] 8>ȷYnU Y6O(\%A \͂Xl҅B !E}sf<w~l7oO޸cCГvWΗ E*\>wUH 9ԈFՎu6zJP067C74Ye67 [n1p&O(ߡҵ:JJCt.zbc uqRe\Q摉H#z+ߡ0A΂E/Zis1`zPɺ(´SEtw_q(2^uˣC CГv:Cb BƅVX5Ysa ;tϡyh)(SyuW.C6JW(LHi4~TuѡСt);Rqr\>sh]2gNCsu.bˡhj_I9ߡ_m}4(;URؑUCH ~nr CvhX{M;txȱ,vN~϶hY 9x^Ё* T@{3 Hy2 8 +r<~!t(9֟ /liIJ_x1mRyʡ9Cݼ"~rj;1p"W%h[kT^גGBҡácջZmnܡg6,t76Z&C1Uw5q;|ðF׭I;:#`ϗ+\][5KJC (9|ԏ ,²UA>D$#pqh(?8ۗufΫDkk+Bҡ¡pOExI%CP:4%DCP@Cgt(JJB}Ħ:Q%C Qt(!СtoDBd@agq(8;M W% i>OJ n P¡P % P¡P % `ٵ0塇P06МRM衐KN mM{R!\[B~K d00di]M}~]eӟJl(i[VθwѕaY3x[!J6Pꎿo(V~̶}ςĝĜbӜwx>ۍP<6DP5C,j jz_놬PRICṆ"2fR DmS=lջmkaᣦ~r)9[3ݍ}yl( `5>~_]Iz.#2Cm411zMf=3 `9>ڟ@Z;yc]?57ebe?hxaL1ٚz͚yfF[.uJ"lE ȫ"쪕nldq }?S(^SDf6]U8Yj->ۍP>6DP~K1U#~0iz ]_uyPHe!𻽶Tp徆fCs㣸3E)>"$†a~Z8iAZT'lO.to֛1 . ^r_P θS'SǶP{$<:kkh;+ԆjSMA42#(7df4,Ⱥbhv>U{v֔E[/[swkh?ƥP{,fDwRC%@ - y,7L댺,Hېin83&ՠBŸf_^)}ڣFCLPfDbTP C}edܪB>L-YMcX, 1>i:'?0tU3nnܪ43>C g7'C2$JkCCP`ŒS{u ]C VO<62k6Ce;owg7'C2$J)iznȀ;}a 5sP,CQ=}x5Cؘ9UkwmF8qLq _0:=* 1TPO gP9χB3mh5F CWzp ? )sjALqy#ˊ-Mtgj3mC}ɽ-R)C%@ TǏchL~! C r Pݪ}6t r6 _6+R7ݵa.*9I;{fa׌wSih[juv{Dbt zJ*,JmC vТlC>*mhcQfizTM'ur hWTl:Nk>D :w"1TP C/.7UvR{ ܑƓ~({//qcMC%Ԥü, *l'֚jVOuiTzΞP|bOga'C[O 1%TUwwtCr:MᾸuڅ9liZ]^ϖگ }kN83g7.L*_b^CW >oC2?з/YK.JO 1r -t3&ߗP U1TP ]x%̜m%Joݍ8~lqNgk==P_*Vbݚ[&J*ub.J7?Z$"1T򵨋O 0 @[~> >8 CC8J8 CC8J8 CC:Ȥ! .Ƕ࡮\Tþ&aBh Vrj'P HPRzIC/ޤgaufLJ5=<9~AWBADDDl(0)FO ͉k֟E۞YbCB \ļ#}M nh,PJÄ3>>pBvn!}E_?GouˆQe#^VX*;^Ctچ ^]=j3V]B} lg'dh::<;X ?%*aC˶p_ |b0/ ;yYء 4P9qfqүHcA@#Ob'S@eΟwѯB~@v4TϦsPƄʜ7'(WCJ'ݾ17ͨ9#hTC v(IBγy\If| ſЈjIi6PC̙$!gP+ibuY,4Hv ١̿3IBءu@IɟwGςʜĜ9NrMF!m D%I*}ۭw[%wBD.=ǡ|ܽdZ'ebz:ZݨeCvhCBu2 >8 ϼWU'yW] 4g*ۡIU|c ]1vңk,y p+T$j;{jI^F8KTCgS=~4ʰCk H k@_9UŋEZ 'v)Fs{{G"QbҮ):t-fy@O*mDY k;TqHe\\Xy)iteeomC"[UTr~;DZPˎ m T AYC{3CLs*]T^ϒgۻ\ҕM=k7Tm cPbCOWwZ}AV3V5t B[WOâM`C5+@,k;[12=m^ܳo(R$Wۼ ]C쾆PcC =MMwwtmYBP K'(\͑)w|kYPK]={~|| %6/h:~X,fi媻IL`o6wdsˤӆnf;ԗbH_}h|m"Q#63VzLMLs~\+Uܳ=؞[x֢z+B4t.56j}=iPbC62l(z_nF ^] cژʿVV#JJٙ*v#Jz_oÕ g|$l(ePzJ7cCcCl(e!:36-F *'Btfl(JDDņd ` o/N@ " Pp( C`p(D8 C ¡<ثc``<y`g N>` CsǺiQUɛ%"1X2Rw/,1   L cpMې2{*qfzBDĆ?-b[ԶpX}G[KRQEqP""CҰ XV"} J荋D/uOCn!4T,H ȴ-661vz|Q]דl(u ^n O74ko1[< 6c`Rd3X_}IFh$JD†>>}Na+*64K[B@,k6t"1^[3y8f1'P;uC $JD†>h]w4:Kjh-[BH>,g0-З1Oټz ݋hK4&\"6)d!nFvC/g  }7VōE+R53;kurs'Vw(uC!B1. }<[͆UCc_C[$JDÆ>p}iZ\tĆb>$@(o5j$gmUU55Js %NaCw]mq_ome%UCUbD;\^MD)l[lDY~\ 05:uzݽ萵^ nhs %NaCwǺiQE'œLɖ!@){|AZG @݄Y"4}Nflu+qʸJxuڪ#զa_xЫ^Uy0.۩@h#vU.:.snq}e`1')z܎Zn2b:xXRT}Ze 4^D l͆P7;vltv7qt 4ch =ظRZCmtڛpr[ζL^?V7vqVDͷFv. :oZl ,,Ūlq_eUոL[?Euq*GTSi4ʹPX   GPh dX?9EާOq44Ph A 0A򈁊IuTBs @1z(<PPx($PHCC R76kSJEo4RQߪLy(y(pR'x(ǽ,IBFAuN;)8y)o>8p)q~jytRvKU7tq@B:СdCWjbaI6FקARotz!n}U}%Bv;С J:46]wA`ZhСa-N|bث6y@5uݤy~sW 'M wlJQСdCo2:ž)Ln,ǫVqi*G{L|e’y=jRPd~6}c]? kw}3S3tʆOkAƇ\.K.C_@(iA AK!~xM< =r-~̎}X -v2ˌc_)ڡ\9t"2LvPaC-zLPUʅl 6S&u0֡˿.(rQXZ:r̆Vk'˲ZG⹾$~ruo2,'_ﻯsjꪺvŝ7;իS?:Xug I'N=.i^嫈GB2Zwdy xd3(<׾Zw'+Uxd! AQu7wC֮7O퉷z\_nm>{&u%uD!e¬V!l (B9|%Uu;Ds`rȡK(*_mqdV7~  r1 Gqvg_91;wTe( &8H ء!g/gd&n{`f ee΁yU}ۈj]~N*] wcwxԍ׀k=%T`m^\2nj[cn3(<׾[١Ks\ f&8N\$rC@ehXWwrۡ [֊S>P֡ӤTw WWh|BK[͝{N׵/_;Л?Q[?K#F>sxOMgVqk. m].y/ JXrC Td#@ic^Q+ƎڀXM$kf|֡7C-I99y!CΥM ZV tG 1Uj#!1 H3Ji9{>kaC-IWhvsDC =Wq&$ `Mk}Iњ)OrGqv #iÕ]}G9ơgREv.4u$N٣PVI:\u><||Bo)}OGqzt];=L]Cy ʦ(LqM2 ;Jٍ|֡ ֡dE -]Y)P˜ @pe9W.64;ttMGqvIm084Kɳ[c&rR|Vdv(e7:YZް&Q7,T]ZCƸ,$XP9G"&2BQ>m6U~6:cxf5ss|%S<P־D>Jٍ|֡7C-=qmZqM]v؊^쬄>|(w]ٚ]mTybwb8gQh\CW9&wInӵ(|DbvXsTᢖ8/g^Q9gjbj9MK,w禍CqBQC~C7`|iA@ 9L]5ws3(QCx r?y 0΍#xfDZqi Z. n9C5UF'IHÎB F*Jmb}s ,dq ό8ZKb^sKQQ8pArzXWĿ/i9{f{|=,C-'sbl7(^h:lYno]_| O2jхՁOAB}׼zϣ΍Gyc,fbUHWI;e,BAbDgb26nd?p; սI 5jܳm Qs*!Cj\<_dhO<;?֬z@lng"$ͪ+Rg,[ػ +Ti uw|2L۳ixiXO,9]!a*m"C@~ #zY7 ^#C@@ 5!C@@ 5!C@gQ8SI.p-$@mlVkAXH} Ɨz3uN[~?qgK l(у`CP""P""DDD7DDD %"z7jĆ}n =$6JD0DD_ JDb?6&ki?N"$kzYobC!< aLL+r3Ƣ1g+woja&*+L 6|;P"z ;8quil_h/Kى/qT2YHJD`ӓpjd^5YVRDۺ򺮷ˆne+y+[2-6 |#m'G=q^Tĸlr5؆͉hɆP" ŨR[FCL:bxPK41%ٿAi+ 糃.Ey,;TZբ,繴&2I64l(d0tiJO5t7r=hODk ]Re?pb0uy.xu>Ɇ %aC;0v /w([bM(]Un&hszlhP" IOZD^/ 74Ş K8dK0.0ZC2([hT3PbO`C((ڨ:d^/╷˸g\%PSM{V:w E[7k!`C(0$auQEo\xC'G -u^~m Wʽmr{|ɭrQpc;S*|Фs`, 꽡6;mQmckǑXlhP" & ]q~-Ӵv7,k[}?ou`L"ФkqI64l(huoK|- 0[b-^KΞw_5{;Ɇ %{P}X؈uDtk4;$FCKϢ`K1й lhP" 3&m G&nVrU XC mu\w@!jw[DzsYsI64l(>b!ߧx[kۭٴktREzs؆vasUD;`7ԟdCQ@ θwyGfCZC hmv^uC &DFɎ/kkl•,>uޖIQ'[xf*#{v߁59/a3u?5ԛ-!`C(,i]7|B[OuP짭~MҺf'0?٫ca `PJaepClá] Cwr(4{I,)ĩY u ]|^uՙy$PIw{&evaC6A,dg#{O6 :A):zOOׇjofr*6+3P" -,5z-# Xَo6E+yEQ DQ & ̤SDAnU{ӓ̤_ Ɔ-kh:"{qhh(B&3 nuD%5Bsr՞4r;'"U Nu71F:-FW`CDDj["̯HFaO}{2:S Z+8f_{Q+P"e adU}Ɔ%!pI]F&uCyl(Ѣv. ij b'f7t|ݫ tJPdžP"z<:JS.oL]4ɂ꽩ɔ l(=!>Cp ޏuCGg2 ZP71i*%P";&.7l-|4.<4'BtCu7=YJD4'-\b+LCsR ge6A8[I8TIԤT*5{6t 6h@扜1LƠnh3} UN)jorR= JD4gA4|rO3~%cCS .P*uոIS[' B266l( 6sȧ3oVp0lEѓӓZZU)bl(6ba 1V[i0p#1CԈ,LnHݎ@`.eIENDB`graphql-ruby-1.11.10/guides/queries/timeout.md000066400000000000000000000053361414121453000212370ustar00rootroot00000000000000--- title: Timeout layout: guide doc_stub: false search: true section: Queries desc: Cutting off GraphQL execution index: 5 --- You can apply a timeout to query execution with the `GraphQL::Schema::Timeout` plugin. For example: ```ruby class MySchema < GraphQL::Schema use GraphQL::Schema::Timeout, max_seconds: 2 end ``` After `max_seconds`, no new fields will be resolved. Instead, errors will be added to the `errors` key for fields that weren't resolved. __Note__ that this _does not interrupt_ field execution (doing so is [buggy](https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/)). If you're making external calls (eg, HTTP requests or database queries), make sure to use a library-specific timeout for that operation (eg, [Redis timeout](https://github.com/redis/redis-rb#timeouts), [Net::HTTP](https://ruby-doc.org/stdlib-2.4.1/libdoc/net/http/rdoc/Net/HTTP.html)'s `ssl_timeout`, `open_timeout`, and `read_timeout`). ### Custom Error Handling To log the error, provide a subclass of `GraphQL::Schema::Timeout` with an overridden `handle_timeout` method: ```ruby class MyTimeout < GraphQL::Schema::Timeout def handle_timeout(error, query) Rails.logger.warn("GraphQL Timeout: #{error.message}: #{query.query_string}") end end class MySchema < GraphQL::Schema use MyTimeout, max_seconds: 2 end ``` ### Customizing the Timeout Window To dynamically pick a timeout duration (or bypass it), override {{ "GraphQL::Schema::Timeout#max_seconds" | api_doc }} in your subclass. To bypass the timeout altogether, `max_seconds` can return `false`. For example: ```ruby class MyTimeout < GraphQL::Schema::Timeout # Allow 10s for an incoming mutation, but don't apply any timeout for an admin user. def max_seconds(query) if query.context[:current_user]&.admin? false elsif query.mutation? 10 else super end end end # ... class MySchema < GraphQL::Schema use MyTimeout, max_seconds: 5 end ``` ### Validation Queries can originate from a user, and may be crafted in a manner to take a long time to validate against the schema. It is possible to limit how many seconds the static validation rules are allowed to run before returning a validation timeout error. The default is no timeout. For example: ```ruby class MySchema < GraphQL::Schema validate_timeout 10 end ``` **Note:** This configuration uses Ruby's built-in `Timeout` API, which can interrupt IO calls mid-flight, resulting in [very weird bugs](https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/). None of GraphQL-Ruby's validators make IO calls but if you want to use this configuration and you have custom static validators that make IO calls, open an issue to discuss implementing this in an IO-safe way. graphql-ruby-1.11.10/guides/queries/tracing.md000066400000000000000000000130221414121453000211670ustar00rootroot00000000000000--- title: Tracing layout: guide doc_stub: false search: true section: Queries desc: Observation hooks for execution index: 11 --- {{ "GraphQL::Tracing" | api_doc }} provides a `.trace` hook to observe events from the GraphQL runtime. A tracer must implement `.trace`, for example: ```ruby class MyCustomTracer def self.trace(key, data) # do stuff with key & data yield end end ``` `.trace` is called with: - `key`: the event happening in the runtime - `data`: a hash of metadata about the event - `&block`: the event itself, it must be `yield`ed and the value must be returned To run a tracer for __every query__, add it to the schema with `tracer`: ```ruby # Run `MyCustomTracer` for all queries class MySchema < GraphQL::Schema tracer(MyCustomTracer) end ``` Or, to run a tracer for __one query only__, add it to `context:` as `tracers: [...]`, for example: ```ruby # Run `MyCustomTracer` for this query MySchema.execute(..., context: { tracers: [MyCustomTracer]}) ``` For a full list of events, see the {{ "GraphQL::Tracing" | api_doc }} API docs. ## ActiveSupport::Notifications You can emit events to `ActiveSupport::Notifications` with an experimental tracer, `ActiveSupportNotificationsTracing`. To enable it, install the tracer: ```ruby # Send execution events to ActiveSupport::Notifications class MySchema < GraphQL::Schema tracer(GraphQL::Tracing::ActiveSupportNotificationsTracing) end ``` ## Monitoring Several monitoring platforms are supported out-of-the box by GraphQL-Ruby (see platforms below). Leaf fields are _not_ monitored (to avoid high cardinality in the metrics service). Implementations are based on {{ "Tracing::PlatformTracing" | api_doc }}. ## AppOptics [AppOptics](https://appoptics.com/) instrumentation will be automatic starting with appoptics_apm-4.11.0.gem. For earlier gem versions please add appoptics_apm tracing as follows: ```ruby require 'appoptics_apm' class MySchema < GraphQL::Schema use(GraphQL::Tracing::AppOpticsTracing) end ```

## Appsignal To add [AppSignal](https://appsignal.com/) instrumentation: ```ruby class MySchema < GraphQL::Schema use(GraphQL::Tracing::AppsignalTracing) end ```
{{ "/queries/appsignal_example.png" | link_to_img:"appsignal monitoring" }}
## New Relic To add [New Relic](https://newrelic.com/) instrumentation: ```ruby class MySchema < GraphQL::Schema use(GraphQL::Tracing::NewRelicTracing) # Optional, use the operation name to set the new relic transaction name: # use(GraphQL::Tracing::NewRelicTracing, set_transaction_name: true) end ```
{{ "/queries/new_relic_example.png" | link_to_img:"new relic monitoring" }}
## Scout To add [Scout APM](https://scoutapp.com/) instrumentation: ```ruby class MySchema < GraphQL::Schema use(GraphQL::Tracing::ScoutTracing) end ```
{{ "/queries/scout_example.png" | link_to_img:"scout monitoring" }}
## Skylight To add [Skylight](https://www.skylight.io) instrumentation, you may either enable the [GraphQL probe](https://www.skylight.io/support/getting-more-from-skylight#graphql) or use [ActiveSupportNotificationsTracing](/queries/tracing.html#activesupportnotifications). ```ruby # config/application.rb config.skylight.probes << "graphql" ```
{{ "/queries/skylight_example.png" | link_to_img:"skylight monitoring" }}
GraphQL instrumentation for Skylight is available in versions >= 4.2.0. ## Datadog To add [Datadog](https://www.datadoghq.com) instrumentation: ```ruby class MySchema < GraphQL::Schema use(GraphQL::Tracing::DataDogTracing, options) end ``` You may provide `options` as a `Hash` with the following values: | Key | Description | Default | | --- | ----------- | ------- | | `analytics_enabled` | Enable analytics for spans. `true` for on, `nil` to defer to Datadog global setting, `false` for off. | `false` | | `analytics_sample_rate` | Rate which tracing data should be sampled for Datadog analytics. Must be a float between `0` and `1.0`. | `1.0` | | `service` | Service name used for `graphql` instrumentation | `'ruby-graphql'` | | `tracer` | `Datadog::Tracer` used to perform instrumentation. Usually you don't need to set this. | `Datadog.tracer` | For more details about Datadog's tracing API, check out the [Ruby documentation](https://github.com/DataDog/dd-trace-rb/blob/master/docs/GettingStarted.md) or the [APM documentation](https://docs.datadoghq.com/tracing/) for more product information. ## Prometheus To add [Prometheus](https://prometheus.io) instrumentation: ```ruby require 'prometheus_exporter/client' class MySchema < GraphQL::Schema use(GraphQL::Tracing::PrometheusTracing) end ``` The PrometheusExporter server must be run with a custom type collector that extends `GraphQL::Tracing::PrometheusTracing::GraphQLCollector`: ```ruby # lib/graphql_collector.rb require 'graphql/tracing' class GraphQLCollector < GraphQL::Tracing::PrometheusTracing::GraphQLCollector end ``` ```sh bundle exec prometheus_exporter -a lib/graphql_collector.rb ``` ## Statsd You can add Statsd instrumentation by initializing a statsd client and passing it to {{ "GraphQL::Tracing::StatsdTracing" | api_doc }}: ```ruby $statsd = Statsd.new 'localhost', 9125 # ... class MySchema < GraphQL::Schema use GraphQL::Tracing::StatsdTracing, statsd: $statsd end ``` Any Statsd client that implements `.time(name) { ... }` will work. graphql-ruby-1.11.10/guides/related_projects.md000066400000000000000000000133571414121453000214270ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: Related Projects section: Other desc: Code, blog posts and presentations about GraphQL Ruby --- Want to add something? Please open a pull request [on GitHub](https://github.com/rmosolgo/graphql-ruby)! ## Code - `graphql-ruby` + Rails demo ([src](https://github.com/rmosolgo/graphql-ruby-demo) / [heroku](https://graphql-ruby-demo.herokuapp.com)) - `graphql-ruby` + Sinatra demo ([src](https://github.com/robinjmurphy/ruby-graphql-server-example) / [heroku](https://ruby-graphql-server-example.herokuapp.com/)) - [`graphql-batch`](https://github.com/shopify/graphql-batch), a batched query execution strategy - [`graphql-cache`](https://github.com/stackshareio/graphql-cache), a resolver-level caching solution - [`graphql-libgraphqlparser`](https://github.com/rmosolgo/graphql-libgraphqlparser-ruby), bindings to [libgraphqlparser](https://github.com/graphql/libgraphqlparser), a C-level parser. - [`graphql-devise`](https://github.com/graphql-devise/graphql_devise), a gql interface to handle authentication with Devise - [`graphql-docs`](https://github.com/gjtorikian/graphql-docs), a tool to automatically generate static HTML documentation from your GraphQL implementation - [`graphql-metrics`](https://github.com/Shopify/graphql-metrics), a plugin to extract fine-grain metrics of GraphQL queries received by your server - [`graphql-groups`](https://github.com/hschne/graphql-groups), a DSL to define group- and aggregation queries with graphql-ruby - Rails Helpers: - [`graphql-activerecord`](https://github.com/goco-inc/graphql-activerecord) - [`graphql-rails-resolve`](https://github.com/colepatrickturner/graphql-rails-resolver) - [`graphql-query-resolver`](https://github.com/nettofarah/graphql-query-resolver), a graphql-ruby add-on to minimize N+1 queries. - [`graphql-rails_logger`](https://github.com/jetruby/graphql-rails_logger), a logger which allows you to inspect GraphQL queries in a more readable format. - [`apollo_upload_server-ruby`](https://github.com/jetruby/apollo_upload_server-ruby), a middleware which allows you to upload files with GraphQL and multipart/form-data using [`apollo-upload-client`](https://github.com/jaydenseric/apollo-upload-client) library on front-end. - [`search_object_graphql`](https://github.com/rstankov/SearchObjectGraphQL), a DSL for defining search resolvers for GraphQL. - [`action_policy-graphql`](https://github.com/palkan/action_policy-graphql), an integration for using [`action_policy`](https://github.com/palkan/action_policy) as an authorization framework for GraphQL applications. - [`graphql_rails`](https://github.com/samesystem/graphql_rails), Rails way GraphQL build tool - [`graphql-rails-generators`](https://github.com/ajsharp/graphql-rails-generators), Generate graphql-ruby mutations, types and input types from your ActiveRecord models. - [`graphql-ruby-fragment_cache`](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache), a tool for caching response fragments. - [`graphql-ruby-persisted_queries`](https://github.com/DmitryTsepelev/graphql-ruby-persisted_queries), the implementation of [Apollo persisted queries](https://github.com/apollographql/apollo-link-persisted-queries). - [`rubocop-graphql`](https://github.com/DmitryTsepelev/rubocop-graphql), [rubocop](https://github.com/rubocop-hq/rubocop) extension for enforcing best practices. ## Blog Posts - Building a blog in GraphQL and Relay on Rails [Introduction](https://medium.com/@gauravtiwari/graphql-and-relay-on-rails-getting-started-955a49d251de), [Part 1]( https://medium.com/@gauravtiwari/graphql-and-relay-on-rails-creating-types-and-schema-b3f9b232ccfc), [Part 2](https://medium.com/@gauravtiwari/graphql-and-relay-on-rails-first-relay-powered-react-component-cb3f9ee95eca) - https://medium.com/@khor/relay-facebook-on-rails-8b4af2057152 - https://blog.jacobwgillespie.com/from-rest-to-graphql-b4e95e94c26b#.4cjtklrwt - http://mgiroux.me/2015/getting-started-with-rails-graphql-relay/ - http://mgiroux.me/2015/uploading-files-using-relay-with-rails/ - http://mgiroux.me/2016/journey-into-graphql-ruby-query-execution/ - https://jonsimpson.ca/parallel-graphql-resolvers-with-futures/ - Active Storage meets GraphQL: [Direct uploads](https://evilmartians.com/chronicles/active-storage-meets-graphql-direct-uploads) and [Exposing attachment URLs](https://evilmartians.com/chronicles/active-storage-meets-graphql-pt-2-exposing-attachment-urls) - [Exposing permissions in GraphQL APIs with Action Policy](https://evilmartians.com/chronicles/exposing-permissions-in-graphql-apis-with-action-policy) - [Reporting non-nullable violations in graphql-ruby properly](https://evilmartians.com/chronicles/reporting-non-nullable-violations-in-graphql-ruby-properly) - [How to GraphQL with Ruby, Rails, Active Record, and no N+1](https://evilmartians.com/chronicles/how-to-graphql-with-ruby-rails-active-record-and-no-n-plus-one) ## Screencasts - [GraphQL Basics in Rails 5](https://rubyplus.com/episodes/271-GraphQL-Basics-in-Rails-5) ## Presentations - [Rescuing Legacy Codebases with GraphQL](https://speakerdeck.com/nettofarah/rescuing-legacy-codebases-with-graphql-1) by [@nettofarah](https://twitter.com/nettofarah) ## Tutorials - [How To GraphQL](https://www.howtographql.com/graphql-ruby/0-introduction/) by [@rstankov](https://github.com/rstankov) - [GraphQL Ruby CRUD Tutorial](https://www.blook.pub/books/graphql-rails-tutorial) by [@kohheepeace](https://twitter.com/kohheepeace) - Rails/GraphQL + React/Apollo Tutorial ([Part 1](https://evilmartians.com/chronicles/graphql-on-rails-1-from-zero-to-the-first-query), [Part 2](https://evilmartians.com/chronicles/graphql-on-rails-2-updating-the-data), [Part 3](https://evilmartians.com/chronicles/graphql-on-rails-3-on-the-way-to-perfection)) by [@evilmartians](https://twitter.com/evilmartians) graphql-ruby-1.11.10/guides/relay/000077500000000000000000000000001414121453000166575ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/relay/connections.md000066400000000000000000000206571414121453000215350ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: Connections section: Relay desc: Build and customize Relay-style connection types index: 1 --- Relay expresses [one-to-many relationships with _connections_](https://facebook.github.io/relay/graphql/connections.htm). Connections support pagination, filtering and metadata in a robust way. `graphql-ruby` includes built-in connection support for `Array`, `ActiveRecord::Relation`s, `Sequel::Dataset`s, and `Mongoid::Criteria`s. You can define custom connection classes to expose other collections with GraphQL. ## Connection fields To define a connection field, use the `field` method. For a return type, get a type's `.connection_type`. The field's method or `resolver:` should return a collection (eg, `Array` or `ActiveRecord::Relation`) _without_ pagination. (The connection will paginate the collection). For example: ```ruby class Types::PostType < GraphQL::Schema::Object # `Post#comments` returns an ActiveRecord::Relation # The GraphQL field returns a Connection field :comments, CommentType.connection_type, null: false # `Post#similar_posts` returns an Array field :similar_posts, PostType.connection_type, null: false # ... end ``` (GraphQL-Ruby applies connection logic because the return type's name ends in `Connection`. You can manually override this with `connection: true` or `connection: false`.) You can also define custom arguments and a custom resolve function for connections, just like other fields: ```ruby field :featured_comments, CommentType.connection_type do # Add an argument: argument :since, String, required: false end def featured_comments(since: nil) comments = object.comments.featured if since comments = comments.where("created_at >= ?", since) end # Return an Array or ActiveRecord::Relation comments end ``` ### Maximum Page Size You can limit the number of results with `max_page_size:`: ```ruby field :featured_comments, CommentType.connection_type, null: false, max_page_size: 50 ``` In addition, you can set a global default for all connection that do not specify a `max_page_size`: ```ruby class MySchema < GraphQL::Schema default_max_page_size 100 end ``` ## Connection types You can customize connection and edge types by using the class-based API: ```ruby # Make an edge class for use in the connection below: class Types::PostEdgeType < GraphQL::Types::Relay::BaseEdge node_type(PostType) end # Make a customized connection type class Types::PostConnectionWithTotalCountType < GraphQL::Types::Relay::BaseConnection edge_type(PostEdgeType) field :total_count, Integer, null: false def total_count # - `object` is the Connection # - `object.items` is the original collection of Posts object.items.size end end ``` Now, you can use `PostConnectionWithTotalCountType` to define a connection with the "totalCount" field: ```ruby class Types::AuthorType < GraphQL::Schema::Object # Use the custom connection type: field :posts, PostConnectionWithTotalCountType, null: false, connection: true end ``` (It uses `connection: true` because the type name _doesn't_ end in `"Connection"`.) This way, you can query your custom fields, for example: ```graphql { author(id: 1) { posts { totalCount # <= Your custom field } } } ``` In the same vein, you can extend your `*Edge` classes with extra fields. ### Customizing Base Classes The provided classes in {{ "GraphQL::Types::Relay" | api_doc }} extend {{ "Schema::Object" | api_doc }}, but if you want to add your own extensions, you can build your own type system using the built-in ones for inspiration. For example, to make your connection classes extend your _own_ base object, you could add a base connection class to your app: ```ruby class Types::BaseConnection < Types::BaseObject # ... copy-paste here end ``` Then take code from {{ "GraphQL::Types::Relay::BaseConnection" | api_doc }} and adapt it to your app. You can mix-and-match customized and built-in types. For example, if you customize the base `Edge` class, you can still use the built-in {{ "Types::Relay::PageInfo" | api_doc }} class. ### Custom Edge classes For more robust custom edges, you can define a custom edge class. It will be `obj` in the edge type's resolve function. For example, to define a membership edge: ```ruby # Make sure to familiarize yourself with GraphQL::Relay::Edge -- # you have to avoid naming conflicts here! class Types::MembershipSinceEdge < GraphQL::Relay::Edge # Cache `membership` to avoid multiple DB queries def membership @membership ||= begin # "parent" and "node" are passed in from the surrounding Connection, # See `Edge#initialize` for details person = self.parent team = self.node Membership.where(person: person, team: team).first end end def member_since membership.created_at.to_i end def leader? membership.leader? end end ``` Then, hook it up with custom edge type and custom connection type: ```ruby # Person => Membership => Team class Types::MembershipSinceEdgeType < GraphQL::Types::Relay::BaseEdge node_type(TeamType) field :member_since, Integer, null: false, description: "The date that this person joined this team" field :is_primary, Boolean, null: false, description: "Is this person the team leader?", method: :primary? end class Types::TeamMembershipsConnectionType < GraphQL::Types::Relay::BaseConnection # Here, hook up your custom class with `edge_class:` edge_type(MembershipSinceEdgeType, edge_class: MembershipSinceEdge) end ``` ## Connection objects Maybe you need to make a connection object yourself (for example, to return a connection type from a mutation). You can create a connection object like this: ```ruby items = [...] # your collection objects args = {} # stub out arguments for this connection object connection_class = GraphQL::Relay::BaseConnection.connection_for_nodes(items) connection_class.new(items, args) ``` `.connection_for_nodes` will return RelationConnection or ArrayConnection depending on `items`, then you can make a new connection For specifying a connection based on an `ActiveRecord::Relation` or `Sequel::Dataset`: ```ruby object = {} # your newly created object items = [...] # your AR or Sequel collection args = {} # stub out arguments for this connection object items_connection = GraphQL::Relay::RelationConnection.new( items, args ) edge = GraphQL::Relay::Edge.new(object, items_connection) ``` Additionally, connections may be provided with the `GraphQL::Field` that created them. This may be used for custom introspection or instrumentation. For example, ```ruby Schema.get_field(TodoListType, "todos") # => # context.irep_node.definitions[TodoListType] # => # # although this one may not work with fields on interfaces ``` ### Custom connections You can define a custom connection class and add it to `GraphQL::Relay`. First, define the custom connection: ```ruby require "set" # From Ruby's standard library class SetConnection < BaseConnection # derive a cursor from `item` def cursor_from_node(item) # ... end private # apply `#first` & `#last` to limit results def paged_nodes # ... end # apply cursor, order, filters, etc # to get a subset of matching objects def sliced_nodes # ... end end ``` Then, register the new connection with `GraphQL::Relay::BaseConnection`: ```ruby # When exposing a `Set`, use `SetConnection`: GraphQL::Relay::BaseConnection.register_connection_implementation(Set, SetConnection) ``` At runtime, `GraphQL::Relay` will use `SetConnection` to expose `Set`s. ## Cursors By default, cursors are encoded in base64 to make them opaque to a human client. You can specify a custom encoder with `Schema#cursor_encoder`. The value should be an object which responds to `.encode(plain_text, nonce:)` and `.decode(encoded_text, nonce: false)`. For example, to use URL-safe base-64 encoding: ```ruby module URLSafeBase64Encoder def self.encode(txt, nonce: false) Base64.urlsafe_encode64(txt) end def self.decode(txt, nonce: false) Base64.urlsafe_decode64(txt) end end class MySchema < GraphQL::Schema # ... cursor_encoder(URLSafeBase64Encoder) end ``` Now, all connections will use URL-safe base-64 encoding. From a connection instance, the `cursor_encoders` methods available via {{ "GraphQL::Relay::BaseConnection#encode" | api_doc }} and {{ "GraphQL::Relay::BaseConnection#decode" | api_doc }} graphql-ruby-1.11.10/guides/relay/mutations.md000066400000000000000000000003651414121453000212300ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: Mutations section: Relay desc: Implement Relay-compliant mutation fields index: 2 --- See {% internal_link "Mutation Classes", "/mutations/mutation_classes" %} for an updated mutation API. graphql-ruby-1.11.10/guides/relay/object_identification.md000066400000000000000000000071271414121453000235270ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: Object Identification section: Relay desc: Working with Relay-style global IDs index: 0 --- Relay uses [global object identification](https://facebook.github.io/relay/graphql/objectidentification.htm) to support some of its features: - __Caching__: Unique IDs are used as primary keys in Relay's client-side cache. - __Refetching__: Relay uses unique IDs to refetch objects when it determines that its cache is stale. (It uses the `Query.node` field to refetch objects.) ### Defining UUIDs You must provide a function for generating UUIDs and fetching objects with them. In your schema, define `self.id_from_object` and `self.object_from_id`: ```ruby class MySchema < GraphQL::Schema def self.id_from_object(object, type_definition, query_ctx) # Call your application's UUID method here # It should return a string MyApp::GlobalId.encrypt(object.class.name, object.id) end def self.object_from_id(id, query_ctx) class_name, item_id = MyApp::GlobalId.decrypt(id) # "Post" => Post.find(item_id) Object.const_get(class_name).find(item_id) end end ``` An unencrypted ID generator is provided in the gem. It uses `Base64` to encode values. You can use it like this: ```ruby class MySchema < GraphQL::Schema # Create UUIDs by joining the type name & ID, then base64-encoding it def self.id_from_object(object, type_definition, query_ctx) GraphQL::Schema::UniqueWithinType.encode(type_definition.graphql_name, object.id) end def self.object_from_id(id, query_ctx) type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id) # Now, based on `type_name` and `item_id` # find an object in your application # .... end end ``` ### Node interface One requirement for Relay's object management is implementing the `"Node"` interface. To implement the node interface, add {{ "GraphQL::Types::Relay::Node" | api_doc }} to your definition: ```ruby class Types::PostType < GraphQL::Schema::Object # Implement the "Node" interface for Relay implements GraphQL::Types::Relay::Node # ... end ``` To tell GraphQL how to resolve members of the `"Node"` interface, you must also define `Schema.resolve_type`: ```ruby class MySchema < GraphQL::Schema # You'll also need to define `resolve_type` for # telling the schema what type Relay `Node` objects are def self.resolve_type(type, obj, ctx) case obj when Post Types::PostType when Comment Types::CommentType else raise("Unexpected object: #{obj}") end end end ``` ### UUID fields Relay Nodes must have a field named `"id"` which returns a globally unique ID. To add a UUID field named `"id"`, use the `global_id_field` helper: ```ruby class Types::PostType < GraphQL::Schema::Object # `id` exposes the UUID global_id_field :id # ... end ``` This field will call the previously-defined `id_from_object` class method. ### `node` field (find-by-UUID) You should also provide a root-level `node` field so that Relay can refetch objects from your schema. It is provided as `GraphQL::Relay::Node.field`, so you can attach it like this: ```ruby class Types::QueryType < GraphQL::Schema::Object # Used by Relay to lookup objects by UUID: add_field(GraphQL::Types::Relay::NodeField) # ... end ``` ### `nodes` field You can also provide a root-level `nodes` field so that Relay can refetch objects by IDs. Similarly, it is provided as `GraphQL::Relay::Node.plural_field`: ```ruby class Types::QueryType < GraphQL::Schema::Object # Fetches a list of objects given a list of IDs add_field(GraphQL::Types::Relay::NodesField) # ... end ``` graphql-ruby-1.11.10/guides/schema/000077500000000000000000000000001414121453000170035ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/schema/class_based_api.md000066400000000000000000000354321414121453000224300ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Schema title: Class-based API Migration desc: Migrate from legacy .define DSL to Ruby classes. index: 6 --- In GraphQL `1.8`+, you can use Ruby classes to build your schema. You can __mix__ class-style and `.define`-style type definitions in a schema. The `.define` DSL is deprecated and will be removed at version 2.0. You can get an overview of this new feature: - [Rationale & Goals](#rationale--goals) - [Compatibility & Migration Overview](#compatibility--migration-overview) - [Using the upgrader](#upgrader) - [Roadmap](#roadmap) And learn about the APIs: - {% internal_link "Schema class", "/schema/definition" %} - [Common type configurations](#common-type-configurations) (shared by all the following types) - {% internal_link "Object classes", "/type_definitions/objects" %} - {% internal_link "Interface classes", "/type_definitions/interfaces" %} - {% internal_link "Union classes", "/type_definitions/unions" %} - {% internal_link "Enum classes", "/type_definitions/enums" %} - {% internal_link "Input Object classes", "/type_definitions/input_objects" %} - {% internal_link "Scalar classes", "/type_definitions/scalars" %} - {% internal_link "Customizing definitions", "/type_definitions/extensions" %} - {% internal_link "Custom introspection", "/schema/introspection" %} ## Rationale & Goals This new API aims to improve the "getting started" experience and the schema customization experience by replacing GraphQL-Ruby-specific DSLs with familiar Ruby semantics (classes and methods). Additionally, this new API must be cross-compatible with the current schema definition API so that it can be adopted bit-by-bit. ## Compatibility & Migration overview Parts of your schema can be converted one-by-one, so you can convert definitions gradually. ### Classes In general, each `.define { ... }` block will be converted to a class. - Instead of a `GraphQL::{X}Type`, classes inherit from `GraphQL::Schema::{X}`. For example, instead of `GraphQL::ObjectType.define { ... }`, a definition is made by extending `GraphQL::Schema::Object` - Any class hierarchy is supported; It's recommended to create a base class for your application, then extend the base class for each of your types (like `ApplicationController` in Rails, see [Customizing Definitions](#customizing-defintions)). See sections below for specific information about each schema definition class. ### ⚠️ Heads up ⚠️ Keep in mind that class based Schemas will be initialized at execution time instead of application boot, depending on the size of your schema, this could result in request timeouts for your users after your application restarts. For a workaround please check https://github.com/rmosolgo/graphql-ruby/issues/2034 ### Type Instances The previous `GraphQL::{X}Type` objects are still used under the hood. Each of the new `GraphQL::Schema::{X}` classes implements a few methods: - `.to_graphql`: creates a new instance of `GraphQL::{X}Type` - `.graphql_definition`: returns a cached instance of `GraphQL::{X}Type` If you have custom code which breaks on new-style definitions, try calling `.graphql_definition` to get the underlying type object. As described below, `.to_graphql` can be overridden to customize the type system. ### List Types and Non-Null Types Previously, list types were expressed with `types[T]` and non-null types were expressed with `!T`. Now: - List types are expressed with Ruby Arrays, `[T]`, for example, `field :owners, [Types::UserType]` - By default, list members are _non-null_, for example, `[Types::UserType]` becomes `[User!]` - If your list members may be null, add `, null: true` to the array: `[Types::UserType, null: true]` becomes `[User]` (the list may include `nil`) - Non-null types are expressed with keyword arguments `null:` or `required:` - `field` takes a keyword `null:`. `null: true` means the field is nullable, `null: false` means the field is non-null (equivalent to `!`) - `argument` takes a keyword `required:`. `required: true` means the argument is non-null (equivalent to `!`), `required: false` means that the argument is nullable In legacy-style classes, you may also use plain Ruby methods to create list and non-null types: - `#to_non_null_type` converts a type to a non-null variant (ie, `T.to_non_null_type` is equivalent to `!T`) - `#to_list_type` converts a type to a list variant (ie, `T.to_list_type` is equivalent to `types[T]`) The `!` method has been removed to avoid ambiguity with the built-in logical operator and related foot-gunning. For compatibility, you may wish to backport `!` to class-based type definitions. You have two options: __A refinement__, activated in [file scope or class/module scope](https://docs.ruby-lang.org/en/2.4.0/syntax/refinements_rdoc.html#label-Scope): ```ruby # Enable `!` method in this scope using GraphQL::DeprecatedDSL ``` __A monkeypatch__, activated in global scope: ```ruby # Enable `!` everywhere GraphQL::DeprecatedDSL.activate ``` ### Connection fields & types There is no `connection(...)` method. Instead, connection fields are inferred from the type name. If the type name ends in `Connection`, the field is treated as a connection field. This default may be overridden by passing a `connection: true` or `connection: false` keyword. For example: ```ruby # This will be treated as a connection, since the type name ends in "Connection" field :projects, Types::ProjectType.connection_type ``` ### Resolve function compatibility If you define a type with a class, you can use existing GraphQL-Ruby resolve functions with that class, for example: ```ruby # Using a Proc literal or #call-able field :something, ... resolve: ->(obj, args, ctx) { ... } # Using a predefined field field :do_something, field: Mutations::DoSomething.field # Using a GraphQL::Function field :something, function: Functions::Something.new ``` When using these resolution implementations, they will be called with the same `(obj, args, ctx)` parameters as before. ## Upgrader `1.8` includes an _auto-upgrader_ for transforming Ruby files from the `.define`-based syntax to `class`-based syntax. The upgrader is a pipeline of sequential transform operations. It ships with default pipelines, but you may customize the upgrade process by replacing the built-in pipelines with a custom ones. The upgrader has an additional dependency, `parser`, which you must add to your project manually (for example, by adding to your `Gemfile`). Remember that your project may be transformed one file at a time because the two syntaxes are compatible. This way, you can convert a few files and run your tests to identify outstanding issues, and continue working incrementally. This transformation may not be perfect, but it should cover the most common cases. If you want to ask a question or report a bug, please {% open_an_issue "Upgrader question/bug report","Please share: the source code you're trying to transform, the output you got from the transformer, and the output you want to get from the transformer." %}. ### Using the Default Upgrade Task The upgrader ships with rake tasks, included as a railtie ([source](https://github.com/rmosolgo/graphql-ruby/blob/v1.8.0/lib/graphql/railtie.rb)). The railtie will be automatically installed by your Rails app, and it provides the following tasks: - `graphql:upgrade:schema[path/to/schema.rb]`: upgrade the Schema file - `graphql:upgrade:member[path/to/some/type.rb]`: upgrade a type definition (object, interface, union, etc) - `graphql:upgrade[app/graphql/**/*]`: run the `member` upgrade on files which have a suffix of `_(type|interface|enum|union).rb` - `graphql:upgrade:create_base_objects[path/to/graphql/]`: add base classes to your project ### Writing a Custom Upgrade Task You might write a custom task because: - You want to customize the transformation pipeline - You're not using Rails, so a railtie won't work To write a custom task, you can write a rake task (or Ruby script) which uses the upgrader's API directly. Here's the code to upgrade a type definition with the default transform pipeline: ```ruby # Read the original source code into a string original_source = File.read("path/to/type.rb") # Initialize an upgrader with the default transforms upgrader = GraphQL::Upgrader::Member.new(original_source) # Perform the transformation, get the transformed source code transformed_source = upgrader.upgrade # Update the source file with the new code File.write("path/to/type.rb", transformed_source) ``` In this custom code, you can pass some keywords to {{ "GraphQL::Upgrader::Member.new" | api_doc }}: - `type_transforms:` Applied to the source code as a whole, applied first - `field_transforms:` Applied to each field/connection/argument definition (extracted from the source, transformed independently, then re-inserted) - `clean_up_transforms:` Applied to the source code as a whole, _after_ the type and field transforms Keep in mind that these transforms are performed in sequence, so the text changes over time. If you want to transform the source text, use `.unshift()` to add transforms to the _beginning_ of the pipeline instead of the end. For example, in `script/graphql-upgrade`: ```ruby #!/usr/bin/env ruby # @example Upgrade app/graphql/types/user_type.rb: # script/graphql-upgrade app/graphql/types/user_type.rb # Replace the default define-to-class transform with a custom one: type_transforms = GraphQL::Upgrader::Member::DEFAULT_TYPE_TRANSFORMS.map { |t| if t == GraphQL::Upgrader::TypeDefineToClassTransform GraphQL::Upgrader::TypeDefineToClassTransform.new(base_class_pattern: "Platform::\\2s::Base") else t end } # Add this transformer at the beginning of the list: type_transforms.unshift(GraphQL::Upgrader::ConfigurationToKwargTransform.new(kwarg: "visibility")) # run the upgrader original_text = File.read(ARGV[0]) upgrader = GraphQL::Upgrader::Member.new(original_text, type_transforms: type_transforms) transformed_text = upgrader.upgrade File.write(filename, transformed_text) ``` ### Writing a custom transformer Objects in the transform pipeline may be: - A class which responds to `.new.apply(input_text)` and returns the transformed code - An object which responds to `.apply(input_text)` and returns the transformed code The library provides a {{ "GraphQL::Upgrader::Transform" | api_doc }} base class with a few convenience methods. You can also customize the built-in transformers listed below. For example, here's a transform which rewrites type definitions from a `model_type(model) do ... end` factory method to the class-based syntax: ```ruby # Create a custom transform for our `model_type` factory: class ModelTypeToClassTransform < GraphQL::Upgrader::Transform def initialize # Find calls to the factory method, which have a type class inside @find_pattern = /^( +)([a-zA-Z_0-9:]*) = model_type\(-> ?\{ ?:{0,2}([a-zA-Z_0-9:]*) ?\} ?\) do/ # Replace them with a class definition and a `model_name("...")` call: @replace_pattern = "\\1class \\2 < Platform::Objects::Base\n\\1 model_name \"\\3\"" end def apply(input_text) # Run the substitution on the input text: input_text.sub(@find_pattern, @replace_pattern) end end # Add the class to the beginning of the pipeline type_transforms.unshift(ModelTypeToClassTransform) ``` ### Built-in transformers Follow links to the API doc to read the source of each transform: Type transforms ({{ "GraphQL::Upgrader::Member::DEFAULT_TYPE_TRANSFORMS" | api_doc }}): - {{ "GraphQL::Upgrader::Transform" | api_doc }} base class, provides a `normalize_type_expression` helper - {{ "GraphQL::Upgrader::TypeDefineToClassTransform" | api_doc }} turns `.define` into `class ...` with a regexp substitution - {{ "GraphQL::Upgrader::NameTransform" | api_doc }} takes `name "..."` and removes it if it's redundant, or converts it to `graphql_name "..."` - {{ "GraphQL::Upgrader::InterfacesToImplementsTransform" | api_doc }} turns `interfaces [A, B...]` into `implements(A)\nimplements(B)...` Field transforms ({{ "GraphQL::Upgrader::Member::DEFAULT_FIELD_TRANSFORMS" | api_doc }}): - {{ "GraphQL::Upgrader::RemoveNewlinesTransform" | api_doc }} removes newlines from field definitions to normalize them - {{ "GraphQL::Upgrader::PositionalTypeArgTransform" | api_doc }} moves `type X` from the `do ... end` block into a positional argument, to normalize the definition - {{ "GraphQL::Upgrader::ConfigurationToKwargTransform" | api_doc }} moves a `do ... end` configuration to a keyword argument. By default, this is used for `property` and `description`. You can add new instances of this transform to convert your custom DSL. - {{ "GraphQL::Upgrader::PropertyToMethodTransform" | api_doc }} turns `property:` to `method:` - {{ "GraphQL::Upgrader::UnderscoreizeFieldNameTransform" | api_doc }} converts field names to underscore-case. __NOTE__ that this conversion may be _wrong_ in the case of `bodyHTML => body_html`. When you find it is wrong, manually revert it and preserve the camel-case field name. - {{ "GraphQL::Upgrader::ResolveProcToMethodTransform" | api_doc }} converts `resolve -> { ... }` to `def {field_name} ... ` method definitions - {{ "GraphQL::Upgrader::UpdateMethodSignatureTransform" | api_doc }} converts the type name to the new syntax, and adds `null:`/`required:` to the method signature Clean-up transforms ({{ "GraphQL::Upgrader::Member::DEFAULT_CLEAN_UP_TRANSFORMS" | api_doc }}): - {{ "GraphQL::Upgrader::RemoveExcessWhitespaceTransform" | api_doc }} removes redundant newlines - {{ "GraphQL::Upgrader::RemoveEmptyBlocksTransform" | api_doc }} removes `do end` with nothing inside them ## Roadmap Here is a working plan for rolling out this feature: - ongoing: - ☐ Receive feedback from GraphQL schema owners about the new API (usability & goals) - graphql 1.8: - ☑ Build a schema definition API based on classes instead of singletons - ☑ Migrate a few components of GitHub's GraphQL schema to this new API - ☑ Build advanced class-based features: - ☑ Custom `Context` classes - ☑ Custom introspection types - ☐ ~~Custom directives~~ Probably will mess with execution soon, not worth the investment now - ☐ ~~Custom `Schema#execute` method~~ not necessary - ☑ Migrate all of GitHub's GraphQL schema to this new API - graphql 1.9: - ☑ Update all GraphQL-Ruby docs to reflect this new API - graphql 1.10: - ☑ Begin sunsetting `.define` - graphql 2.0: - ☐ Remove `.define` ## Common Type Configurations Some configurations are used for _all_ types described below: - `graphql_name` overrides the type name. (The default value is the Ruby constant name, without any namespaces) - `description` provides a description for GraphQL introspection. For example: ```ruby class Types::TodoList < GraphQL::Schema::Object # or Scalar, Enum, Union, whatever graphql_name "List" # Overrides the default of "TodoList" description "Things to do (may have already been done)" end ``` (Implemented in {{ "GraphQL::Schema::Member" | api_doc }}). graphql-ruby-1.11.10/guides/schema/definition.md000066400000000000000000000152331414121453000214610ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Schema title: Definition desc: Defining your schema index: 1 --- A GraphQL system is called a _schema_. The schema contains all the types and fields in the system. The schema executes queries and publishes an {% internal_link "introspection system","/schema/introspection" %}. Your GraphQL schema is a class that extends {{ "GraphQL::Schema" | api_doc }}, for example: ```ruby class MyAppSchema < GraphQL::Schema max_complexity 400 query Types::Query use GraphQL::Batch # Define hooks as class methods: def self.resolve_type(type, obj, ctx) # ... end def self.object_from_id(node_id, ctx) # ... end def self.id_from_object(object, type, ctx) # ... end end ``` There are lots of schema configuration options: - [root objects, introspection and orphan types](#root-objects-introspection-and-orphan-types) - [object identification hooks](#object-identification-hooks) - [execution configuration](#execution-configuration) - [context class](#context-class) - [default limits](#default-limits) - [plugins](#plugins) For defining GraphQL types, see the guides for those types: {% internal_link "object types", "/type_definitions/objects" %}, {% internal_link "interface types", "/type_definitions/interfaces" %}, {% internal_link "union types", "/type_definitions/unions" %}, {% internal_link "input object types", "/type_definitions/input_objects" %}, {% internal_link "enum types", "/type_definitions/enums" %}, and {% internal_link "scalar types", "/type_definitions/scalars" %}. ## Root Objects, Introspection and Orphan Types A GraphQL schema is a web of interconnected types, and it has a few starting points for discovering the elements of that web: __Root types__ (`query`, `mutation`, and `subscription`) are the [entry points for queries to the system](https://graphql.org/learn/schema/#the-query-and-mutation-types). Each one is an object type which can be connected to the schema by a method with the same name: ```ruby class MySchema < GraphQL::Schema # Required: query Types::Query # Optional: mutation Types::Mutation subscription Types::Subscription end ``` __Introspection__ is a built-in part of the schema. Every schema has a default introspection system, but you can {% internal_link "customize it","/schema/introspection" %} and hook it up with `introspection`: ```ruby class MySchema < GraphQL::Schema introspection CustomIntrospection end ``` __Orphan Types__ are types which should be in the schema, but can't be discovered by traversing the types and fields from `query`, `mutation` or `subscription`. This has one very specific use case, see {% internal_link "Orphan Types", "/type_definitions/interfaces#orphan-types" %}. ```ruby class MySchema < GraphQL::Schema orphan_types [Types::Comment, ...] end ``` ## Object Identification Hooks A GraphQL schema needs a handful of hooks for finding and disambiguating objects while queries are executed. __`resolve_type`__ is used when a specific object's corresponding GraphQL type must be determined. This happens for fields that return {% internal_link "interface", "/type_definitions/interfaces" %} or {% internal_link "union", "/type_definitions/unions" %} types. The class method `def self.resolve_type` is used: ```ruby class MySchema < GraphQL::Schema def self.resolve_type(abstract_type, object, context) # Disambiguate `object`, from among `abstract_type`'s members # (`abstract_type` is an interface or union type.) end end ``` __`object_from_id`__ is used by Relay's `node(id: ID!): Node` field. It receives a unique ID and must return the object for that ID, or `nil` if the object isn't found (or if it should be hidden from the current user). ```ruby class MySchema < GraphQL::Schema def self.object_from_id(unique_id, context) # Find and return the object for `unique_id` # or `nil` end end ``` __`id_from_object`__ is used to implement Relay's `Node.id` field. It should return a unique ID for the given object. This ID will later be sent to `object_from_id` to refetch the object. ```ruby class MySchema < GraphQL::Schema def self.id_from_object(object, type, context) # Return a unique ID for `object`, whose GraphQL type is `type` end end ``` ## Execution Configuration __`instrument`__ attaches instrumenters to the schema, see {% internal_link "Instrumentation", "/queries/instrumentation" %} for more information. ```ruby class MySchema < GraphQL::Schema instrument :field, ResolveTimerInstrumentation end ``` __`tracer`__ is another way to hook into execution, see {% internal_link "Tracing", "/queries/tracing" %} for more. ```ruby class MySchema < GraphQL::Schema tracer MetricTracer end ``` __`query_analyzer`__ and __`multiplex_analyzer`__ accept processors for ahead-of-type query analysis, see {% internal_link "Analysis", "/queries/ast_analysis" %} for more. ```ruby class MySchema < GraphQL::Schema query_analyzer MyQueryAnalyzer end ``` __`lazy_resolve`__ registers classes with {% internal_link "lazy execution", "/schema/lazy_execution" %}: ```ruby class MySchema < GraphQL::Schema lazy_resolve Promise, :sync end ``` __`type_error`__ handles type errors at runtime, read more in the {% internal_link "Invariants guide", "/errors/type_errors" %}. ```ruby class MySchema < GraphQL::Schema def self.type_error(type_err, context) # Handle `type_err` in some way end end ``` __`rescue_from`__ accepts error handlers for application errors, for example: ```ruby class MySchema < GraphQL::Schema rescue_from(ActiveRecord::RecordNotFound) { "Not found" } end ``` ## Context Class Usually, `context` is an instance of {{ "GraphQL::Query::Context" | api_doc }}, but you can create a custom subclass and attach it with `.context_class`, for example: ```ruby class CustomContext < GraphQL::Query::Context # Shorthand to get the current user def viewer self[:viewer] end end class MySchema < GraphQL::Schema context_class CustomContext end ``` Then, during execution, `context` will be an instance of `CustomContext`. ## Default Limits `max_depth` and `max_complexity` apply some limits to incoming queries. See {% internal_link "Complexity and Depth", "/queries/complexity_and_depth" %} for more. `default_max_page_size` applies limits to `Connection` fields. ```ruby class MySchema < GraphQL::Schema max_depth 10 max_complexity 300 default_max_page_size 20 end ``` ## Plugins A plugin is an object that responds to `#use`. Plugins are used to attach new behavior to a schema without a lot of API overhead. For example, the gem's {% internal_link "monitoring tools", "/queries/tracing#monitoring" %} are plugins: ```ruby class MySchema < GraphQL::Schema use(GraphQL::Tracing::NewRelicTracing) end ``` graphql-ruby-1.11.10/guides/schema/generators.md000066400000000000000000000041231414121453000214760ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: Generators section: Schema desc: Use Rails generators to install GraphQL and scaffold new types. index: 3 --- If you're using GraphQL with Ruby on Rails, you can use generators to: - [setup GraphQL](#graphqlinstall), including [GraphiQL](https://github.com/graphql/graphiql), [GraphQL::Batch](https://github.com/Shopify/graphql-batch), and [Relay](https://facebook.github.io/relay/) - [scaffold types](#scaffolding-types) - [scaffold Relay mutations](#scaffolding-mutations) - [scaffold GraphQL::Batch loaders](#scaffolding-loaders) ## graphql:install You can add GraphQL to a Rails app with `graphql:install`: ``` rails generate graphql:install ``` This will: - Set up a folder structure in `app/graphql/` - Add schema definition - Add base type classes - Add a `Query` type definition - Add a `Mutation` type definition with a base mutation class - Add a route and controller for executing queries - Install [`graphiql-rails`](https://github.com/rmosolgo/graphiql-rails) After installing you can see your new schema by: - `bundle install` - `rails server` - Open `localhost:3000/graphiql` ### Options - `--relay` will add [Relay](https://facebook.github.io/relay/)-specific code to your schema - `--batch` will add [GraphQL::Batch](https://github.com/Shopify/graphql-batch) to your gemfile and include the setup in your schema - `--playground` will include `graphql_playground-rails` in the setup (mounted at `/playground`) - `--no-graphiql` will exclude `graphiql-rails` from the setup - `--schema=MySchemaName` will be used for naming the schema (default is `#{app_name}Schema`) ## Scaffolding Types Several generators will add GraphQL types to your project. Run them with `-h` to see the options: - `rails g graphql:object` - `rails g graphql:interface` - `rails g graphql:union` - `rails g graphql:enum` - `rails g graphql:scalar` ## Scaffolding Mutations You can prepare a Relay Classic mutation with ``` rails g graphql:mutation #{mutation_name} ``` ## Scaffolding Loaders You can prepare a GraphQL::Batch loader with ``` rails g graphql:loader ``` graphql-ruby-1.11.10/guides/schema/introspection.md000066400000000000000000000147021414121453000222310ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: Introspection section: Schema desc: GraphQL has an introspection system that tells about the schema. index: 3 --- A GraphQL schema has a [built-in introspection system](https://graphql.org/learn/introspection/) that publishes the schema's structure. In fact, the introspection system can be queried using GraphQL, for example: ```graphql { __schema { queryType { name } } } # Returns: # { # "data": { # "__schema": { # "queryType": { # "name": "Query" # } # } # } # } ``` This system is used for GraphQL tooling like the [GraphiQL editor](https://github.com/graphql/graphiql). Here are the default parts of the introspection system: - `__schema` is a root-level field that contains data about the schema: its entry points, types, and directives. - `__type(name: String!)` is a root-level field that returns data about a type with the given `name`, if there is a type with that name. - `__typename` works a bit differently: it can be added to _any_ selection, and it will return the type of object being queried. Here are some `__typename` examples: ```graphql { user(id: "1") { handle __typename } } # Returns: # { # "data": { # "user": { # "handle": "rmosolgo", # "__typename": "User" # } # } # } ``` For unions and interfaces, `__typename` returns the _object_ type for the current object, for example: ```graphql { search(term: "puppies") { title __typename } } # Returns: # { # "data": { # "search": [ # { # "title": "Sound of Dogs Barking", # "__typename": "AudioClip", # }, # { # "title": "Cute puppies playing with a stick", # "__typename": "VideoClip", # }, # { # "title": "The names of my favorite pets", # "__typename": "TextSnippet" # }, # ] # } # } ``` ## Customizing Introspection This is an __experimental feature__, only supported in class-based schemas. With a class-based schema, you can use custom introspection types. ```ruby # create a module namespace for your custom types: module Introspection # described below ... end class MySchema < GraphQL::Schema # ... # then pass the module as `introspection` introspection Introspection end ``` Keep in mind that off-the-shelf tooling may not support your custom introspection fields. You may have to modify existing tooling or create your own tools to make use of your extensions. ### Introspection Namespace The introspection namespace may contain a few different customizations: - Class-based {% internal_link "object definitions", "/type_definitions/objects" %} which replace the built-in introspection types (such as `__Schema` and `__Type`) - `EntryPoints`, A class-based {% internal_link "object definition", "/type_definitions/objects" %} containing introspection entry points (like `__schema` and `__type(name:)`). - `DynamicFields`, A class-based {% internal_link "object definition", "/type_definitions/objects" %} containing dynamic, globally-available fields (like `__typename`.) ### Custom Introspection Types The `module` passed as `introspection` may contain classes with the following names, which replace the built-in introspection types: Custom class name | GraphQL type | Built-in class name --|--|-- `SchemaType` | `__Schema` | {{ "GraphQL::Introspection::SchemaType" | api_doc }} `TypeType` | `__Type` | {{ "GraphQL::Introspection::TypeType" | api_doc }} `DirectiveType` | `__Directive` | {{ "GraphQL::Introspection::DirectiveType" | api_doc }} `DirectiveLocationType` | `__DirectiveLocation` | {{ "GraphQL::Introspection::DirectiveLocationEnum" | api_doc }} `EnumValueType` | `__EnumValue` | {{ "GraphQL::Introspection::EnumValueType" | api_doc }} `FieldType` | `__Field` | {{ "GraphQL::Introspection::FieldType" | api_doc }} `InputValueType` | `__InputValue` | {{ "GraphQL::Introspection::InputValueType" | api_doc }} `TypeKindType` | `__TypeKind` | {{ "GraphQL::Introspection::TypeKindEnum" | api_doc }} The class-based definitions' names _must_ match the names of the types they replace. #### Extending a Built-in Type The built-in classes listed above may be extended: ```ruby module Introspection class SchemaType < GraphQL::Introspection::SchemaType # ... end end ``` Inside the class definition, you may: - add new fields by calling `field(...)` and providing implementations - redefine field structure by calling `field(...)` - provide new field implementations by defining methods - provide new descriptions by calling `description(...)` ### Introspection Entry Points The GraphQL spec describes two entry points to the introspection system: - `__schema` returns data about the schema (as type `__Schema`) - `__type(name:)` returns data about a type, if one is found by name (as type `__Type`) You can re-implement these fields or create new ones by creating a custom `EntryPoints` class in your introspection namespace: ```ruby module Introspection class EntryPoints < GraphQL::Introspection::EntryPoints # ... end end ``` This class an object type definition, so you can override fields or add new ones here. They'll be available on the root `query` object, but ignored in introspection (just like `__schema` and `__type`). ### Dynamic Fields The GraphQL spec describes a field which may be added to _any_ selection: `__typename`. It returns the name of the current GraphQL type. You can add fields like this (or override `__typename`) by creating a custom `DynamicFields` defintion: ```ruby module Introspection class DynamicFields < GraphQL::Introspection::DynamicFields # ... end end ``` Any fields defined there will be available in any selection, but ignored in introspection (just like `__typename`). ## Disabling Introspection In case you want to turn off introspection entry points `__schema` and `__type` (for instance in the production environment) you can use a `#disable_introspection_entry_points` shorthand method: ```ruby class MySchema < GraphQL::Schema disable_introspection_entry_points if Rails.env.production? end ``` Where `disable_introspection_entry_points` will disable both the `__schema` and `__type` introspection entry points, you can also individually disable the introspection entry points using the `disable_schema_introspection_entry_point` and `disable_type_introspection_entry_point` shorthand methods: ```ruby class MySchema < GraphQL::Schema disable_schema_introspection_entry_point disable_type_introspection_entry_point end ``` graphql-ruby-1.11.10/guides/schema/lazy_execution.md000066400000000000000000000057001414121453000223710ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: Lazy Execution section: Schema desc: Resolve functions can return "unfinished" results that are deferred for batch resolution. index: 4 --- With lazy execution, you can optimize access to external services (such as databases) by making batched calls. Building a lazy loader has three steps: - Define a lazy-loading class with _one_ method for loading & returning a value - Connect it to your schema with {{ "GraphQL::Schema#lazy_resolve" | api_doc }} - In `resolve` functions, return instances of the lazy-loading class ## Example: Batched Find Here's a way to find many objects by ID using one database call, preventing N+1 queries. 1. Lazy-loading class which finds models by ID. ```ruby class LazyFindPerson def initialize(query_ctx, person_id) @person_id = person_id # Initialize the loading state for this query, # or get the previously-initiated state @lazy_state = query_ctx[:lazy_find_person] ||= { pending_ids: Set.new, loaded_ids: {}, } # Register this ID to be loaded later: @lazy_state[:pending_ids] << person_id end # Return the loaded record, hitting the database if needed def person # Check if the record was already loaded: loaded_record = @lazy_state[:loaded_ids][@person_id] if loaded_record # The pending IDs were already loaded, # so return the result of that previous load loaded_record else # The record hasn't been loaded yet, so # hit the database with all pending IDs pending_ids = @lazy_state[:pending_ids].to_a people = Person.where(id: pending_ids) people.each { |person| @lazy_state[:loaded_ids][person.id] = person } @lazy_state[:pending_ids].clear # Now, get the matching person from the loaded result: @lazy_state[:loaded_ids][@person_id] end end ``` 2. Connect the lazy resolve method ```ruby class MySchema < GraphQL::Schema # ... lazy_resolve(LazyFindPerson, :person) end ``` 3. Return lazy objects from `resolve` ```ruby field :author, PersonType, null: true def author LazyFindPerson.new(context, object.author_id) end ``` Now, calls to `author` will use batched database access. For example, this query: ```graphql { p1: post(id: 1) { author { name } } p2: post(id: 2) { author { name } } p3: post(id: 3) { author { name } } } ``` Will only make one query to load the `author` values. ## Gems for batching The example above is simple and has some shortcomings. Consider the following gems for a robust solution to batched resolution: * [`graphql-batch`](https://github.com/shopify/graphql-batch) provides a powerful, flexible toolkit for lazy resolution with GraphQL. * [`dataloader`](https://github.com/sheerun/dataloader) is more general promise-based utility for batching queries within the same thread. * [`batch-loader`](https://github.com/exAspArk/batch-loader) works with any Ruby code including GraphQL, no extra dependencies or primitives. graphql-ruby-1.11.10/guides/schema/limiting_visibility.md000066400000000000000000000057111414121453000234140ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true title: Limiting Visibility section: Schema desc: Flag types and fields so that only some clients can see them. index: 5 --- Sometimes, you want to hide schema elements from some users. For example: - some elements are feature flagged; or - some elements require higher permissions If you only want to limit _access_ to these fields, consider using the {% internal_link "authorization framework", "/authorization/overview" %}. If you want to _completely hide_ some fields, types, enum values or arguments, read on! ## Filtering You can hide parts of the schema by passing `except:` or `only:` to `Schema.execute`. For example: ```ruby # `except:` blacklists items: filter = PermissionBlacklist.new(@current_user) MySchema.execute(query_string, except: filter) # OR # `only:` whitelists items: filter = PermissionWhitelist.new(@current_user) MySchema.execute(query_string, only: filter) ``` During that query, some elements will be hidden. This means that fields, types, arguments or enum values will be treated as if they were not defined at all. A filter must respond to `#call(schema_member, ctx)`. When that method returns truthy, the schema member will be blacklisted or whitelisted. For example, here's an implementation of `PermissionWhitelist` above: ```ruby class PermissionWhitelist def initialize(person) @person = person end # If this returns true, the schema member will be allowed def call(schema_member, ctx) Permissions.allowed?(person, schema_member) end end ``` The `schema_member` may be any of: - Type ({{ "GraphQL::BaseType" | api_doc }} and subclasses) - Field ({{ "GraphQL::Field" | api_doc }}) - Argument ({{ "GraphQL::Argument" | api_doc }}) - Enum value ({{ "GraphQL::EnumType::EnumValue" | api_doc }}) ## Use with Metadata This feature pairs nicely with attaching custom data to types. See the {% internal_link "Extensions Guide","/type_definitions/extensions" %} for information about assigning values to an object's `metadata`. Then, you can check `metadata` in your filter. For example, to hide fields based on a metadata flag: ```ruby # Hide secret objects from this user top_secret = ->(schema_member, ctx) { schema_member.metadata[:top_secret]} MySchema.execute(query_string, except: top_secret) ``` ## Printing a Filtered Schema You can see how filters will be applied to the schema by printing the schema with that filter. {{ "GraphQL::Schema#to_definition" | api_doc }} accepts `only:` and `except:` options. For example, to see how the schema looks to a specific user: ```ruby example_user = User.new(permission: :admin) filter = PermissionWhitelist.new(example_user) defn_string = MySchema.to_definition(only: filter) puts defn_string # => prints out the filtered schema ``` `Schema#to_definition` also accepts a context which will be passed to the filter as well, for example: ```ruby context = { current_user: example_user } puts MySchema.to_definition(only: filter, context: context) ``` graphql-ruby-1.11.10/guides/schema/root_types.md000066400000000000000000000025171414121453000215410ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Schema title: Root Types desc: Root types are the entry points for queries, mutations and subscriptions. index: 2 --- GraphQL queries begin from [root types](https://graphql.org/learn/schema/#the-query-and-mutation-types): `query`, `mutation`, and `subscription`. Attach these to your schema using methods with the same name: ```ruby class MySchema < GraphQL::Schema # required query Types::QueryType # optional mutation Types::MutationType subscription Types::SubscriptionType end ``` The types are `GraphQL::Schema::Object` classes, for example: ```ruby # app/graphql/types/query_type.rb class Types::QueryType < GraphQL::Schema::Object field :posts, [PostType], 'Returns all blog posts', null: false end # Similarly: class Types::MutationType < GraphQL::Schema::Object field :create_post, mutation: Mutations::AddPost end # and class Types::SubscriptionType < GraphQL::Schema::Object field :comment_added, subscription: Subscriptions::CommentAdded end ``` Each type is the entry point for the corresponding GraphQL query: ```ruby query Posts { # `Query.posts` posts { ... } } mutation AddPost($postAttrs: PostInput!){ # `Mutation.createPost` createPost(attrs: $postAttrs) } subscription CommentAdded { # `Subscription.commentAdded` commentAdded(postId: 1) } ``` graphql-ruby-1.11.10/guides/schema/sdl.md000066400000000000000000000071361414121453000201160ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Schema title: Parsing GraphQL Schema Definition Language into a Ruby Schema desc: Defining a schema from a string or .graphql file index: 7 --- GraphQL-Ruby includes a way to build a runable schema from the GraphQL Schema Definition Language (SDL). {{ "GraphQL::Schema.from_definition" | api_doc }} returns a schema class based on a filename or string containing GraphQL SDL. For example: ```ruby # From a file: MySchema = GraphQL::Schema.from_definition("path/to/schema.graphql") # Or, from a string: MySchema = GraphQL::Schema.from_definition(<<~GRAPHQL) type Query { # ... } # ... GRAPHQL ``` Definitions from the SDL are converted into Ruby classes, similar to those defined in plain Ruby code. ## Execution You can provide execution behaviors to a generated schema as `default_resolve:`, which accepts two kinds of values: - __Implementation Object__: an object that implements several methods used at runtime - __Implementation Hash__: keys and nested hashes provide procs for execution ### Implementation Object By providing an object that implements several runtime methods, you can define the execution behaviors of a schema loaded from SDL: ```ruby class SchemaImplementation # see below for methods end # Pass the object as `default_resolve:` MySchema = GraphQL::Schema.from_definition( "path/to/schema.graphql", default_resolve: SchemaImplementation.new ) ``` The `default_resolve:` object may implement: - `#call(type, field, obj, args, ctx)` for resolving fields - `#resolve_type(abstract_type, obj, ctx)` for resolving `obj` as one of `abstract_type`'s possible object types - `#coerce_input(type, value, ctx)` for coercing scalar input - `#coerce_result(type, value, ctx)` for coercing scalar return values ### Implementation Hash Alternatively, you can provide a Hash containing callable behaviors, for example: ```ruby schema_implementation = { # ... see below for hash structure } # Pass the hash as `default_resolve:` MySchema = GraphQL::Schema.from_definition( "path/to/schema.graphql", default_resolve: schema_implementation ) ``` The hash may contain: - A key for each object type name, containing a sub-hash of `{ field_name => ->(obj, args, ctx) { ... } }` for resolving object fields - A key for each scalar type name, containing a sub-hash with keys `"coerce_result"` and `"coerce_input"`, each pointing to a `->(value, ctx) { ... }` for handling scalar values at runtime - A `"resolve_type"` key pointing to a `->(abstract_type, object, ctx) { ... }` callable, used for resolving `object` to one of `abstract_type`'s possible types ## Plugins { "GraphQL::Schema.from_definition" | api_doc }} accepts a `using:` argument, which may be given as a map of `plugin => args` pairs. For example: ```ruby MySchema = GraphQL::Schema.from_definition("path/to/schema.graphql", using: { GraphQL::Pro::PusherSubscriptions => { redis: $redis }, GraphQL::Pro::OperationStore => nil, # no options here }) ``` ## Directives Although GraphQL-Ruby doesn't have special handling for directives in the SDL, you can build custom behavior in your own app. If part of the schema had a directive, you can access it using `.ast_node.directives`. For example: ```ruby schema = GraphQL::Schema.from_definition <<-GRAPHQL type Query @flagged { secret: Boolean @privacy(secret: true) } GRAPHQL pp schema.query.ast_node.directives.map(&:to_query_string) # => ["@flagged"] pp schema.get_field("Query", "secret").ast_node.directives.map(&:to_query_string) # => ["@privacy(secret: true)"] ``` See {{ "GraphQL::Language::Nodes::Directive" | api_doc }} for available methods. graphql-ruby-1.11.10/guides/search.html000066400000000000000000000045031414121453000177000ustar00rootroot00000000000000--- layout: default title: Search ---

Search:

graphql-ruby-1.11.10/guides/subscriptions/000077500000000000000000000000001414121453000204525ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/subscriptions/ably_implementation.md000066400000000000000000000255611414121453000250410ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Subscriptions title: Ably Implementation desc: GraphQL subscriptions over Ably index: 7 pro: true --- [GraphQL Pro](https://graphql.pro) includes a subscription system based on [Redis](https://redis.io) and [Ably](https://ably.io) which works with any Ruby web framework. After creating an app on Ably, you can hook it up to your GraphQL schema. - [How it Works](#how-it-works) - [Ably setup](#ably-setup) - [Database setup](#database-setup) - [Schema configuration](#schema-configuration) - [Execution configuration](#execution-configuration) - [Webhook configuration](#webhook-configuration) - [Authorization](#authorization) - [End-to-end encryption](#encryption) - [Serializing context](#serializing-context) - [Dashboard](#dashboard) - [Development tips](#development-tips) - [Client configuration](#client-configuration) ## How it Works This subscription implementation uses a hybrid approach: - __Your app__ takes GraphQL queries an runs them - __Redis__ stores subscription data for later updates - __Ably__ sends updates to subscribed clients So, the lifecycle goes like this: - A `subscription` query is sent by HTTP Post to your server (just like a `query` or `mutation`) - The response contains a Ably channel ID (as an HTTP header) which the client may subscribe to - The client opens that Ably channel - When the server triggers updates, they're delivered over the Ably channel - When the client unsubscribes, the server receives a webhook and responds by removing its subscription data Here's another look: ``` 1. Subscription is created in your app HTTP POST .----------> write to Redis 📱 ⚙️ -----> 💾 <---------' X-Subscription-ID: 1234 2. Client opens a connection to Ably websocket 📱 <---------> ☁️ 3. The app sends updates via Ably ⚙️ ---------> ☁️ ------> 📱 POST update (via gem) (via websocket) 4. When the client unsubscribes, Ably notifies the app webhook ⚙️ <-------- ☁️ (disconnect) 📱 ``` By using this configuration, you can use GraphQL subscriptions without hosting a push server yourself! ## Ably setup Add `ably-rest` to your `Gemfile`: ```ruby gem 'ably-rest' ``` and `bundle install`. ## Database setup Subscriptions require a _persistent_ Redis database, configured with: ```sh maxmemory-policy noeviction # optional, more durable persistence: appendonly yes ``` Otherwise, Redis will drop data that doesn't fit in memory (read more in ["Redis persistence"](https://redis.io/topics/persistence)). If you're already using Redis in your application, see ["Storing Data in Redis"](https://www.mikeperham.com/2015/09/24/storing-data-with-redis/) for options to isolate data and tune your configuration. ## Schema configuration Add `redis` to your `Gemfile`: ```ruby gem 'redis' ``` and `bundle install`. Then create a Redis instance: ```ruby # for example, in an initializer: $graphql_subscriptions_redis = Redis.new # default connection ``` Then, that Redis client is passed to the Subscription configuration: ```ruby class MySchema < GraphQL::Schema use GraphQL::Pro::AblySubscriptions, redis: $graphql_subscriptions_redis, ably: Ably::Rest.new(key: ABLY_API_KEY) end ``` That connection will be used for managing subscription state. All writes to Redis are prefixed with `graphql:sub:`. There are also two configurations for managing persistence: - `stale_ttl_s:` expires subscription data after the given number of seconds without any update. After `stale_ttl_s` has passed, the data will expire from Redis. Each time a subscription receives an update, its TTL is refreshed. (Generally, this isn't required because the backend is built to clean itself up. But, if you find that Redis is collecting stale queries, you can set them to expire after some very long time as a safeguard.) - `cleanup_delay_s:` prevents deleting a subscription during those first seconds after it's created. Usually, this isn't necessary, but if you observe latency between the subscription's initial response and the client's subscription to the delivery channel, you can set this configuration to account for it. ## Execution configuration During execution, GraphQL will assign a `subscription_id` to the `context` hash. The client will use that ID to listen for updates, so you must return the `subscription_id` in the response headers. Return `result.context[:subscription_id]` as the `X-Subscription-ID` header. For example: ```ruby result = MySchema.execute(...) # For subscriptions, return the subscription_id as a header if result.subscription? response.headers["X-Subscription-ID"] = result.context[:subscription_id] end render json: result ``` This way, the client can use that ID as a Ably channel. For __CORS requests__, you need a special header so that clients can read the custom header: ```ruby if result.subscription? response.headers["X-Subscription-ID"] = result.context[:subscription_id] # Required for CORS requests: response.headers["Access-Control-Expose-Headers"] = "X-Subscription-ID" end ``` Read more here: ["Using CORS"](https://www.html5rocks.com/en/tutorials/cors/). ## Webhook configuration Your server needs to receive webhooks from Ably when clients disconnect. This keeps your local subscription database in sync with Ably. ### Server *Note: if you're setting up in a development environment you should follow the [Developing with webhooks](#Developing-with-webhooks) section first* Mount the Rack app for handling webhooks from Ably. For example, on Rails: ```ruby # config/routes.rb # Include GraphQL::Pro's routing extensions: using GraphQL::Pro::Routes Rails.application.routes.draw do # ... # Handle webhooks for subscriptions: mount MySchema.ably_webhooks_client, at: "/ably_webhooks" end ``` ### Ably 1. Go to the Ably dashboard 2. Click on your application. 3. Select the "Reactor" tab 4. Click on the "+ New Reactor Rule" button 5. Click on the "Choose" button for "Reactor Event" 6. Click on the "Choose" button for "WebHooks" 7. Enter your url (including the webhooks path from above) in the URL field. 8. Select "Batch request" for "Request Mode" 9. Under "Source" select "Channel Lifecycle" 10. Under "Sign with key" select the API Key prefix that matches the prefix of the ABLY_API_KEY you provided. 11. Click "Create" ## Authorization You can use Ably's [token authentication](https://www.ably.io/documentation/realtime/authentication#token-authentication) by implementing an endpoint in your app, for example: ```ruby class AblyController < ActionController::Base def auth render status: 201, json: ably_rest_client.auth.create_token_request( capability: { '*' => ['presence', 'subscribe'] }, client_id: 'graphql-subscriber', ) end end ``` [Ably's tutorial](https://www.ably.io/tutorials/webhook-chuck-norris#tutorial-step-4) also demonstrates some of the setup for this. ## Encryption You can use Ably's [end-to-end encryption](https://www.ably.io/documentation/realtime/encryption) with GraphQL subscriptions. To enable it, add `cipher_base:` to your setup: ```ruby use GraphQL::Pro::AblySubscriptions, redis: $graphql_subscriptions_redis, ably: Ably::Rest.new(key: ABLY_API_KEY), # Add `cipher_base:` to enable end-to-end encryption cipher_base: "ff16381ae2f2b6c6de6ff696226009f3" ``` (Any random string will do, eg `ruby -e "require 'securerandom'; puts SecureRandom.hex"`.) Also, return a header to client so that it can decrypt subscription updates. The key is put in `context[:ably_cipher_base64]`, and `graphql-ruby-client` expects to find it in the `X-Subscription-Key` header: ```ruby result = MySchema.execute(...) # For subscriptions, return the subscription_id as a header if result.subscription? response.headers["X-Subscription-ID"] = result.context[:subscription_id] # Also return the encryption key so that clients # can decode subscription updates response.headers["X-Subscription-Key"] = result.context[:ably_cipher_base64] end ``` (Also, if you're using CORS requests, update `Access-Control-Expose-Headers` to include `X-Subscription-Key`) With this setup, - `GraphQL::Pro::AblySubscriptions` will generate per-subscription keys (using `cipher_base` and the subscription ID) and use them to encrypt Ably payloads - Those keys will be returned to clients in `X-Subscription-Key` - Clients will use those keys to decrypt incoming messages __Backwards compatibility:__ `GraphQL::Pro::AblySubscriptions` will only encrypt payloads whose `query.context[:ably_cipher_base64]` is present. Any subscriptions created _before_ `cipher_base:` was added to the setup will _not_ be encrypted. (There was no key to encrypt them, and clients don't have a key to _decrypt_ them!) ## Serializing Context Since subscription state is stored in the database, then reloaded for pushing updates, you have to serialize and reload your query `context`. By default, this is done with {{ "GraphQL::Subscriptions::Serialize" | api_doc }}'s `dump` and `load` methods, but you can provide custom implementations as well. To customize the serialization logic, create a subclass of `GraphQL::Pro::AblySubscriptions` and override `#dump_context(ctx)` and `#load_context(ctx_string)`: ```ruby class CustomSubscriptions < GraphQL::Pro::AblySubscriptions def dump_context(ctx) context_hash = ctx.to_h # somehow convert this hash to a string, return the string end def load_context(ctx_string) # Given the string from the DB, create a new hash # to use as `context:` end end ``` Then, use your _custom_ subscriptions class instead of the built-in one for your schema: ```ruby class MySchema < GraphQL::Schema # Use custom subscriptions instead of GraphQL::Pro::AblySubscriptions # to get custom serialization logic use CustomSubscriptions, ... end ``` That gives you fine-grained control of context reloading. ## Dashboard You can monitor subscription state in the {% internal_link "GraphQL-Pro Dashboard", "/pro/dashboard" %}: {{ "/subscriptions/redis_dashboard_1.png" | link_to_img:"Redis Subscription Dashboard" }} {{ "/subscriptions/redis_dashboard_2.png" | link_to_img:"Redis Subscription Detail" }} ## Development Tips #### Clear subscription data At any time, you can reset your subscription database with the __"Reset"__ button in the {% internal_link "GraphQL-Pro Dashboard", "/pro/dashboard" %}, or in Ruby: ```ruby # Wipe all subscription data from the DB: MySchema.subscriptions.clear ``` #### Developing with webhooks To receive webhooks in development, you can [use ngrok](https://www.ably.io/tutorials/webhook-chuck-norris). It gives you a public URL which you can setup with Ably, then any hooks delivered to that URL will be forwarded to your development environment. ## Client configuration Install the [Ably JS client](https://github.com/ably/ably-js) then see docs for {% internal_link "Apollo Client", "/javascript_client/apollo_subscriptions" %}. graphql-ruby-1.11.10/guides/subscriptions/action_cable_implementation.md000066400000000000000000000012721414121453000265060ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Subscriptions title: Action Cable Implementation desc: GraphQL subscriptions over ActionCable index: 4 --- [ActionCable](https://guides.rubyonrails.org/action_cable_overview.html) is a great platform for delivering GraphQL subscriptions on Rails 5+. It handles message passing (via `broadcast`) and transport (via `transmit` over a websocket). To get started, see examples in the API docs: {{ "GraphQL::Subscriptions::ActionCableSubscriptions" | api_doc }}. See client usage for {% internal_link "Apollo Client", "/javascript_client/apollo_subscriptions" %} or {% internal_link "Relay Modern", "/javascript_client/relay_subscriptions" %}. graphql-ruby-1.11.10/guides/subscriptions/broadcast.md000066400000000000000000000110061414121453000227340ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Subscriptions title: Broadcasts desc: Delivering the same GraphQL result to multiple subscribers index: 3 --- GraphQL-Ruby 1.11+ introduced a new algorithm for tracking subscriptions and delivering updates, _broadcasts_. A broadcast is a subscription update which is executed _once_, then delivered to _any number_ of subscribers. This reduces the time your server spends running GraphQL queries, since it doesn't have to re-run the query for every subscriber. But, __take care__: this approach risks leaking information to subscribers who shouldn't receive it. ## Setup To enable broadcasts, add `broadcast: true` to your subscription setup: ```ruby class MyAppSchema < GraphQL::Schema # ... use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST use SomeSubscriptionImplementation, broadcast: true # <---- end ``` Then, any broadcastable field can be configured with `broadcastable: true`: ```ruby field :name, String, null: false, broadcastable: true ``` When a subscription comes in where _all_ of its fields are `broadcastable: true`, then it will be handled as a broadcast. Additionally, you can set `default_broadcastable: true`: ```ruby class MyAppSchema < GraphQL::Schema # ... use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST use SomeSubscriptionImplementation, broadcast: true, default_broadcastable: true # <---- end ``` With this setting, fields are broadcastable by default. Only a field with `broadcastable: false` in its configuration will cause a subscription to be handled on a subscriber-by-subscriber basis. ## What fields are broadcastable? GraphQL-Ruby can't infer whether a field is broadcastable or not. You must configure it explicitly with `broadcastable: true` or `broadcastable: false`. (The subscription plugin also accepts `default_broadcastable: true|false`.) A field is broadcastable if _all clients who request the field will see the same value_. For example: - General facts: celebrity names, laws of physics, historical dates - Public information: object names, document updated-at timestamps, boilerplate info For fields like this, you can add `broadcastable: true`. A field is __not broadcastable__ if its value is different for different clients. For example: - __Viewer-specific information:__ if a field is specifically viewer-based, then it can't be broadcasted to other viewers. For example, `discussion { viewerCanModerate }` might be true for a moderator, but it shouldn't be broadcasted to other viewers. - __Context-specific information:__ if a field's value takes the request context into consideration, it shouldn't be broadcasted. For example, IP addresses or HTTP header values probably can't be broadcasted. If a field reflects the viewer's timezone, it can't be broadcasted. - __Restricted information:__ if some viewers see one value, while other viewers see a different value, then it's not broadcastable. Broadcasting this data might leak private information to unauthorized clients. (This includes filtered lists: if the filtering is viewer-by-viewer, it's not broadcastable.) - __Fields with side effects:__ if the system requires a side effect (eg, logging a metric, updating a database, incrementing a counter) whenever a resolver is executed, it's not a good candidate for broadcasting because some executions will be optimized away. These fields can be tagged with `broadcastable: false` so that GraphQL-Ruby will handle them on a subscriber-by-subscriber basis. If you want to use subscriptions but have a lot of non-broadcastable fields in your schema, consider building a new set of subscription fields with limited access to other schema objects. Instead, optimize those subscriptions for broacastability. ## Under the Hood GraphQL-Ruby determines which subscribers can receive a broadcast by inspecting: - __Query string__. Only exactly-matching query strings will receive the same broadcast. - __Variables__. Only exactly-matching variable values will receive the same broadcast. - __Field and Arguments__ given to `.trigger`. They must match the ones initially sent when subscribing. (Subscriptions always worked this way.) - __Subscription scope__. Only clients with exactly-matching subscription scope can receive the same broadcasts. So, take care to {% internal_link "set subscription_scope", "subscriptions/subscription_classes#scope" %} whenever a subscription should be implicitly scoped! (See {{ "GraphQL::Subscriptions::Event#fingerprint" | api_doc }} for the implementation of broadcast fingerprints.) graphql-ruby-1.11.10/guides/subscriptions/implementation.md000066400000000000000000000033601414121453000240230ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Subscriptions title: Implementation desc: Subscription execution and delivery index: 3 --- The {{ "GraphQL::Subscriptions" | api_doc }} plugin is a base class for implementing subscriptions. Each method corresponds to a step in the subscription lifecycle. See the API docs for method-by-method documentation: {{ "GraphQL::Subscriptions" | api_doc }}. Also, see the {% internal_link "Pusher implementation guide", "subscriptions/pusher_implementation" %}, the {% internal_link "Ably implementation guide", "subscriptions/ably_implementation" %}, the {% internal_link "ActionCable implementation guide", "subscriptions/action_cable_implementation" %} or {{ "GraphQL::Subscriptions::ActionCableSubscriptions" | api_doc }} docs for an example implementation. ## Considerations Every Ruby application is different, so consider these points when implementing subscriptions: - Is your application single-process or multiprocess? Single-process applications can store state in memory while multiprocess applications need a message broker to keep all processes up-to-date. - What components of your application can be used for persistence and message passing? - How will you deliver push updates to subscribed clients? (For example, websockets, ActionCable, Pusher, webhooks, or something else?) - How will you handle [thundering herd](https://en.wikipedia.org/wiki/Thundering_herd_problem)s? When an event is triggered, how will you manage database access to update clients without swamping your system? ## Broadcasts _Broadcasting_ updates to multiple subscribers is supported by GraphQL-Ruby, but requires implementation-specific work, see more in the {% internal_link "Broadcast guide", "subscriptions/broadcast" %}. graphql-ruby-1.11.10/guides/subscriptions/overview.md000066400000000000000000000051011414121453000226370ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Subscriptions title: Overview desc: Introduction to Subscriptions in GraphQL-Ruby index: 0 --- _Subscriptions_ allow GraphQL clients to observe specific events and receive updates from the server when those events occur. This supports live updates, such as websocket pushes. Subscriptions introduce several new concepts: - The __Subscription type__ is the entry point for subscription queries - __Subscription classes__ are resolvers for handing initial subscription requests and subsequent updates - __Triggers__ begin the update process - The __Implementation__ provides application-specific methods for executing & delivering updates. - __Broadcasts__ can send the same GraphQL result to any number of subscribers. ### Subscription Type `subscription` is an entry point to your GraphQL schema, like `query` or `mutation`. It is defined by your `SubscriptionType`, a root-level `GraphQL::Schema::Object`. Read more in the {% internal_link "Subscription Type guide", "subscriptions/subscription_type" %}. ### Subscription Classes {{ "GraphQL::Schema::Subscription" | api_doc }} is a resolver class with subscription-specific behaviors. Each subscription field should be implemented by a subscription class. Read more in the {% internal_link "Subscription Classes guide", "subscriptions/subscription_classes" %} ### Triggers After an event occurs in our application, _triggers_ begin the update process by sending a name and payload to GraphQL. Read more in the {% internal_link "Triggers guide","subscriptions/triggers" %}. ### Implementation Besides the GraphQL component, your application must provide some subscription-related plumbing, for example: - __state management__: How does your application keep track of who is subscribed to what? - __transport__: How does your application deliver payloads to clients? - __queueing__: How does your application distribute the work of re-running subscription queries? Read more in the {% internal_link "Implementation guide", "subscriptions/implementation" %} or check out the {% internal_link "ActionCable implementation", "subscriptions/action_cable_implementation" %}, {% internal_link "Pusher implementation", "subscriptions/pusher_implementation" %} or {% internal_link "Ably implementation", "subscriptions/ably_implementation" %}. ### Broadcasts By default, the subscription implementations listed above handle each subscription in total isolation. However, this behavior can be optimized by setting up broadcasts. Read more in the {% internal_link "Broadcast guide", "subscriptions/broadcast" %}. graphql-ruby-1.11.10/guides/subscriptions/pusher_implementation.md000066400000000000000000000220641414121453000254130ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Subscriptions title: Pusher Implementation desc: GraphQL subscriptions over Pusher index: 6 pro: true --- [GraphQL Pro](https://graphql.pro) includes a subscription system based on [Redis](https://redis.io) and [Pusher](https://pusher.com) which works with any Ruby web framework. After creating an app on Pusher and [configuring the Ruby gem](https://github.com/pusher/pusher-http-ruby#global), you can hook it up to your GraphQL schema. - [How it Works](#how-it-works) - [Database setup](#database-setup) - [Schema configuration](#schema-configuration) - [Execution configuration](#execution-configuration) - [Webhook configuration](#webhook-configuration) - [Authorization](#authorization) - [Serializing context](#serializing-context) - [Dashboard](#dashboard) - [Development tips](#development-tips) - [Client configuration](#client-configuration) ## How it Works This subscription implementation uses a hybrid approach: - __Your app__ takes GraphQL queries an runs them - __Redis__ stores subscription data for later updates - __Pusher__ sends updates to subscribed clients So, the lifecycle goes like this: - A `subscription` query is sent by HTTP Post to your server (just like a `query` or `mutation`) - The response contains a Pusher channel ID (as an HTTP header) which the client may subscribe to - The client opens that Pusher channel - When the server triggers updates, they're delivered over the Pusher channel - When the client unsubscribes, the server receives a webhook and responds by removing its subscription data Here's another look: ``` 1. Subscription is created in your app HTTP POST .----------> write to Redis 📱 ⚙️ -----> 💾 <---------' X-Subscription-ID: 1234 2. Client opens a connection to Pusher websocket 📱 <---------> ☁️ 3. The app sends updates via Pusher ⚙️ ---------> ☁️ ------> 📱 POST update (via gem) (via websocket) 4. When the client unsubscribes, Pusher notifies the app webhook ⚙️ <-------- ☁️ (disconnect) 📱 ``` By using this configuration, you can use GraphQL subscriptions without hosting a push server yourself! ## Database setup Subscriptions require a _persistent_ Redis database, configured with: ```sh maxmemory-policy noeviction # optional, more durable persistence: appendonly yes ``` Otherwise, Redis will drop data that doesn't fit in memory (read more in ["Redis persistence"](https://redis.io/topics/persistence)). If you're already using Redis in your application, see ["Storing Data in Redis"](https://www.mikeperham.com/2015/09/24/storing-data-with-redis/) for options to isolate data and tune your configuration. ## Schema configuration Add `redis` to your `Gemfile`: ```ruby gem 'redis' ``` and `bundle install`. Then create a Redis instance: ```ruby # for example, in an initializer: $graphql_subscriptions_redis = Redis.new # default connection ``` Then, that Redis client is passed to the Subscription configuration: ```ruby class MySchema < GraphQL::Schema use GraphQL::Pro::PusherSubscriptions, redis: $graphql_subscriptions_redis end ``` That connection will be used for managing subscription state. All writes to Redis are prefixed with `graphql:sub:`. There are also two configurations for managing persistence: - `stale_ttl_s:` expires subscription data after the given number of seconds without any update. After `stale_ttl_s` has passed, the data will expire from Redis. Each time a subscription receives an update, its TTL is refreshed. (Generally, this isn't required because the backend is built to clean itself up. But, if you find that Redis is collecting stale queries, you can set them to expire after some very long time as a safeguard.) - `cleanup_delay_s:` prevents deleting a subscription during those first seconds after it's created. Usually, this isn't necessary, but if you observe latency between the subscription's initial response and the client's subscription to the delivery channel, you can set this configuration to account for it. ## Execution configuration During execution, GraphQL will assign a `subscription_id` to the `context` hash. The client will use that ID to listen for updates, so you must return the `subscription_id` in the response headers. Return `result.context[:subscription_id]` as the `X-Subscription-ID` header. For example: ```ruby result = MySchema.execute(...) # For subscriptions, return the subscription_id as a header if result.subscription? response.headers["X-Subscription-ID"] = result.context[:subscription_id] end render json: result ``` This way, the client can use that ID as a Pusher channel. For __CORS requests__, you need a special header so that clients can read the custom header: ```ruby if result.subscription? response.headers["X-Subscription-ID"] = result.context[:subscription_id] # Required for CORS requests: response.headers["Access-Control-Expose-Headers"] = "X-Subscription-ID" end ``` Read more here: ["Using CORS"](https://www.html5rocks.com/en/tutorials/cors/). ## Webhook configuration Your server needs to receive webhooks from Pusher when clients disconnect. This keeps your local subscription database in sync with Pusher. In the Pusher web UI, Add a webhook for "Channel existence" {{ "/subscriptions/pusher_webhook_configuration.png" | link_to_img:"Pusher Webhook Configuration" }} Then, mount the Rack app for handling webhooks from Pusher. For example, on Rails: ```ruby # config/routes.rb # Include GraphQL::Pro's routing extensions: using GraphQL::Pro::Routes Rails.application.routes.draw do # ... # Handle Pusher webhooks for subscriptions: mount MySchema.pusher_webhooks_client, at: "/pusher_webhooks" end ``` This way, we'll be kept up-to-date with Pusher's unsubscribe events. ## Authorization To ensure the privacy of subscription updates, you should use a [private channel](https://pusher.com/docs/client_api_guide/client_private_channels) for transport. To use a private channel, add a `channel_prefix:` key to your query context: ```ruby MySchema.execute( query_string, context: { # If this query is a subscription, use this prefix for the Pusher channel: channel_prefix: "private-user-#{current_user.id}-", # ... }, # ... ) ``` That prefix will be applied to GraphQL-related Pusher channel names. (The prefix should begin with `private-`, as required by Pusher.) Then, in your [auth endpoint](https://pusher.com/docs/authenticating_users#implementing_private_endpoints), you can assert that the logged-in user matches the channel name: ```ruby if params[:channel_name].start_with?("private-user-#{current_user.id}-") # success, render the auth token else # failure, render unauthorized end ``` ## Serializing Context Since subscription state is stored in the database, then reloaded for pushing updates, you have to serialize and reload your query `context`. By default, this is done with {{ "GraphQL::Subscriptions::Serialize" | api_doc }}'s `dump` and `load` methods, but you can provide custom implementations as well. To customize the serialization logic, create a subclass of `GraphQL::Pro::PusherSubscriptions` and override `#dump_context(ctx)` and `#load_context(ctx_string)`: ```ruby class CustomSubscriptions < GraphQL::Pro::PusherSubscriptions def dump_context(ctx) context_hash = ctx.to_h # somehow convert this hash to a string, return the string end def load_context(ctx_string) # Given the string from the DB, create a new hash # to use as `context:` end end ``` Then, use your _custom_ subscriptions class instead of the built-in one for your schema: ```ruby class MySchema < GraphQL::Schema # Use custom subscriptions instead of GraphQL::Pro::PusherSubscriptions # to get custom serialization logic use CustomSubscriptions, redis: $redis end ``` That gives you fine-grained control of context reloading. ## Dashboard You can monitor subscription state in the {% internal_link "GraphQL-Pro Dashboard", "/pro/dashboard" %}: {{ "/subscriptions/redis_dashboard_1.png" | link_to_img:"Redis Subscription Dashboard" }} {{ "/subscriptions/redis_dashboard_2.png" | link_to_img:"Redis Subscription Detail" }} ## Development Tips #### Clear subscription data At any time, you can reset your subscription database with the __"Reset"__ button in the {% internal_link "GraphQL-Pro Dashboard", "/pro/dashboard" %}, or in Ruby: ```ruby # Wipe all subscription data from the DB: MySchema.subscriptions.clear ``` #### Developing with Pusher webhooks To receive Pusher's webhooks in development, Pusher [suggests using ngrok](https://support.pusher.com/hc/en-us/articles/203112227-Developing-against-and-testing-WebHooks). It gives you a public URL which you can setup with Pusher, then any hooks delivered to that URL will be forwarded to your development environment. ## Client configuration Install the [Pusher JS client](https://github.com/pusher/pusher-js) then see docs for {% internal_link "Apollo Client", "/javascript_client/apollo_subscriptions" %} or {% internal_link "Relay Modern", "/javascript_client/relay_subscriptions" %}. graphql-ruby-1.11.10/guides/subscriptions/pusher_webhook_configuration.png000066400000000000000000001236421414121453000271430ustar00rootroot00000000000000PNG  IHDRTh! iCCPICC ProfileHWXS[RIhH #E^%BBP# ]Qq-,6"u`CM N9g3fPefAĤdP(@DE(MHk֒X:_Eq@ N89  t@\ XM @%8]5$8U6~{@t%YtGQV ''ͅ>V99 V"Cl]LfaY.R!Eroa-C-YaLM *\8"?f l0!f|؞-B{4+ǩYh ;"\gy/dD16ia 3bd<3!eń}fEFMFl0h^ - 束,yQ\ 0X]rߒ(=V3v@T3{5lG(\rbep?X@ [*2sy6trͨGtD1 |Q=^}z1&4a07bqWǽ(hԸIVYe5EkVYjsƗ?86m=O{vNN&:L]oLuzz|z'`|X٬J֠~X~AAACazvA#=FS]377~obj`̤乩iii}3YYusy. "â%jdɷbmErXZݲYXX7X0mmlm^N033fO̻XձqJ'''$NH\x9I+ԒLJOޕ<45`ꆩ}L9tghȞqlLC)=)ّZPjHAg#כy=OH_ޟQ1W_egn|U5ݘCI9*Pd ҝ5gVwenInO[ކAapMcNLهk1wgqןd~" S/2\ToqKK^d[T^vibŏ DDXrk+VlZ[z̶JK?\ȪUV׬!kr&kZZ_톙.VLغQ2eѦ5>WeTݨnܬy[[xߪlmmmo5AQq~֮]_u=wwߣgu n;mo>}-ood65כr9GGJM=-I-GC淺6cV/>>rܓO=n~tg<v¹s?qBEG/\jtñtl|˵{R^WO]vz7"ntߌyִ[=ɾn{>P~PPa?s׿Q̣{9_<=W޳Gߋ%y18Jj7oN|>5]λ4?Oφg&}bk#9##l![z`Cx]= *^RAdE) gRq3J l`/9zzaE`/E7‡7:Z"22e'${y;D|\:;G'kꞍC pHYs%%IR$iTXtXML:com.adobe.xmp 360 1620 1 QH@{@IDATxUHHtwt !v- *t HAlE1FJP@Z{v]g?oo̽{kfΜstPHHHHHHHHHHHHHw 7 TF            P IHHHHHHHHHHH $@$@$@$@$@$@$@$@$@$@$@$*q3 P{HHHHHHHHHHHB% n&           *Tx @T$@$@$@$@$@$@$@$@$@$@$@$@            @ HHHHHHHHHHHP=@$@$@$@$@$@$@$@$@$@$@$@P7 *HHHHHHHHHHHH *Tf           B  @J@L$@$@$@$@$@$@$@$@$@$@$@T            P IHHHHHHHHHHH $@$@$@$@$@$@$@$@$@$@$@$*q3 P{HHHHHHHHHHHB% n&           *Tx @T$@$@$@$@$@$@$@$@$@$@$@$@            @ HHHHHHHHHHHP=@$@$@$@$@$@$@$@$@$@$@$@P7 *HHHHHHHHHHHH *Tf           B  @J@L$@$@$@$@$@$@$@$@$@$@$@T            P IHHHHHHHHHHH $@$@$@$@$@$@$@$@$@$@$@$*q3 P{HHHHHHHHHHHB% n&           *Tx @T$@$@$@$@$@$@$@$@$@$@$@$,lظEΝ.GRH!>p֬N4L6.#G9e=P+U &IA$ fl WhBr_ *T;w (Zƍ'QM[yHZ=Һ%mP    ண P (!    x g.*U,.],ۙG`AaeÆ-vcNSXaI6v.$^ׯ_}eDyf$@$@$@$@$liѺ s 1LLC$@$@$@$@"g*˕[nE4o]R% Nh&V`&vљntq+jIJ*.9.[%KW \A2IR@>u}3$@$@$@$@$pH.tx]G1CHߴ~:ziGAHHHHH LxJ ټe ovʵk$YDgCGsT1"3 @֬I;x\=cҭ@پci߰ J\    Y L:z<.      Dh*n5U(T6wdZ@;*M.],;v!egSƌp"իhrYj?~RRH.s甆 jK2%=Z.@[eoL+&K4}eXIk*ۮ8W^sɥ>I<<J \|Ed,wr"ugvSĹ9|wZԯW;I.鞦 BEs"^;'    P@l+WeOֹLwW|yi?;ǐ4qk/z+2,:!a Z>oHɝm!   H,(=P˗X_`5qT0٢[RAG C :"-Hg?Wj)UVuyL*2Y6 a)ٲe1+U[oդ㝡:y2`QH!RtYL LI#h% ,t uVz~GN8Ҽ7+T)sLҿϛfDn!ΕÙ iJ~9X[=W;1uw_xLɑ#~ǥVjwMQkG:`JZJ}27L= Dh`Vd(oYՊ)bZ:|N( *pC(`aRH4.]i-] 4Oe ;>V + 1rʻ . $>qj|RhfUt>|$*ٲfGeTZ'}TarnBxGo,QLRJE67~̜@&LL-E IE÷zN^z԰kᆫ_FsL& fiO] 8֬ 0t$[` Pdʔ,c4ԗ_ }?Lƾ;lÿu q`4ר1;}{&P*ŧ\P=Vd{[hNwզq,pHHHHnbPdt|kGJTz~Yrއ*+.]r,Qw`y1rtX=^Ab)Mf%pֵӾ>= [Ǵ*'zt}EҧOgcؠwȪo.0pZo Z&od>GOO+2̝QjJ4p=ֳ{)R6kܪR+X0 3-6ú\;g FC=*N!   HB(]qT6O~q+ K܂ ^:!h`Xb+5,ܭL1_ۙx(o+v2s!_hk.΂kVl ;f PWw)؎C4~}X8G+1k˰]j:?xcyWZhhϟn*5o 7E[RZ%^r#wL$@$@$@$@II0+k׮}JHb;1ϱ)Gbk66o71(6t@e *N0wZX>nI2Mc@,mgݢa`XZ&˝P`#ڥhY٥. ( m ֬ujW7 W    8PxdLE7 x]r?0Z8yJn5nw_5Р[[E *%4 \D%)SFponN>c,f|7΅r~mZڹsY,Y-lPlFuy(jnXZxekVB?*>&    (@HXeD&%Ks6jbǟؓ.{5֤ x E!pug)LaiRxə Q-= , Tw5#_hVZo`w _]vZ4+Væ&b|SlV-Y|ug>ׁB$@$@$@$ ĹB%U[6`u-V!SV.Vʫ )n^ټw?-f[l{ Px4N^ΩȠP9+W`>y /]lV߯p.6aD#!&yw!u֧W0돪ֆ(q=Mny΂ Gޡ 9ob36u=`V $SG/#zuj3v/VęGAwV,]KmA|d0˗B%~%hş/4ؘ0<~ڴW&>{=IHHH'8W#Z7^r_nu5e]da~M|( HSpA3N%O63׮#ɓmH$#{=Lǡܹ́ޛ峮mI^gB06|P~]0 ,XAI0AkrIɑ=a]UHYɓ'a O=eAݧZΝOrg-x1̽qt^2ob3g|_-7LP8}w*ނjU+6իr.܋6. $^bÚ )faauc+W(YGg >l{,O{XQ\rb2U+mk.mZ7sg`e}{%s-^nr]GBZGV`L]MZvhO|Yi!d1>v() PFu~Q8Or| y7kz,\Lm~3J!jRXaɜ)Z 6ˑaV>ȏ;Wewo=1YҪeS_IڝA +jlM $@$@$@$@$" \>@K=֨^E&$Z~^HkbdEKjeވ _~TkwGtiPFtVIQlUvɓӴq ƍʧv:qS] gYj ג"E %1M)jGcᬌ t14f̜Jy77S[b]{QO<ߨ |g {wPbиΐwF \Kj~UDUjcw~d~4Q3R]F-_ƽc>W9@$@$@$@$@ "/^tCɰnʀマlG|KA'e֝x C#i-Q_z*ejz墪@(_(:L8; 6oCU|や)Y깋9_Z%Yrӝn{|գ-[,tn=i?ۖvy    DL ^\~' (N| V`b-J:KB$VQMa0jx: uL2dPO)T(c]RݿC!R݂ n9;nhs{3N!^3sd|00kԈZdɒG\~D})ߦiؠ@;_/S    H,k wI$qC0Pw׌UPv+# 2>A{=uTr/Ay˺B~#If:J̙d>PRU5oJdۖRjؽ7v)L$@$@$@@*O'NFG>#{TpܧN}ȯLZ;v¸,BSde$odd\=[Vɞ=IK$@$@$@$@$<S/W׻59խ{Z%p'J:ܰ `rqi)*CX`O1*&A^ԝ0\z($@$@$@$@M&"Xfӆ/a[&aA_t$&y`ՀLz!    =S/Abq%ψ_t$P^RAebrFu?_T:@/*3- @!h* HIHHHHH1k$Uɳ,4 ֢_\O$@$@$@$@Q%@JT1= @@O6_ivS    3TjHHHHHH *&K\RҦ=ܦ+lv:UiIHHHH/Ds @!pU  HHHH-T>M$@$@$@$@$@$))H7 q'HHHHHHHHHHH(*Ta *T5D$@$@$@$@$@$@$@$@$@$@$@ P@/HHHHHHHHHHH Pw'           J zx$@$@$@$@$@$@$@$@$@$@$@qG c= $PT$ &          ;TkHHHHHHHHHHH HWǽC$@$@$@$@$p('Mzd<,     -T"í$@$@$@$@$@$@$@$@$@$@$@$ qfbYsD%) wbsy$@$@$@$@$X B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH B%^Y @P2,HHHHHHHHHHH HXOE$@$@$@$@$@ 3K".ߥagqkr)E/"3%Ic&    K {x$@$@$@$@$@EUl=q]NHu^_sa[.҈ΖDRB$@$@$@$@$P],HHHHHH9|VK/bJeJ"?"G&3+\wҸ.RdOTr.)n SĄ)WH%c**Ux)H \|Y]NG8@qB KW#jﱸ}O { ]ׯ_$Ib~+9g_ʁW_}&M%3o 6J+٥d8'HҤICR >P\ _ $ $@l3 @@irI4iHT$E+WŋhI<_@ *d-3s@!(\KI#@ ?׬㋝eݺ 3䙧IՖ*:0QJ,WL[$3ݴSmc8/sҾf+/sQ߬xoh\JLߞP @#e҃I0e3";N+G%d:F"-P(Fb6$Quvu9.%$|`򯖝TjRF\C$>${v&9u[LLBd`V;P s0ڵkrU9s~䐱h)RΓ7O'x#(s$`P7̙s䴚Vx(Q瞂I3MrɒrYu7ȂI737@̟Д}ݰ   Kn`b);OI*R| 铫˯H2%YŔpR|: g.ٱmΕKr!)Յ~pq-{BJ(TRN-YdcǎJYܸO+|4C PM|ײc.yS{ `@⠀yOzu-4~3s Đ* .KBcX @ =bX7_P*IǗ1>bȃ]7eQ*INT@Wtuc u͓LfMJ/"ŊyFl? ӧK+ŋVdM $J 9u7g jQ-kw\G!'@Jȑ p,[Rʗ/+ݦ}H0i|d*kkOis_ːɂHHHH *@7_L1$%*Zk0uI,q`[O$r9\C8{Ew:he?ϒ)(/J oƍ[:*T<8 FgF-ST?Lmߥ)F ,($#e׎mRTIɞ=%|"ʔ E|l>.Wr*ap@bxbpa>|ԙ|֬_q7|i-sk ZoڴٌG~@ru2}ϦSU>RX֝YJShS e#gvizwccb*Yl*Ӥ" IVJ"=GypCq}y^MGT!믿eJIwݰݮ;/3fɱߏʫSFeJSjUVXn.mVK"mG?U̷l&[~sΗK䞦M<;~\V,_{e=r]s%5=41c<2/LkڴͮjZ!l,ΝWS"Rt \ΛP.[.~ 3Q Od#ռܹKfDԨQ]jg&FXUAeu2{<9zY_B9yMD_`ɼ!cɟLSCɭDΏ}˚5a*Ν;>UaOȴieYw'Zha} j[ymN'}o6{J,f5Ž3 O:}Fu<-6öm姟fށgϞ5J,&hMhYu3+WNQ[ԯkc{_et9k>|D $[RhQiٲ1=^]( KG+xnU)x7|+͘1M) $[O\43~fY.h9^Re@R`m!Km?V?IJc{!cI4 Kڴi{,(G#/̣̿Kb%b-\5'_퉭vȒpNp~Cm;:k_ 9zCʨu~}zHX-vXTqN]{icSӹzZ>|tFQÉXZdQ߲Ŵ#*p0 :X'LXW*wT.%-4Dg3ϴQ3J#[D8q4S]n6S̙sO?2 DP;˸vE3&/u({?ȑ36o٪sdǟʨCb*^,]&#Fk6[ ,sDF ,嵛-Io𡍚 ²e+㏧H~= :-w# Ut`/eoJF ݛc/i`@u{nVG ҥ}Wn-#ltF*F~]DG唩S&nz+$B&g=6/ϿMV7lŠ?NRznJtQ~p{C2 j@7oQNYG1n%@9_hY&g}oȁ":ȝ ј ^\' (B#^ڪPMwÞhGס_A}θ{^YxO"sgg<\@Aȸ#Zʷ}鳉2pz7uXtQLuI\?ySgNVy xu[suٴiYp$y՗*)$@$PugKv_"~cܟrelX$B---s 2Qc[/k{(={V)Unu7T#~ϝR<>e;~G=B Ap]e{(e'OM,TXKl ypWӏIunstbg2~#O:%(*ީlP 0rgڛ}Jʒ^; oߡ (>8Y舴gn=U@ICKjw訉jXqyuN^'hʔ)ŋNH}YGZGraΜyo ;d:ܛ4X8ЁsˇN=2Nׂ 21odf8fv=V>kcЃmoqi xWDΟ;~|W-U}<}>(5B-z~~Nqrj(nZja8]T+X)/^(oCgs"̠q`LuF3͓X_({Wz_3 2f̠TnUk{mWߔǿSdm(mjש)I$6lh 굨kE-=9@"OCqN/Ϩ{W/Ev)j`3(hErac4[u_fx-2eJkyd*yQTdWKj Ov#L`Wx^+\F#[_N-PٸqϠY^k׮XyˢEK̪2KKv0r6k,z/ݭtUcN}X͘9[z'4zO|dHAUYkwx(ހ5"UVRTPH @اʍsvvl9yằN{@5CJU^`WLa1 x_z <)Ԩ̝X-4kz{{/TGz#&h 4w74 ]u!-[)QZ7T@7=S4MnG?Ղ_JG|s_ɟ~MQl,I<,e蠜>,e>>ӨK;zvo.9xzTɬm䊚 Xnlz^Y5{>w`%oi Sl|| M u]t=ѩOE{N1`xlVҮozڱsoGF!bcP0Ɋue3R^M# 7gJtPPG:w-Lq͛X]9m3@>G( 9z=_Um 9s:DkCYa SJ[j%2lctvcnr]{c#XJTu2e4cN|ݟ{lO@D݃N}**{f0 ~x|-[6W*S܇Ұa=gy)3z2={eU ;Vйf1a/=ZpϽmFmC448֭[x(Sq֬q5}!pKSp .#VҦMK$P(Ք2&Bl EW'o2ŝw7 ;w:Ptg##Xx| /<2ŝ!; E:|9xpGG,5P8ʟ@yVtPA?Qhct;iV+h4x^64]^1yF3_*! q܂;CF lP{3_F(zg3Bn xO{zj0IkwP( <7PBCb`g1k]<)tPX,q}It=x6 %ȋP7_Q%bd;;PA\N#F @x 48K&GΟHk#?s $,ҧ&ޗFZI-%iJRm1 >/+c3ݮ.,x{ eKJm-|Z71e 9k1tp;xȻQؾ0٤BrwNGNj 20 u+m)yYF|(S~B [L#һPLEfݍcƽ5DWnukkan$(8Ͽf)?ywQ&@;vxJT-FPh"]ác'ˤDl^;Rk*X0zSV`5>o jLMG4"D7nL㜟}1ӑfE;fs<_jA(tZ)S{ͮ2)(}LyGL~L^xҌPG'?N=,3E#gT߁|<.As`ABh)Pzg׾jB f DzŁ*n X 2dL>Z+ HTux6 JJ~oAJ%Yλ4*`o6e?AG*~(7=;?ÝW-ZT:j'XZ Uk.]^reDucA~_p+PŋX&Mi.tnƝwi~49gq(]hYA-\$WΜ6]7@`bJ1``>FCqX? w3g1y*7N#?yww enL}bvcyfebv,ߧ?1bx3ۨ"kӳ;c(CtꚺǨ[YŞ=H>`~0nML=o ŊRѭ:O W/xjSb9+am\jaafy20MLzvb vӂeXMa0JRkfu>T;kNl\[Z!7~̜@>3nJ,Muڴi,60}Ŧ\7+n! -3_v@HBnt+ /}{m=(l~ޛ-4TgTށ|<.As[`@6.]\zxr#ҵ9s|п~+)}kΞqɰ>T ;h%wQOw}G~UG||F}#~/+c:u[[P &ہk׮[9ѿϛlF XFg/)`ΥKVTI~F)}I+U)H6.;͏?6dQ2D YnKGiLA3@i!_y\ 1^F~XBa Z!M[7'Nx1Rpecr^ L+R{H]}Χ6pө32+q ^Sę ,neY?Nϥ3-\%vpsbWl*uӾίËo&y(S$ė GVPa{oyu՘?0;ϑ;!s[`(2c+!-Pj%_:P ݹivV(S߸:j}iWŮ4:|Sl1 @]6i\lɻ]vLSr| ;!=كA0r/-|D!C0(>ҸqjԨtz[(A+Slw`Ҹ 6:Gfnc+z fm  ܪSUmʧ;K~[JeoL5}=eSA#! ٢ȡP:.<tz;#'& "Vb 3:oq׃g vĈ $;|t?`K1k`A~(; ڑB!~LX٘- f?4kS#ؔE]];1Sڵ}_jTirу-+_J%cqxRgUu=|&9, . !իW5Vx_UreK԰bBl ^V=rE . }IfM[Mg[ϩ [Ϟ䣏ӑ4+G(:|+/>8$f{TdѮs2frԒ6i<(/+8Pf/0E btl_^u5<ύ%;#vc:W`؈"+"ζe˖UVCL5_f$ Ў3LPv6a,~%8 m Vrc6&#>E{̚+g'My O:ՙd͚\wn KP`WBP#Jzm-q1LW\kPQhȄW!WFYbW# N7O. \Ν&=P{lYiWEu(͖-Lpmw?d:aMfu/!|!G0拗.|úE Իa]L6. ӈhRu춍%pG9z3W:' ھb$͇N2;O?0noq}h|>m t`͂Q{a$[Zhf;'=޺u be \,Y} 8?>g; .27רQl1r@;vӟrڭfDe} v]t &#)>9ժ߀DgyܣisoqRhޛtJjU+}QpIKhZ TܣOӹ>6E}`]:+3m 0_eAe5FRLI .2N/j-19/w9QqfcBgc;w[ [pqNo\w6MbfȐ nB.]tזTWB#>j r*5]f5:VaeϑA 9zCU"XWvo EOk AEY58gG2UVYT;MIb(tK>+3RDE S mVuW}iԨ,[O H'5l-[ -[>yT~ 8۩5LWXEdXd@d:YљY>]RVA-_3^シ^;;t|EG6>ѸkzyguO<&q}p \*[5o#S~^m,oFjCmN^<3v0.\l+QqP Ph$8~Gf\ :X"EU0 ېkɷ~A G5N1yF9@izj:BCepT0zpکS{ED{}(MtD7{^顖$ Ħڭt)]t ӧO/C`FȀE=ލswYWbgC~4D @B P0S"_MW аN6uFQa*\ۅ4ec1J}q)?2Gz8'}&/U^}3hiCP!¢.m08 :1.QOϿ4dž:嫥dxj`Ub-):"]pV,PEVDy4|^)mجRi?s:|R7hE6 cuɦ~<ĜyEvmC{ L.9zmcs<0ٳ`Y2pap YB7b%m2&׸=J BgΜ1Dg X 41eނ0-?L Q|֯mb*U(MupmݺSz"]A jqĠ|{S,[#mB+"H-yxȾ)/W,/Uqk =V8)tL$.naW*ċ,i*iCWϮS|ۿlF-[JɣqDcs`beGMgֱ9ǥ}B]ָP+ʕ+~f)P~j(K,sFVw%}p+vvr P!fx":tL|gAGb%wm$2/94ir~~ch\Z_pѸα<܂p/>(< .$uv ?g.0'8&שUZ%`}*)P >+utQe~<}{(RgxqD%_ƌBrUxQ晃bg:EUV= 0倎|0iyT]׮ɔO?|{xm#0OLև,R :Pjnޛxwf͚E obPY. ym,ߔ=mPD̙bV2. Dt5+[FM IH > dK#%+*7N.uޟu%Kfa~8?P }R`0WP-Sfigʁ_f3v7:I̙7EyruQm: *ގv:`; x&z2tBLp(СyG=֞ms^r+X8U(_Q`+raٷR\))Z6:x/'W^3ڐ p[--Ŋr,xp)RPF kEGgZJZPp~wv0y\1jI}aMx$<[}Wd`x}?}`_ Q8}Z]?;oxv{V~聖n~s21D?tp,YܜFnڼŸNqWdI0b>%v⣓s2Fw5٩|Ǝ.;I\9Uɾqw/@]}O>f)i'B (G"& 6L`W!`d $6P\  !izvgwvs;=3|B3sν|sg?sI#=>0'uڷ'O2;hZ uUק?oܕn`6mܘy'w(CD;mo7L@~Ĉ_\7wm'".d_/JƅboSb8|*P7R[t>ŔW߽~?-Owo;MK7fOq?.>wסi/T<<}I7v=3yNWqxxmWg?<v|:]|EusZ# Okpgo>t[^\&yD~$Py7վ~b=*+^9# ũOsk^>k}qbw=Y哟H:'}㍯iEݘ_nbs+^/c|aJZOV\!@l\.'?'V_q5FkoMťg;[3}8w- 9Wocx[{{ժtm1X+?M𶷜\O=EXC&.( S7qw?8Lr;~kOUϕxT1-7N[;:>[b(dzm({Wsmw3>ʿs.7 3~xGg*[:QuA ԱX={ځ%`hteGHF[ kQݭ[/Lqѩv(7󅿩Fb:-`|Ow_&mo~*e&To{/X$7#J",8/[NT+]w'.HF_o~wfy$d ESPyXގg| ?FvLE|'.lqNgxӍąp:S[ؙׄ~339}c6 WmqwmOu&{4WڕŨLaөx>Dxlƚ?%nOݓzt٥sR_,z /K֟必\5"/)b6 ץ y*CyT؞G!N3yDhnXyRbzmy9ſg]o]מλ}U('P >9U.=.{m[Ϟq&Pﱦ TݏS ~zm1?I?Pxv@& PIX=Rڱܿ'{<_ StŅźG->x/9.=~mq˚Fzyf=ÁXܑ~tUWC?Yh6E|Q)”_)-wE)y(UR L&{y-b]4X;3&81XHlhS+;=|4wwR~Sb<֋6)k5,Gs%xG*zEylP;Z3Bg7L@%zG)exMOfQ@25w5b!?_o~';u쟑#(K _ @`-Wۚ9D*CtX^z022Z;*%5S.[<1Вfq,@\ ۱=ŴڤngR_OAJ7L@%FfbkXG ,-u{M_gddW  @g@`F ZowtFz'V5Jѧ[i  @ @ @GZ%4T  @ @ @uP+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @r @ @ @Tj %@ @ @ TʩG @ @# P)5 @ @+ P+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @r @ @ @Tj %@ @ @ TʩG @ @# P)5 @ @+ P+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @r @ @ @Tj %@ @ @ TʩG @ @# P)5 @ @+ P+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @r @ @ @Tj %@ @ @ TʩG @ @# P)5 @ @+ P+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @r @ @ @Tj %@ @ @ TʩG @ @# P)5 @ @+ P+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @r @ @ @Tj %@ @ @ TʩG @ @# P)5 @ @+ P+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @r @ @ @Tj %@ @ @ TʩG @ @# P)5 @ @+ P+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @rZ) IDAT @ @ @Tj %@ @ @ TʩG @ @# P)5 @ @+ P+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @r @ @ @Tj %@ @ @ TʩG @ @# P)5 @ @+ P+ @ @ P@P @ @ @@z @ @ @@1bZC  @ @ @r @ @ @Tj %@ @ @ TʩG @ @#08[-ˍ:ww5  @ @ @@u#T[)I @ @* P)5 @ @. Pn$ @ @ P@Ўl @ @ @@ @ @ @@B;^  @ @ @VJ @ @ @ T x&@ @ @ T[)I @ @* P)5 @ @. Pn$ @ @ P@Ўl @ @ @@ @ @ @@B;^  @ @ @VJ @ @ @ T x&@ @ @ T[)I @ @* P)5 @ @. Pn$ @ @ P@Ўl @ @ @@ @ @ @@B;^  @ @ @VJ @ @ @ T x&@ @ @ T[)I @ @* P)5 @ @. Pn$ @ @ P@Ўl @ @ @@ @ @ @@B;^  @ @ @VJ @ @ @ ql֌}Ҕ6#k[2;>J4 @ @XykWt?D4۾|,ZHW?oyjg1 4ҏ]HifDvO/M=-5Q~fzŅ.;);i?ǜzoB @ @@ee=kܿʷwL}|u3r7N_^LY:u^76+6 iFz? @ @"`2yW_+БVl3=Z3+z– ”yʰyIV.L/i Sb{wN#:_l6 @ @X F^ܐڰuݟNi ,MG[>9.Z$m]KFN>W]<x}494 ];z`ږG\y~O2an @ @ @( P]?%N7ǷХ{_+F%9 e[k .IN)埃V!+6 @ @ P)Y[G>ul]L'$i2Yqw7m݀e\I7 @ @ @n|o])I?=~De.K=^dyzIs\b ҜwwVG @ @f$ P|E)t;]ELXGhZ\qz,ckKJz֛??6kK&,qbM @ @dck[D.ϋww+Ǐv&[GeG^Lfj'gH+'Zix3e ?2 z*^y @ @,6#TQKl^ɸMό +Ɲϥ`@#]Ryb#-o~S?5z.8'1.`9QK~pz;6+ҽOty)=K @ @ @` 2zxIlZ#9%i.=ZͥhE2qs8}gݕṣKbʓ/<{F^s̉Q*iat{w @ @ *7[gI>8&ńSL5,W}@jXaC_z"?6-?5<ߞHYP%r׾?= @ @ @` 4t8M˦:qkX޷]HPS3[dh9yo{x4zl$-l-g/I9ytH:G~[(%ҕy.s{=iU^~9Kbi#퀦ipʞ~J7[Sr? @ @[kۮbKWKGOGL?si`PZbm%ɶ|t9H9{;RJKrcdv#@ @ @+ PG}aYySlCgQ)l|'K֤^f#Y|Kw\vYeZ:v4WkC9v' @ @X cWCka@PNns @ @(`Q{]  @ @ @*}q)L @ @( P)׵ @ @K@ @ @ @@{]  @ @ @*}q)L @ @( P)׵ @ @K@ @ @ @@{]  @ @ @*}q)L @ @( P)׵ @ @K@ @ @ @@{]  @ @ @*}q)L @ @( P)׵ @ @K@ @ @ @@{]  @ @ @*}q)L @ @( P)׵ @ @K@ @ @ @@{]  @ @ @*}q)L @ @( P)׵ @ @K@ @ @ @@# @ @ @FTR @ @(V@Rlk8 @ @ PU@RUJ9 @ @ @XJ] @ @ @@UJU) @ @ @b*v @ @ @U*U#@ @ @N @ @TTR @ @(V@Rlk8 @ @ PU@RUJ9 @ @ @XJ] @ @ @@UJU) @ @ @b*v @ @ @U*U#@ @ @N @ @TTR @ @(V@Rlk8 @ @ PU@RUJ9 @ @ @XJ] @ @ @@UJU) @ @ @b*v @ @ @U*U#@ @ @N @ @TTR @ @(V@Rlk8 @ @ PU@RUJ9 @ @ @XJ] @ @ @@UJU) @ @ @b*v @ @ @U*U#@ @ @N @ @TTR @ @(V@Rlk8 @ @ PU@RUJ9 @ @ @XJ] @ @ @@UJU) @ @ @b*v @ @ @U*U#@ @ @N @ @TTR @ @(V@Rlk8 @ @ PU@RUJ9 @ @ @XYbjIENDB`graphql-ruby-1.11.10/guides/subscriptions/redis_dashboard_1.png000066400000000000000000003116001414121453000245160ustar00rootroot00000000000000PNG  IHDR$ iCCPICC ProfileHWXS[RIhH #E^%BBP# ]Qq-,6"u`CM N9g3fPefAĤdP(@DE(MHk֒X:_Eq@ N89  t@\ XM @%8]5$8U6~{@t%YtGQV ''ͅ>V99 V"Cl]LfaY.R!Eroa-C-YaLM *\8"?f l0!f|؞-B{4+ǩYh ;"\gy/dD16ia 3bd<3!eń}fEFMFl0h^ - 束,yQ\ 0X]rߒ(=V3v@T3{5lG(\rbep?X@ [*2sy6trͨGtD1 |Q=^}z1&4a07bqWǽ(hԸIVYe5EkVYjsƗ?86m=O{vNN&:L]oLuzz|z'`|X٬J֠~X~AAACazvA#=FS]377~obj`̤乩iii}3YYusy. "â%jdɷbmErXZݲYXX7X0mmlm^N033fO̻XձqJ'''$NH\x9I+ԒLJOޕ<45`ꆩ}L9tghȞqlLC)=)ّZPjHAg#כy=OH_ޟQ1W_egn|U5ݘCI9*Pd ҝ5gVwenInO[ކAapMcNLهk1wgqןd~" S/2\ToqKK^d[T^vibŏ DDXrk+VlZ[z̶JK?\ȪUV׬!kr&kZZ_톙.VLغQ2eѦ5>WeTݨnܬy[[xߪlmmmo5AQq~֮]_u=wwߣgu n;mo>}-ood65כr9GGJM=-I-GC淺6cV/>>rܓO=n~tg<v¹s?qBEG/\jtñtl|˵{R^WO]vz7"ntߌyִ[=ɾn{>P~PPa?s׿Q̣{9_<=W޳Gߋ%y18Jj7oN|>5]λ4?Oφg&}bk#9##l![z`Cx]= *^RAdE) gRq3J l`/9zzaE`/E7‡7:Z"22e'${y;D|\:;G'kꞍC pHYs%%IR$iTXtXML:com.adobe.xmp 548 2054 1 9@IDATx @יe,^DAd nq[m4IM;iI6LIMIf:ͤiڦ46&N\5%*lW+e?]{+Qr9.{wʕA0 ܒkkkoɎS$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@` ?|\Ss4g'$@$xo&R ]%#      1G&              1D€14* #@asHHHHHHHHHHHHHH` 0` 6J$@$@$@$@$@$@$@$@$@$@$@$@$@$0P0Ɯ=&             C( Cͮ =1gIHHHHHHHHHHHHH `$@$@$@$@$@$@$@$@$@$@$@$@$@$@ccoc              1D€14* #@asHHHHHHHHHHHHHH` 0` 6J$@$@$@$@$@$@$@$@$@$@$@$@$@$0P0Ɯ=&             C( Cͮ =1gIHHHHHHHHHHHHH1WvH`ЦLF9oDkwG=:~~ ԯ܃`؃ =ͭW- (  r6?IK!hB<#_Cx-GQsf"_@Xb/pT)_?ssaM&?0WyIH]A/W@Ur/^x 33 Ht&            ( tiH"8l gt$b?|!q1PV7aʗD>m'180fTw8?@( *#            P&f"U ty{! C5:op<7븊ݏ(ύY"6"d 65?HHHHHHHHHHHHH`4P0iH%nhu¤%s1{1~z]to:i-B%wGGEi;U>}~&-. 8@E$@$@$@$@$@$@$@$@$@$@$@$@E€"I;$@7->tKSDT,?`L~iCg}#wR&ʶ({u6%ӟbK^*            0`iHV :P㟡 D@A,H۵3N)njAą#L눞U]Nm" @a 0L$0 J9mt4"a¬nAMÔ/Fgm= 8o`\L70qlu7ʻ$@$@$@$@$@$@$@$@$@$@$@$@$0C X& r?hsaky )J]U:T\ gNn"y.$@$@$@$@$@$@$@$@$@$@$@$@$ ,$@c8/>k8̣OM_@ %ZOg%.D_W]'8描#J`EyHHHHHHHHHHHH!@a7H`PEe(ƥOO#B'O"0&??(&-0J@{-}}>^"nøiS3 #w2IH o~~v N=ctPd^6&]fԾ')28,p8SwZ$].~}R^ +͑gXb!            [0$@׌8wjtO=uvWk&FjoY0"ǽ"iiGܟ_w`P导ZP0:'&           a Pp F$pGwS3NozQ4~d.R 1Qh?S?({iIR8̏9._0[B=2˒ #@apvH`xz1pQxDMj!4nbW.BxwR/ „Ц$IAK@mTB_}`HHHHHHHHHHHH<0!'  4 " P"&"s#_5E ɳbZDMz Ÿ6WkD0 XHHHHHHHHHHHH`yy2om2ϼ+V{rTl$-yII%B;J ܰe"(cH%g].>;Lc>B 0~nXNl E3/ݵiɈj17;ۏ;‹B`]XfbPfol-#           zFע~ .i񙋱郷QLum'݈\l_ Ʒ{rjqߦ}xk7L'D{VbЋykWp]?z!q10=?0G*z.WVǐx*d?JM \߈ic: 0䤄?qزtd̛; xٴCBߍ\]}qޓ[aU}"-:DNC&r`y*AxV<Ývn3*@E Rq`n?%s|}xx໾l0^6u[gNŏF|}9~% ]-p깟|I0(u<)Q>]fQPS#IHHHHHHHHHHHnUUً x' ݊{<)n돭-XV|Qn+`Ar]cCg<(w ^"yx;""ŒO帗J[^řQ 3ڰ&2:/]u6ǿ}"PBǭX?؈q1bh4N1P+>Ít|(}闸tX<0FJ@P0µ ꇡ7~twK?.1!hEKGK |ьlq= ⠐0he>AN-Kyn0N=; .4ZDEF{ulh\Ϫc/TՏ M N%S]]B7&6.!_6VhHHHHHHH>{MKg}xx;l;W݄staFJN.2cYŅޱQc@ʎƙsse86Y-/]Շp @̰Ѱz0L K.s6}D_iiGS$@$@$@$@$@$@$@$Ypf/橌y/ER{[ϕ+e-:!櫕x+Qu/?Q!,I,gcy6$يvʣ@`Q@Ȥh4M-mMSu(kh8"gg!s#gS_cTJlUp_჏0.9YE̢ *WaYzNu- PLj:w^ _[  H-%a]3յ(@g6VbeA*}-5 tTborע+8w;Tjt&_Sw1-WGe-iĄ0&w5]6]8 \_.֤r#![S֗ 3|^W&c8{59kP=nbGDA%P-)! ۝yd#*Bk W*^AZ9o$= Ix}Wǚ-oXplP%Djv/[ :H\8h,IhFmIq/RޓSة:p~Cشֶ2> +m }K8qӄ3: {$}C:=ٻͼO$@$@$@$@$@$@$@7# HG8~rטBX.A I+.6/m})6绥Wupt{%__21oy%RTnSPᐱ *F`+w"QPٙ`j(>>՗n؜|DZ^zv+Fm.FXb";m79 6@яlOú{ ]1!S3Ew ;G[kNľ}ʗ(݈m)$@$@$@$@$@$@$@$uYKbV4AgZN|| ^ ˔ƛL6"/͉(@)+lwa:&"ޅ^%*v=Υ(@mF /?o)}v! Phɭ!sDTaM8evBlO@k`lTԶ8KGtxsxCkCnHE̦y-9B]V>D;@@h0ο }zGދ3rYNDx0C)9yTw߱۸{Z 9'+" 8i-/yM 9ӣN~D`j[$M)(. )!%mvƅK@iT!:1Z$hDž:]ІۅA `_]c$(D twX?$tp=]ڎh/\_?{e\׎Ɏ}M~h*!*< z} 샾*}׉ .Y,=`y ]@߈Ua'u(D#\lc:Ҁ ;dS Ytm^ꬮAXgSMe2,vGɈD``'ZA Hm>aqX#vdD繿LNtAh3aBX|di-st]z(r!yEԄP& ]h&80t4WkA=vI#Ӑ29s]~=tܪGD 6*=r=]lLN@DkvK[+Pc~/g&[]^¶ffTuL_VIhF31m MJ %Ht)Mjڤd~_8 X : 5hl1M2-kE91       ŽC߳g%Zuvxk,?œx7]Wf~;_8 +̰Mm']Wh߿~&sK/Vq%%~S>8W;i*`}U 5t[[>1dn.U(sMLlz8'_Bj+m>7֜¸6 r,~klyw-I#/a(o+oa*ܼrعu.~330njνqU{έ g|{Il~301Qh: H][PV@ͯ!Eѿr[W|br +ED0Lh(@B5.Վ`.YxDm,(>;ɘױi3r{E 6D,\86ԔTnGCjs,3ݱȞmߞz I7?aԗ'=)E:3[-fρ(cݥǰP&GslR~ENssi25IEn1+WePуƒSDg}K9<:z4Ԧ<Ժ9Px ) Ȥ\̝dNXYSSEׁ\1q*̙$É^dg8EC션3}R\Љj[!23(|/!}M,Դ "9n׹e%@ dteR܎~ WAQNЈɫh%U_8,n,oj:)℟l`-CmؐљX1C]8Љ>1tJ>fDy$|K}ϔ7`||ey A&fSzY >V'iQp VD[*e\KUi+d,MR͕HQafy6'0YKd&;i[$32+"~wcžSQbc'zx5vVõ*Bӱ I.U× >IR|>~rYUCޅM/㱡{s1_$hH"tX!J"sxGı߼/}"*i+qYmߥL-_`[od;zn{QW(=߳Uv*?zg( @J pJr)yۡ RFWKvVm\]"rڡZڷМ¬>p(}RsiƱ3-woeMrM[wt'vvH{$-t;W攲߆حxkB)#W*xmxD$@$@$@$@$@$@$@&idFItqW1-\;_Zkl: =7՛om~oZah1gz.We/KhgwbuBd8k+@"y{#l1}(~ur}5Cܰ⦕sc«[6yK4z~j[3ZϿk;wuÜw"\6>%VȫȓX83!"'OLoTW"t76w`P1 0BO~bys{O_ʸ1ꗿ^L[fR~?%OO7pѱ$Ͻ& \_s1$)i,w 6)[ d 7/B~ `CՑ2_U 8Q&sq>+)Ѧz}T]u0xل*\;2&YDrkNK}fGO>KF1ώyd5}Vaz+%VMrzjgD!tmfG8 {Mi” '`ZP8 ެH=udg zN׌J v2,(U>2hR. >$:/^f&B/ٷAxӌT_<P]zue嫦9Z@dqVz, &d Um,]І|R1SPRomٚeܬľ`|ȊoE>F W/Iwx'&g8| E%ȝc.(aK_!ڕǺ F{?]qYoNʪm6d۴">R bq||m09" ݯl%zr1&!J~ڇدDv;ll% B),+~+ʄm0\HʿLIpDn lwۉ_A'%R`."TQg vMq/01$@$@$@$@$@$@$@$0270 [%{t^PwN``[񫧰@zwe/tz$mھy[C.V#5XaMMo޾-V*=wSpq㹥-5*nJ\IQ"rѬݹAwU]rzbZ* FDxi+ڟObCy["zY =^^ںyFcb,qOGO3ID6 ӟ鳮?^  mh"0{smRM8n[nhE]SduUMA'F-u-{e/LEʸr|3f!WmRi%?-z[W^!p F2V'F:*!_wZw3GJrF\ i.8f^ 6u4BCjg $PL0؂#DA"Pi^UaA6nۮˆ#ɲ|_T{'E=u(JGΤ 0\x6IQԀ)c쌘"+E`;@ȹS-NsDƥ@ASCtދc9.IH0QaQՆABJʷFTWa@r_j|@V'$OEjg(L] ;(ێЌJˊ`./Fr8.f\:Y򬙊g`W/!.\#ao'FG[0D`kQ(Gqee- \@al:PmA<> D8UnGSS)tHrڠuL̟Z57&`阫c        P ?] w g ͵9kyW ;9HCT&AN@raH._ҝq}ɝt0ܛnZ-+g.uqᙵ&b70+ meW od<٫zһ94RB4J[€L;!&EyqJ)Hbe+dxhhl KaWOniHi3qaׅRH99ScuVrY>؎vy[1 qupXLl0ՙyا.4_6C)x fLx3{j_."-hZ;}= 0OE8$XsոC,_'a ZɎA)Eb@ `JmY fK]BGQ\ ܀€=xt1%C[{7@YCU P9M*`Δy9阨wWAS wӷӗVody ѢR"Hyl?-WAI,|2;}h͟Ua A8>}l4@o8kF@ɏ^ÕU eA@ޝ61 廍[ t56c`0n!yxg4{C[^VҕxȂ۶uЄXY3 UJv7YVC6ZA&;$[qnm8õV9 Cص~/oe{kGQ/t=hmspto8P. =I))H4!s2_H .zc(0iREQp;_f~ u:瀭4/z ⫫v=C1Zb0SVu\Џ<[fHt!F~{w @%:QWpnlByvb\3 Zd S'keA H4/禕rkDoZ*~1̓'i1ȇX_3c'͙#^eC ;eg:&2aWFOF:xN$@$@$@$@$@$@$@%p]0+PIdtxufH鵍XNJ :[]AG}oȭ2?hgŀ|.T]!*BY1*>m"]v['l@^zmwo#?q+Ӧt8^g%rzD/GwS emLҝ0@ 8f""/#bf N\)D=+1͟PڛצOcŪթm5pY|uEnJ/ɪ2nƝPwgqd~,$F#|pbMH|H-hu˳6a@GKNtSDH Cjj.SFbE0XU^'d`<..ch.y|3@DT+iv{S%Jɬ"ԣ;2G\ /Fۇ8w{a$ KBV8(l51IB}Z㊯z5%zQWd|>#ͫИX,ܵ}]&7F2n Js]i K 9*gy=m=^A$;OVڶ^BG{+P߀ع7yQ୽a&woz*Ar 6fΑ۵(koSh2 DBw/_q4^zZ$eb6$vۋQcƳ#ngp[V+aaXb7Bigv"eۀiƠح!ݟWʪ39gQwٚ6{W-&_Q9;.$]CPXsZaO!K&g-(ܷ DqinqKKդl0I%TV|re(bSN oX:n# X| ~eW"DlT̄r㝭KNE! IJ{2e.46ԣy\hhD{Kڇ]Knn7J86ȽKƁ2f} &$tw-{Ksk=Ze       DJeIKc39 #z^Fd_ãKPǤ- KCX4yHOP9}7n@R^k~0 :L4(4ji*Z;o*I%"4_B3Wu(-h f!DTL'8 XVk<)_;~^VOEH1 ۟0*^^B㧧Yj*.\St/OƂbe1C"C5A|j'+#fO!7*< T62:aǢ"fED@d\*҆ӂ['W,09.c&DZԓhiq3X2xdD^nZ[7"sehlkERHP$s7 oP EOyLkZP_l:oy_ r<Ύb0s̲|ѬR Cۿ-oqVebú=؊&O.YF! 12f wT}jm .Γv̇kHHHHHHHHB*{Tg%/ϸᾹ 9sW`gd$!k?y}ZꓫHm<󡹫rT}8#H=gx}3`DQ2];vlܯ,E2B7MVu ( L%u93W_@7A ;_wP?\-zZ};q W[34vQD[HIAK\4*C {{qtF*,[lz` )?> >g!VZe2r1/TN+Sg T e7[kDf UCu䏬빾*^6"[xj^IeOS5BAdtrކX̘j 9 -lYYΫ\ I+e.CvEa[@Lzn}޹`o,+sk ѯ:z{P(:ovƩ6B'}(kqkPc.0MGaMoh +5N+Ay*mMgPc0!:6BJ$^B'&Nm.>v;'Ȏ) t̯AhHHHHHHH`>GUlYq[ƭZ]|^J~HKruzPQmJ!gq\-JYTn,=qmZ}-#9+ l#Qix]C8b٫C߲R]DCXf]O]%l}tubM(*ej᣺PH</00@V>$<ʔ[ R~݅RK?x2oH֯b_eŎJ؁J ( vm%{D\0\8qm6,GYٚ;ZD{Me5g`U@ZUꂯ5M2`aRT:- qFl|tB+$@$@$@$@$@$@$@$xwĬv>6`54yTzB0~~ӱ鵗"˞1T/7YNVfkM+prņ77G14Ox<>Ϭ@zo:w/VlkUɫ6a!9'_3GWpg"rX&̏%0݌NoJH2mMoz۶nˇڏLM#GMMB98[e/NDliMD8]rjL~M|ouoX8Z#8.H_&th=zŝPEѧ)6ŁSqzB' %% ѝ.3I @V? UgQY{i2abQq' tҕ>wE7 y/SmP C +N;jm֟?w,B\j*NCDDBي u5(S]*QR~uΛ&{'v] nHiGM)/FX$}%f#cJ 4hP3j6lSr7:'Cw?YȚ0q| n&%\dE&5QajkF3(=jB 5Sfc9Ѐhǡ|?~ۘ\x4%&zWTIM9N)T  N-lHcJf&"ssQFA@$~jdFfSD,*N|ZuD`\˜ 4@$@$@$@$@$@$@$@fO)mǖwmEb{o9q.aNJUSc 8sک݅Xbn[@ֽ& ʱ]Xi#[rZ)Vl{j󐾬x`m] ā=}/UqrV٢"}FY5^{yk/C^o~[^MD:E,#ssNYdB"%{R 8+7q7&7`G|lnFw6<U΍"` ^)+Eׅ5Z8?%mHNHK-*@!@Vv+nZU"P`܆C`Bě5Ac:S2؁?BUWTNI)(4p :sph'|SsgvkN@e--lcTu0b\h/ Isa۳bT]I {x0M 0 m_;+ 1pފ(~u ZkM~?V!]R۹8Vg*|x< Laotփ?B%OPC±0$r#g<klj}I^ N[py8repbaɴ5ɋBVwTb_-뤼*2GN엇ȿk5^Y1Go!<'qX~!_f4ĚiCnY^ni.e;ĈÅyg{z? S,| @,~hو6FF#/nux9BAO+J"k0NJNu8ܱ2 \GapױaQtFyd ]\j8BΖPO[+bO.j8`C hh$mRY >w6]o6RBJ0/Y3ⵇ7? <7Vkpߓx:ȧJ+\:S~% 0\f<`6Bz uS!nk ӰauM [a̻fw㼋:GymܐFSy ?ѫ% 8Dž9HX ?8~rl%8@޲Lwݍ͌ۂc{d {{T-+u^J?]% GV; >؋sE8^RaB72#C" ҀӅе9w!zO+[]H3٪h:t(:s 0=>Ao(]V~K/,Cw>K+2 %ĩcnquUtv*D&ېXZiBƬH߃}WoЙs{RsioAm8^ M-(.pJ^s lAƮ ̝'3 RzR/"+)Y8sjJAD371އwc\1||RV |/$0i0DqM@PxxD&J%ZR >KЮFGkܭג_[ĭ(KRI  )#N>=ܓd$ܯluz\6L3JFlT&`Ў&W uwfpi˾:A /Q^fpG]5EڙbzۻmF0s?l;;um;<6L8_ߐ6=A]͘x.ҴvZEգf}?*Ahu:mź1#S{uah>GgNeW^,۬mC9qF&0"͌$ᚇf    @zN(X=wڹpwFtL7gUoG}4/nGW/ץel}Ӆ7]2  `?$ nVOF -t#OĊ##ʌьg<<}:E)C[d=&us?|Vʎtߙ_W|E?aN!g>B?! eHzq.0#ꬫ5,4|u֞ MH#\u?ӄ|UlЗu+S%߽;[6te8GiCa:;MgǣuJ f̜ 6R#>҇!uX-o0yzu)@SdZjZsआi%ci:#!9b]o~Cg)߷\}9X#|%E::wc꡴#}ɻ;jڵAE:43]BƸ1+M^%;!O1 61kV Nesi%sOXuU_E'>_7 TGu"mt\=t]7JV1Y/6|L(cl_PleҚi0fv`J2y\ك   #Vܣ\+O02@G|A?+G$t]hs7#/ZgzS~,`nLwjޗß.PIQʴcgSD@,m3o8AtR2ם7׫pM3ä(v{f꜡wT fd ^ .eSv5_v sĒkOkݯf+,'giœ^oFHO/FLp948zؗOGp5WMg5eP_/?M׿'^wg`A0뮧E>5(%1ۮ'C 'y;brҫ5@?IY>r2d,9SIFNVi=,K7ZOu^ƭ:W_@ :Gd˾sZacGkzod H8q|x$rxP335ә믯cKSNMfe}Xo]/G[ Yo1zasEBS%"9xZ52Cg[WXHLFb I   1 Ȉ]k8~ށ޺%_.f䁵׷q7;Dc֏~y=wg.qȾ@3~w`)N0sf9 &mz')e\Mjv%Z$Ysv$Jv hX@@@@RX8j՞mqNU;ڕ\4Cce[?_!& }8% tƥ_ШsL^'t;;2_[ V)VZIʘ]Q} dG_ :Sb5/A %lK*j\9@@@@ 7j B08k,X:TyEY_Ǵ`Çɑ1N#?wƘQ\/~^Ýc}C}uf۲MwoF"&|ZVpfZn[2AN~y1pR^7Քcǎş  k%'6|8t (@@@UX8N-!C5u%_'?^3b@e.}h 7preyY:iF8k~A=HYѻhjw4o/]?':['9BOeqH-|@4|8c."   I 0 3G@`i\T pjs/~a?Č&`6a'ח.Rq/Ti#|kφM:~`& k)/ ^.ͻ+t䃿GBS      @? /QM:ӄ8~)B@3IOwH>B;T!bɗ5aNF?_ծ_M?"CZ~vmRC2NKY}<5@@@@@@ϖry$ A֐8 ? j ѐa7mΞuc@Ӯ&@hؑ| vYXa*[}a !     )r!54v=k[0G?XcyCc.'~wKg{J_X1|63D@@@@@@A)@`lT:|F9?tсd:+˔\6,Y(}y>w:qp)O_9yℯ]o-g2.U?ݬ!C*}i=sJs ׉#޾"     )t1 gܛjN_`I5s53uSo'[4& ؁Ca}[dn8}^󴆍yJ=JC Ӥo_z￞Mf      腡Z |KyaykXqӦj\>2kę'3M0lhS9n"0߯"     #@`S* yg>tN9yta_mFN g J5}@G;6dxF @@@@@@φL+@}u\W\ac;{kQ,ʋUȚ2oMc/<713Wrk      @ H"&Y!@j lÚ_>Pkj=A'GM\@$J;d҆Q s3R97ޟ$#@@@@@@Q]H\6ύx1 xv7@@@@@@@z'0wI       ,@`@*_       @/ % @@@@@@@HeRP7@@@@@@@z)@`@/I       @* W!       Kz Hr@@@@@@@RYT: @@@@@@@^ K@#       աn       R^@@@@@@@T 0 uC@@@@@@@       |u        0$G@@@@@@@ OS @@@@@@@`ĀH       @  "       ۑ@@@@@@@ 0 /D@@@@@@@ q#%       )/@`@_"*       @$nGJ@@@@@@@R^DT@@@@@@@ H܎       )         )@@@@@@@HyRQA@@@@@@@ 0 q;R"       %       $.@`@vD@@@@@@@  HKD@@@@@@@H\H       @  "       ۑ@@@@@@@ 0 /D@@@@@@@ q#%       )/@`@_"*       @$nGJ@@@@@@@R^DT@@@@@@@ H܎       )         )@@@@@@@HyRQA@@@@@@@ 0 q;R"       %       $.@`@vD@@@@@@@  HKD@@@@@@@H\H       @  "       ۑ@@@@@@@ 0 /D@@@@@@@ q#%       )/@`@_"*       @$nGJ@@@@@@@R^ =kH@@@@@@@ϠwF_~M'x?CG8q%\Pc XOt F@@@@@@&`R VEVXp# Z#      ),``0qu;4O֞M;C} ZoiO,2bS(       YϿpM&"3 #VpW5i@#\d@ `2ӑ6z{`y/o&='͝o lmBς7#{Q#     }!.:o݂Uy }VF$0>8hՒvZHv搝l.V"Umm7EʛC~jkJKs|n=ՙR*m_Wwߢ 6h FxiDn.4u[,rܣ5wj5Ev#̪u^jP~+ jSfCef Kw㾎v=B_GϛzzʦgēeZS2d%%ZrfoUۧ]rBfc_њGDyEza^\}> v!     xܻ9nO,טH)vj`Xk_^(uQa@@@@@6}r9pVCO;CCϻD#T5Ss<"wuNv0~f]v'r`} _J"{fhs1JV5|L1kJC?%`;WGh-k*bE2b\ (]BV[haTt]+ )ccJVݞ0BvyTuM<|_ݻTzm*_rnqH6(rK,;8QanAV 1}uk^ؠ-:/-&("LMIxG{a1`/mOM PdUkOб^uk n~[]J0lP@®5w͎IdO0pϧ/ 5|&hUZ \P@O:-"[kʹ=-y .2;mkf[t9`·智T{oGF     @n}:t{: jegl귳]'Tʧ@ 4ߙ%'0$7T vEY%NYY*L*_kdUO:h%|ނr=t|͜l ؼ2 D4ob}WeQZ֕uKEmTq*ͻ*܊PAZ =5pܺ?B0G0uϳh _G٪̌qQ%[CbM 0[FhV[BJo,2CBD-֩b .|%O;ιq m{t"kebl#5tIٱiF(^<_X9_ȱ ^)ߺ&[PʲUyC44_5/.5AZ~O?~)'xUN*0Cض Ζue*۪BfJ9!E~Uq|ԫTf)=0:0ڃp&xe`0Tb=sֳ`5w"-}ʭZ\L]<ѧ`!     0#csǝr9Wɟ.e'I`yo/}m:p׭P^;_cvvh!Vڪ{)t. 54ss%W!FRۡә[ X{? vNSvh[9"7QE oаYK>/ja>JL}5Mb',U%QF10#Y,y/UbiIV'kY{_ˌm4:=1R@H( yuY PBߴ`; - yr Jܨ-=qEU~ \УO̯c)} }&g@U뺿}Եl#     00:-״.KJ_l%, W5~ăvfm4Ë7ljI~>uL.RF-uܰ\5S'53H tF ءvpY?t`hwLSf#̼|cWaꈺWf>+ =ڻݖourdtۋjQSO+QoTxwZjLP@U3D)nq$prGq0a5׬h{ֽnJU[S,sc4gzciтN]~K5P<L rw&؎wM&QhIl!     0PDC]\ikUpUv{5m zm zi{Ҩ-&/nԻqs@{{m핎-ϡc}ѐqcp<[pm 5Ghn5\ u5͜ /H')5eb_>j\!_%7-crܿQ.zQYGۃGVYQy6j6G"OHrwW/^w]6fܤEl/fA˂8:u;R- $CtZlT28C1z!l^} EZ4j+KJԎߍ#qS}`-;66;!dcze}/      ̿tx۶ֻP{isk~ =x77WPԹj1j屻 qj0~6 ]7=չjxkEzuQa`GU*1]ʊ*\S LvEUlV$Pxy\W-Ԛ|RbH%qQef;X[w94־8l-QUo^͉5)$f޾N/7f:}Br&D RWQ ,nj06l|K[ k%W;Oώy@mAuڶ+c7pm%++Ӷjf#Ymb%peٞmݟnӢXG '}45c(ǟ66g)+ک%f3Gi?} @@@@@H}(UW.{a:>ߕ&7+{z($#Lav 0{ӮAд:|KQڏWȑKT$ TvC (ז=;;ǻ _Lʳunf#+Qɥ5ᆰ$NPiGҝʙlWL؉}23W1:{`T0 >IEޭjv~o1|02VCKĞـ9*6znwŀ@RhG"UZA͍Zri<<4{Dy3)]gT+s $(R_5kKDݪt Кuujj5_& o[S\i"IWSBb24miKƱN5>[urTt[Q e!kJ5mZ:A2dzSLRًy-|YՕ[d˵d ;A0p98S݆^Nh 7tNm(zyN%I]9 0(B@@@@8uޯu uh#{чU+uCW|KzOtޮta׍+]q/`!g?qB]cwtœ=2'?o8>\8uj14lm<<( f4N-^Ӣ*SpNRVR:\mļPΕv>8WOZ s'a晴65^OTkm{6 y_ EoT.g ( Owj_PɩGlO#0;ym7d"~Jԩ߱᭥AmE=,s|vjM'whh.Qw~QNJn)kϨzqmڅ&nA䘘bS!Q;5NR rBdjy^Is&׊.ppR5HwkD{>ߨޒstAcz8؋Qr@@@@@8C3׭7˲t/31} w|Wo9+Ү`G9ojWn;umf&\ ZdV)O冦SuS^BSQ s;tM\r~ejm#t|tG_6mqʈ+oڛKsiZMpKsZ6q(=n=QfDW YYd4uM;N5&1 R p!c5wڟ*=%H yșhUz6'Xmk'AGd!     0Ӕ~d~:!nGv6Aܣ/Cf$(}//FZWA|r3t=^EZ+奝ʹ5^f54+VẮ2 wPi+m9;jz%!7sRrT]FMf#/}&dZQj88ewLOy*wf-~]^?_qi[<k\p5s^_v!Zb/Erv|ԟTpl|6ctR#JC[lSbx>7nGyBvPN;2BnFJ:AHV^Y0Qe;k*c(l      0k7XgՎqi\ 2ikowitsf.9o냭o;݊ Qm$ySSLvz4AE?b\7Aʜ}QrϏ=2 .m~< ,x[1~WK2@@@@@M1US~voz} W\'c7)ctn%nU';ԮVkoEc0jK4RzK:% 7*g{Ne:V_'Etԕ l/.VN GVjtƺxPyggyiUb]p|k ?oí*kǣW'*wzrNTU9x{# , |ּMOUd29u@Z[k_<9w'^oK5k̘KJVވԩ`GvѲey>"?h2%Ǩ>u+JlAEZtepf&鑗b/BEsy@B"3m{{}[gb~z>1VӔR|Eh@@@@@H!sܪ^my}8G7Cٲ:}Is Vg>ڨy?.iR5C ~ḻq~?xN{iBWiS@i]F ~{ЩB`@2@O. ɩٚZ5"uְlC{ׂ}gzLGʛUho Avȭ +*ײqN+7ӽ]-}xWr-C̴mW**-o^1Q&oOUU:T뒐/;LҒҤҫ;yǭ5P%؁hϢ7$ץM Ge9U8Q Wʴh_)˕RM{QO7D/TacƊϞ6.[aG>:،4b;\g!Lgļco]cKdե8dۄ8Kil"     7iWm5kz\niJɭw2 m}MnQy'P[/P3"z>~Յ+:ha٭ڕj(\Lޘv2,m6c7j^Pkm:f;eЭh]~W!T ؝YTX 4_Ӣ iVa*#ᜩ^#4*U]Ds&TE8-ЮgX Y kڨv;sS 3T+aEKTu:*{M>f.qZMKOSê5VYwjugK=P,8ѶSw{'zgktVB*-0soݦqn_<3ZRbx]TU@ Ng-.V㞚&̨5/WkJrb9G =wV-]z-l֤UKt`dTfj|3Z@{`*`Oz}fU3CF > m>iSSy´yɓa[LJ@@@@@K`.2mYױx-W)Kzmkp+g⩁YһfjǵvYYzk$#PJ9:z* :XY%;Z ݄Ee`,Jߗ7\f;% UW TJʍ;\Wi (VUtwIy{U9L Й*͌`l=|UTmJSu#tK'V)Ayg:4o8V|y}El@@@@@3ޏC>}cku҇^kFmnګgmҳvFk5Žz#=t.~~H齭Jiwy"tw~[0w9pOJj].Wpeacr7ռVϏOnT.uUy{agt-\LX̴͝.vn:2lnfǨ-ݴK87|yZF[vG 3˴cG.+ "<~Q6#6uF/dr(}ܥʞc!∎*qcsr\cBw5)Y|eZ͗MXYBio!M3<C'^0 }HgB=~_1h|U}WPU#'Ke@e6j5)(1l &z]Z[[^֒ܣ!^E*[V;=_ @@@@@gTT!}Vڛrejti˴9w>}r=9@@@@@3)s=g֔o_abȻMnEЍ       d\qo߿B偭'"`];Z&Oo qݟ6#      $O@ӻzk:yěLi.zZc H1`@)@@@@@@,`u [)@AEv5Ƞ@@@@@@@# G@@@@@@@EX8@@@@@@@A*@` pT@@@@@@@X Es@@@@@@@ G@@@@@@@EX8@@@@@@@A*@` pT@@@@@@@X Es@@@@@@@ G@@@@@@@EX8@@@@@@@A*@` pT@@@@@@@X Es@@@@@@@ G@@@@@@@EX8@@@@@@@A*@` pT@@@@@@@Xc97۽INZ@@@@@@@N .`@N    ٻ;O8`4˿egɗJ*ЪaQ&6ZAZLCl +5ָ~!h7PDp5F sgasw=s    L)       tN       $F8S        "@`@S(        0 1Δ       @-       @b H3        -t ;"       L)       tN       $F8S        "@`@S(        0 1Δ       @-       @b H3        -t ;"       L)       tN       $F_b@@@@@ ̩˕3$5P̡gj)]O[W\c:\SKS9e@@ =r*Py)) !u@@@@lU|8E{>{;#\/uP@X^e_WEϚi x>& @@#G (]YԲ%W^qvos%uhPz*G-yvtV>?T}pd(cШ0}ofK Q3L*ϊN5'g9 @@@@'P& 0 E>4jv◧IVG$20C@"uyl <t(IG3=#% #/Fk2t6_d1h$9ij_|+.}Ixnv[0rfW\z7nSm!Q|jM(Dxft9Z62#R(%CNgہ'ku!4^0u7ϢgJgi: 1z;lD@:(,SciWR 7rxG^*Ǭ/їh٭R׾VGah?z>cy9\қjعhT pO'eɻ;hjտh.M.Rٽro    =M~s4*n[5ͻ]ŪR),RcJߺ*&<2c9L3m{@wT꤫aSbTvGUdywg;ʱZzG>X53<,!LC{w|XvD`%ﶥZ07ygY"eeFUNGZu<-o2T^A%Oe+h[EyiM\5Y\_zeoO˱Rw&9sP ܳ<iKUԡ{8"^" r8xOBYoDbIU(Ozpq*{FU4o^Ҽa3)G@J5!YR}u4i]&oQɍsTl &h~_ߛlUF5/T2R#[o@@@@WF2s(Yز([},9SY)٘IY4'saz(Z#^F|kіd1n Ur=9[B_>f?.֚;s[uȥ⋋BTF,~&iF@XxMgyQ%,Uу<#(J<.l7ޥRE;Z^J "vf .-*-עDpo_yu^ ~DOrFy꿆%7Fԧ7M'C;:g!U0ix^>m}+ӒWdcK`P*%Oγ&Zy-dC-jv^Yragۤk̗9k_ ~UC}Ê!\VDZKقYSulq`P@%ZxERCmjRke1.K(r`A@@@@$i+ό2 YMϖÝ3&⿭vG n5 =qI9NHWd֖*oQΗ3#YJ%Q*gLdF%&equJw$bM. X# 7u-[!A ?|^z(gwUXP@uՌ5YNzsM/FJ( JOJujΝkZC8fwNY>fkh}^ rjɪEaAYcU",zӾvz~! 'GP>iUYL$?.P|i{b^rϿ(CuYf˵y=Xyz@j^.UbCgvQ2x/iͣ/    =Q~LJv29g2Ң?c9fiIeUr-U*}ۃWFYԮoRsJUo yJiԇ)'6t|UZUf.jf/3<3Rժ!ޟz s.ԿCT|SNICrTvymkSUhʹ /:}/u+t*ϚLWo+Ocb޶Z_SUi:]}Ry t^st͏kɛ"Q[ 6Go"m_qWV2Fؖs3V/i/yڈ~6_ZNx    @ x}0{doְb7TcSZtm؟OAE*{Z m|)F.P4rzчu225Б>29̳V+!Uy+U]3Rj!k9ʎa*$QV7P7~oq)Lpv+&;1;Oi#Yjދ;ɡԳR# Ȏ  lLĦj3_Tg'``r77~KޫVK*{Tl;ӯo?,7$GZnkIM`%s3Tp_蹳@@@@ Oվzܪ޵ɮy2Vy7hە*r b~ό":rNy?wr0S ˪fXPe*Uocb_ɦ󜶱SiI͌D6UAޞ7Ԍ3w&fѿnFHi|6Ԩ>c֖6I:V*PETZcFvF@NX V-|ݫT9mYӣZSF+W_;ۿٻ?HwHC=MM&R͌ly(ןޟ@@@@Wüt_VMZb˸х57.pF;>)y5^[TRoR򮜮ǩ(75Уլ{˥ö yֿZ7ޥO+~|p\2)Y*UçZ[̏8'izv&ei쐘F͖  @gz_`9Sǘ|-rzhE31SZn~)'s!T@=Tx(^@OE&)@@@@+-3C0 $%+uU*'ec5ۚ}}ݪ]?:0uM#EcxvxV܏%_0RĞѐH tK ?*eqV4`+8}m>W!~}W{e&jTpSk5cR֚֬W( ~|KW,Tq_uNG@++,˔B4N.SVȗVKsGs5vo.chE7 HMLRpK y;/c!    pBTh0]wʾF%5K ynO%9Tt]ޑ=ՙQ\;$^#~iY6SѶMT3QlG<`CM6SF' +&)5tR3?YTt[_Fpg=?E  *{YNW26/֖h!g蹅{ToQv^4nPVͪ#!$~'f9a<7):+b@@@@+zQ. ֿR]5E፶uU~M7qf!lf\;V5,wPSnijYS8ҭ瑦;AyQJC&ߔ؜˦ r2B%bݑ9Vsd5IhM`Uk[p;#soje_Ys(w袜[T32?HZ[f:@D$94vfVHsR3 $JP @}Iʝ۫:;J6IFO޹F)P+rm2_ -4sU=ZK>@@@@zGozrWs?fL}t {zt@oҼMϯ9?| yVi[Sj( B6Ϭig~RK\s`ø&oPU|9NޗO:XʱZUk9뤼Z]9:LX|EٿV*G^*>g?e V+WJx]jjʉ   @W .hGcegTr2id]L%b;/3]+Ua{e\]wYE@@@@.v(CwiZW5IY?Gr5x?PV޾Ll6 hMj-cf,K2}_Vk{jogKXiVo\-W}EZVmdןm6U7CjεQ_>v- 'd =XUju?]_Mg+u9C7Cy!-עjb@/K{}TfjKCyմ DcyB~ҿ c@@@@ kԇk}gG?yҺ`^xsD63@ќY*~j65y[nP?bDNepVvRM/Uzy6cֳ mCFTR0ˌ Zz c247i[gv6ESCFWxPEVhGgVܟ<u9>_5 sGI+O%ژsnO _i+Ux]]6&9YjNSKh=MЙEQ\ޥYVjnL>qj-,-b$gZvb1#0u.R[Tֵ9Q.o, fD og;ђ͔/5Czw#imE˥e͗o}[O/ern_Ѳ V9*Yrںܢ˕?&8XlA@@@@o+c)wrxo|&-RW7:˜wV*)DOʯ~vmcߛm*X](_os5j诜rKVCGm22L 0iNsŚ˵ZQ`,U>Yy̏)o傸 ڑic-mHujTzEkѓ˔MgZ9saCK,QVJ뽁9X Tu%]ZBž*-'%/@@sBGRRV9&i>\d(CI}[WnZDY\ZevTY%S&9J]Bs#OM7UsJh"~C/V{Eb̐    t3"-HYg~:1O\9OںB\D-ׂkfmz/]Yun6]%g2[M|ë $cå*{\ ndW2#D5ixֽJnU3,"y͟Ycɣ\QS'y󯗵E <y^#=*3]/yd֬\;{|-5TjYc]lݻTo/Qvd}|VFJ5R9{sV(1u|/(L  pBO(6H==&=I& eu8_XJ.6M|ڠ^Uv$ @@@@HUm#ΠykSigm2ϟ'S4Kz   ;Q] nyN>hTwܚ_&3U?WS@@zS ‹֞*7Ձ VzsvEs~DCr4Z|Q;+nL՛8@@@,`F ?V/BO|Zdoz?,&  #= ?]dnhÕ6b2S9Qޡ}\' Nӱk8J?=~Fipބ?&&/3\[vmZ[kع cG jYV>g:_Cn 9ƺQinxZn1󶧝аUp}9&1/m74o,g==wUo 8'S_s֐M=cd#~8lG3m߬{ε[zPc0jD/vbڡ5tS4wFD9Jg@ cc_|.y5*)s3kMI+}KqMqy{駁Z[\ޱIS_i2$1   pJ 4|N"o[ǿ[ͨyg6p!^!  _ۓAh_UX {ͅEf9S&nt~B; CIjg(_B:ݡW{|ZSΏhvUfFizk:sSih{9PeYq4fLzF\#lE{:Rf-$"6?~-̿bB.G4=1z]]Vمk Q՟{F?#g9Y:Mjn5'ڿ"$ơ/ѐ?\΍^5m}t+m\oxnS5bCXsuhvDNyreF{ ~)UD9{Y0UP@܈F2>2?9@hn+>)/}D2nеo8Nj5Z?g:;F}~\y1q|hڵnC_>[lQ*<" 勵j qD엦oyG;Ãr'47Y55Ƞ묭hߔ\(kZD^O ~fksO}MDPkbֻwh/,a#<  {8:4(KҊ_O:wrqA˪.WƸW {l}YFVÊݓ>u답%2(B6Ni+ltW45eYA%&pDF;yqe ؘ2qP5+   DpŪzw^ۭ?suV@@@ LG~^ ~ʎhrUܢo>qܰCEh4oܯUpwXo齯\u[~_uсQ[Mow{+@vﰓoӫ|̍ju3T=H-7\A޹#yFgFOӰ~ЌN;^W-iOJ ש{愬^nh{~kxxiPu]ȼ~Lo?g 2bAOV.P;y6`|pa`oqG?0u qnBuiN_4 |#%m&ULcCl m/kj;};h[G,78X-޿u:b)M4;ʃuM?u|Jڽ6rZf̏1N=`FưF8WowAi^b$ij5}a[n+r}8jVC-#mC:ƙ kѾ&?m36ԽG2W}J`*;/k\k$K?ȿ0j P\Zv- x30>-ZmVڒu:h="W-mLG~Y8;3"  R}|tLL#`-sŜӗ@@@ @1/^Б55m۲C{_Q X'>ZS|UӮ0cz?d sUiʌ`Pus/Է_ؠyEZZiKѥ&?e`P{Aoi@Y}c`;}iD6sm~|T 1st?%x;s q~Lo 51eH3{(PNsɫݗf!M2 VVN:`3M÷6[S+* {d}b)J ciA֎35L0Kt}W+=Wi?32Mɟ9szOдetB2=v`e6U{Wa]qes5A4 AVϭ@;ɑyc5,' <¼U)%׮_i[eKNݣO)P#۸-\?%V_~=ij5tW~/^cOh?}K `nWݬQ!QY;~f#Wu|vxG_i&yhwQg+gېŭ;BK9ذSu0XBvź'z?_htEy   p \>뮼Pm/pJq  gOՃNg; Z$]Xlg?Y3ޒ/6Zo~'p\0IX&^/}R_fkF-_Hq?zN!cazW U]Gkآ[ۿ,9N?􊽡|F\qs? |pyO{G線sT$MZz8 G]{-ejKF״ow{@rɌpbTE!BCb/z·{| 9p{ ʵ+}Z2M=2HrL]2$Xrcpǽ&@;z5dh3͘ӯo!I   -мI yL+= t޹/t8tIZOsf@yI:Q8 Cx=Ÿk\}s/c]frNm|7ovS({n3̿o|q4yWFk=aJig; 4._w]r_ɣէc*hFJi Mp&Nh; X̠$;gG h O gl \|Mx݈Mn,-Ϯ&HabK _j1S[gbo韖dl}.Tg;ƒK2ٺCD3r1tD@@@@ @g3 3@@h@:q7^1tSю{1<ȽT1[] 7_ZҎcGw|mШ6L}Gw|fc >`t ~pa|Q sSu>F ks`?4h:sR2_(ۊk`J.#ځP "tYϜf#aS25n?.s,S5bD9C|ec77ב/Ը?k1R&ߢhT{C@@@F  X6]97)"  Ѕm6uaY]{|NHKژ\Qܡ#;ףrTxLނzF6\Cr9:iou=Kn4ΙWkqgq05,PXL#{hPD VN==V:`Cp'O&(|Z䮲S.0zh('vGϽ&i |1#e;\fk[ڳ Z"׿#S< όW   @]:@@:^p@xg@Wu4ҟYu㯴!8Cw2];Z}MbSlғ?Nh:O#!# &b[U^ތRWZsNх3$rP   @7   pD43s';= ]1Avܼ?z(v߮aI{߿̻]C 7}m؇t˞3O3v,D*z [Fn֠v_f{ F~ $/{H+☒FD:6I{'skA̟j>1Se)XZׁ-   IUPGЛ uE@i@+KwUp.&S\ځ%QǟIȡ)+B1tӫL9rTMdBǠx{ 5 +@$N7z=2{6[35ى=e*2i+1.l-U7vv.8jG˗u11,Õގu E5y$C@@ D Q_3@@>ζv_%W6O?Lz{n&4_F_C{ 7Ym{}Od5dxVxd^J7lQîm::Qmn`P`{۾Z[z=o/x:l>OF1vwzsemo~}Nձ=>e6Z#hPrh@U&   "*(Gp\~@@ .0U[v/Gis^ڨ#!YY<ژxz-y!4hz[Mo5Gwk k:;I^b'ݻ n%+hrAѸwosm ke^7`>zBD+vse66 O3o/Js)ݔś+6Zy;ZA][3rC@@A=ezU  =UGy5ռhퟆu8p6~?}N L̕coבW1ZZvk?$GF 3s Lc5y6>|CЖf4Ҩ120x~|+zP=':X,}UBQywHiZTﮎ`ޢ#v}O-Ggo;rk6y e1MbÝC'@,wz+Ic~t}֍;  س m ᐝr/?ab~sٟBVwm~vm4_m@Ї2TXF9϶퐲_{_V(}B{eXx3~tu\xaOѫV@@@@dP"3}W  @聁SY]cWmF5L] >joεz#}n]vi.3sƣ/eQ__sy.z!7֏,^]PM]ڳ5Vo(]7|M۷n x>Ш}&튷x7|C֚@r=s*vj^`]ulJ}R[OA}[Ogk8ڡ/>Wgisox?E [@@@z_P淃jӺyu V0@@(#x;œ@iwkĊ/yiSm_ j[XM_qՙ<^O<.].l MczNNoke^ 4GI׮M!CG+S^)|GiHԷJa/iX}pOoK{Ҕ[#GNO`m&܂c"!2>i3u[wkG|i˴ ts-=Ys?x\oHQU:'hIMH l8s,N)@X墿~+m4`#uκq?Ir:[UV^[?zK159ڿcnW,ܗ Sk@hf$i/TkSE1[\c1Hn/a_n4u\cVO'ʽorm|LU*q+YZYwƨӣ#i.wnL+Wy74녛5,Z6QKf#   $5?e^IqBfzt{3 Ƀ{=Xϛ-ss:H^<0yu8Nџ'om6ϊgE `     $@ (u_֧@@@@@@@z@w1gP@@@@@@@b @@@@@@@z:QK@@@@@@@:%@`@8@@@@@@@!@`@N@@@@@@@N )6B@@@@@@@w;D@@@@@@@St@@@@@@@D-@@@@@@@b @@@@@@@z:QK@@@@@@@:%@`@8@@@@@@@!@`@N@@@@@@@N )6B@@@@@@@w;D@@@@@@@St@@@@@@@D-@@@@@@@b @@@@@@@z:QK@@@@@@@:%@`@8@@@@@@@!@`@N@@@@@@@N )6B@@@@@@@w;, s$;@@@-t]:k_xz@y  _QZE/;S5Y/_jx6 ^^NDvVܠKܭ37^o{Ңk ϟ爵ؗޜl /1:яKexVx5QοV(xT(9ZğV9U?9EZ8*ArFH ICb !g-{gNy$gf}ך׬-יּ{25?6 M   Po^%Po SQmo3֠YΜrn-W8}9Q kۙ>ް>%Vp{y" H*:Zv#   @ UׇH J2B6_ l#  n1^6|b6|q``٤Ğ4e_CtI#4Xt?ߴ\e& ,";i]pvlhS@@@=_uUNֺu퀾uy:koג>>e}   /@HiUhifq6K_?7S6Z,   $kK܎5|:4y`#6}r&<JߘL-   h׻:yόk[N 0w?Yp^."  Sf3W|y7)}9-7z-'@@.F ؼ3;kyRK̜%)_?>k>/n):g~[wS'Uq6J]4g*\(%]KLJ+IJTϳTwR5sDlzSq8~JT>O2Hg̖s\/;ufgi']W W{TcMljG8%th2 L,u4ekXv*OTfZ:YpiWJmGAycIⸯ|ΡXX> q=]![4Kz ĺU`_85/O?u~I0YmuWFˤ$}uTn=x7>cRY[e?~C*;Qf:hbؿ&F1(+%VݔhIߚQ1?v9,_aΜ8N=\>?ŚSgn#ݮ?hy7K   >*Oi/}Dw4-p_\`,@@hN:owsV2eݺ&h\utҽ:6ʼue+F#4tJ /3] {h<+^,3Qӕ^4޳c%x_ {V*kV3j-Y2[Wk~~^{Y`M:ώ5ʴ:: J=;|ֲ_}Vݳh/s)?t}wϺi4^_S3%5GzRsL{ tiO=Ϊgڃ6^V^UI؛i %[/]q's[;IZs9ȶ8ں&X+=x}Сcԯo]ƙhgWUuHl4Ƚe&fMU~H[iIcyd3 +Il>x4x31 E7U+jc9F<~6;=h܏hՋ]-X~1/F}X!K>ݠ @ )̱    } jG/ߤӗs_e澚,fXjvi}Qv+C1& `u?ӄFۯFpH9,'IV5S;,L ;mf0   @5Z^> (G!3G@@mJ[XaYsq==,3[༆<}cF;zIA>?c=9|Rytbҹ<'͓כ^Ԩ鞩 w铹fpveOzY=vR-a*zMJs]7S]ʱgyvXiwեS͠X0|r{pg'J\{)m4؞ߚ> fMsǪ5C[ѤaV6O\yR̫\V@իu1J{lCڵdIp_Â܊%J enKey5gJ6S1N2ϊy?:]^ ajvVM C,6ޮAY3oϒS滋j5˃uMX9vtߙkh?oշk YwJ}vz@cvHM3so:~$ lP毯Q'oe4=*)w#0E ?]*Ifzf\\y]u?]Xy`I)^a;Nv<0󔼦UºM-VbyUD6[Sn4(XCw]qjIqwZ+V߼+z- 7[7DMV83T,'{k   4Zcw]WayFZnKۏ{@@@ @VÕkU:v٠UTYӽY:8,5M8M vMN'W'kYc<9͙2߼=&N&X%S膷3 ˴?ԓVf^Wiś`iwԖ.ϺlEl]~q/յwfW@mWΣvEE ՔzJ~A%OAVXIkak) 1|6%40;n튾yrV |CY;5K{>AZ')Sc̫>$l 6ks;5 NLљsTn<\TG~e>c+Xͮ> [disιUN6I4$X[_TZYZ8ORUr?s_ީOff(rZ}496N1vn#۸;V۟W~kM|~=f^/$jm5{ϋ-ck|0FMVw/5zc\yYzOaK>{[滫0|ygϔIV)-Q+LrT Ok{lzBa;@@@Χo,_f~M|@@`b@݄o<c-gn/v9*|c6=VLI֒n 8y?13Bmf.nW 2w"3,d;{j7>{- 蒮y>h#_k$9>dK=ק–gy9L3ykcQ>gVrfXZCΪ _:I߹OѢcO oP~k> wNfz\b_̓jK]ݻJ^8t~8; Y{ҦsܳJ>E[τp4GD'OҕKͱZG^\П<5xվ8l=GWeL^P@@@GԮ~>_ o<  E(&~r"gWJf?Ym_}kj]Ir3M;qdO91z{]68OYB 'wWٵgaMR; ,ՈK.>ui3f[tE8A˅ؘ4#@H~IgT!du݋<7&]1fM NJ]y4NZ1˳C& 6pUsdwWIR1 >{iT|S3}&%sX\ڐj{kZ6    :RlMԷb@@<mNGi=)~OW"e5Yֻb|zuyuvuGo醯`BzWa8otF>=zT<Ľ'OdV:nhCMbc_GN_N;٥̠yO>631~{V5]VF~Wr4q u&`e޲E) *{]Qqx>=B3E8ڮf8JBnY}~ל dGPe{;}6CEGltcG8BOg6#{74tҴǵ3Éc5's,5ЉAM' {-.*x"   dkߤoAAIk"  @ỶFQvȞ gJ]Ҡ@iQ'd7 [K=S̠ixwmSj/ȳJiɗN}:]:Wg쓞 f} zK0}V Sx`7I Zeʶa2{%[ΆҴ~߸ڥ224IסgN9@@@$,)B-]"ڂ  p \`C?e*Lmym %ѭ0H߸iZ_ ӿ,GT4G|ϲ?KN $'RV:D@@ЇYI'ý{O8mkfT),kKW`   @k ԇ  p1 !)'U 7z8Y/h@S֞ݽc 0ywJY?3TCԘr]F d#a5%45ӕ~GMEl?.>p֛r}z%Y(J]ho5 |jziKq@@@.P+)`}) [@@h@k?5~e..oS[]vz if2ӯj|1=gޒPlQ6^W6h$PJhԔ7!JO @\t[Iѿ :{R*m uT$:Uӌ=Z'x]}x   @[I?؀  :;򶎆+}OkGƷ>ڛT`{;kf d\y4+vV_㵫6sgUGkNd!0K֚[7(ѵ_hoPvuͼM6[:'d귶 \]dp5x%Cy_mUZ!cϬ1t6&"h`6"   ^VRY#I8@@ \pS%K(~ xI"Ho#aI^S]GyTO|'c͓R3$*W6o[#Zhk*B&PDz(s{݃ej=s/+OI+l\A?o- gtp{p^k{7T<.ѢJ_ēZ[c,TKbmŏ铢rY2߸jORT>زB_d{_O,ާo춸~c_8^iߓG@@@L Xh^$41@@hU:z|̴~*;ppu[o{pv9*̂p`]$׀]mqiv}S_ɩ2I/o2=ꞝ@JW[FĂmsb#h?]kpϫ!3pf ؿF{gk.m}x^/ɬ\:F+2e)>2A>X:C81ﯼ[]x / {zjֹ5=Z=oO)tۃiSyʵ+fݧOB4"brMx(pl 6)U#G$NH;~djruSs-Jz_+)Qցֆty9Ә3=](گ(]Z]c"CxF̾] xڮC8ژn0xc/bX\AYn_NA-zv`)*QSWO{>sjtx}%/wyE]]Ofzp}3~sjKt4vh>`i=Au_J}N#K|D&Gi熬;r" ׇ"Nwc^7KmoOu"~wmtv56W&{   @K D'mtkhtY "  p1 |Z[̼>Jr F)6!NI,3m~^qΘiíİ4qOVmu,/2UB0]zk䊶ze]'Umb:wS+J7by*7[=7Qk\}(OEñݔh #0I/ 11M@@@@@@ <u-y"1%       @sĀ6*%       gS@@@@@@@ک3 i!       ` @?@@@@@@@@ Ў/.       @@@@@@@ڱrj       @@@@@@@@@ Ў/.       @@@@@@@ڱrj       @@@@@@@@@ Ў/.       @@@@@@@ڱrj       @@@@@@@@@ Ў/.       @@@@@@@ڱrj       @@@@@@@@@ Ў/.       @@@@@@@ڱ@ǖ>        BB@@@@@@@ @b@{       !H f@@@@@@@ڃ*r        1  @@@@@@@h$9        BĀ0lF@@@@@@@="       @B@@@@@@@ @b@{       !H f@@@@@@@ڃ*r        1  @@@@@@@h$9        BĀ0lF@@@@@@@="       @B@@@@@@@ @b@{       !H f@@@@@@@ڃ@pC(H53 bĦ6-pXyeHHQJ|6*ӶmSӰ~JQDLS댬X'(ՅΨNW+Sb|E*~%@@@@@'xq8 tyΛ4Mܭ{͜|}ppmlpi i[ݧ#)㦵*oSM;_LgmlP˪rjtܛy69ibMgӑf 3&sOZuuܗggbshJv+ɔ}rr+'@@@,P(V6ip   WEZ<ܩP󍪶 aD}]9Jt:} ĸݰU9䆕){>[}v_sNڒ4Q'|9Ǘ);L}l@@@@~zPV_l^i]Oe:1ZG(|K{iTsqJFޡoS{KyX緽J@@@M lzBn6R  #۠/kEK@4N&΢4wv|;U랒ٌK5fs:J'stlyC)J7Ldv#   #P]n}g   m0F }k=m✍oRĻ4p f3[άS?)[of7TۭrN@@@#P\/g^_G)n@@?co_%ER 7gW J;ϞѱuPbEݿcNO;ѹEY&VhbYU}#Y_Ůww.)}7E(,=凜BI~R3jM~ΜkT|⃕w*5uZC9{ Xr1q KQTm$TUz+Ea,=M"KM؁*7g-9>vĩbTxk5W2t?Pn9s-wVE_։C:m:i{t8{.M^!MX}tiIYGu4a77'̽zƽA217EQ1u$ ToM](u6?sMW^:sXR._+ 7Ċbϱ˞ػǙڳ*-OTj@?8E}z4Ywa>!  \2Wmu tuɺ֋SG@@izZ eo|J93}l͘y/WiAqmg+Fy ysك;wh sjLJ;4vڱ?ި{_R7=U9+'J`[/jUStz;j=cu.eR) (Ǖ`0gŤ\舆(ѼC|gWTUEJ噾 Q(}wZN?gVd85xz[ l|iӭ8wjڃ6NߡVt{\oJsӮ~`>B'E4f}BJo:V?ƎI: 4t9AQ>pSiԿ^nNwEV_<0! ejm4Ƚ9}9k _9xBWJ]J8]7|R߸K.w"7;<9 hc{I܉'GX~WYF*uF]X- !"n[G*WI}˳   p Tumњ_f@@h-Ń~]ab ꗔw|~ܿYfɉ{qJ|6K3pܩlVL'VL"έX%SN;}Wu3W$$DA*pd6z AJdw/);Ce ]_|<ٚ~e&\gc'wmO_J ,P%}8: )_ǔ_0~08 qf懕˔19[O[D8}=7)WZSUmwkw' mrL_ Wt'7|wd7peVH#.ցKXa7Dg($MdWRIRMV))?k`Z4TN4l@@@h#fyN彏0EWVV@@@)WnJf)v:PRu ^嬽OE(qR V>de9X'=~OK{o-qδf=eIjϵkw@IDAT|m(z3iO Lr?e}j^U~N:Oi[OU?}S[՜ܱީwa͓ݟ^`^ ߷u~2u[UzXO1IkXdf8VHztwOZ<sUE֙3%m2n$' XCk ^]+*ʚ^֫!K?n*yiO.-<KnVΜcR LͿE 5˃uM^}\G6L36~*ު/;Un]̗L 4fW335v[,5/Ud(I}Jݯ8>Y`%ܬ)YyϽkǴ_9ѥx kfN,ux+J1hw}zҖT >MeOf{)4Ogu:WD C4(X\3mޙBb_}EL2ۂEpo%>1z6}Ҧ.tO !L&뺙OS|mIz؈   pQ lz>ݩ[}Q:p  @1֭UͪZY xr:OOQ^3ޤ +>i.٤Zp8f*6g!ꓳMc&X/k_XIs͈d>/B=i>YGhfoRc aizkv{֭3l]l~82X7Iftd 1uƼn^ LrٳSO7)ăc.UۗjNS@'tЕDk{׭j̒ml]M}w<IVھM\|.[T&tQx{:z3Gqw,(Lwȧ_%D ٺ#~VV>Ht$`s_B'hC{>kЏ1 i&Uﮪl/X皟x(w[ŵ;|k9]Ӯ6vxS5{[v=L9в@@@(~Y yJ+1?#  @p3f내(#ϓ[+_4RuR{lStGrcZ`m>=_șJjy">`c^ʤ:m{>.ArFT3л޶L_{=j_P徇T|eo'usL|h>gV;Pcb󞭳 jDG7V5Ys33JI M@@@(P_s~>z?n8'@@"&b'N1ȩtn~WyRw0{pi SY5cau\c;´ɗa3Ϳ{II82[2}e:CL Ǫ~;! .9ʮ9ğ~uQȧ;|f8 "St7y"g}] 3I3J5ʷ=~g|߬:Kt&_jϨ}0>k7 b^$ ;s߉Hyz}vI?Y9v{KdBg=::uyh(wh$|>f@   HRo?  @TmќgzqQֻb,BvשGQWoh}6jQ[k]IV0!#bԍ8J{)A-6SGNiOgՠΧCFc:!6G{\(3h+:eJycVA~Wzy=E/5qP꠴mSNs}{}6۫q*z4N?Kā Eɚ)`<1:Ι><   p lzB>_\GVE8q@@V;|*h@%_ntuiYj''Tq?ݸ>Q䷖i5a\ǻKosW{Qtoť7M_ɞsZ Q`}"<íDӇ5!Oz_R2zgqU>)ԻzgCiZK W8@@@ P]n}>;铷*:y= \#  pq \`OxžQɶ GG }ϪQFϽUJ7.t3P#S%Dq&ȓH[i`Їϝi GyU[m 2Dڴ$4͏   )PW\}nu*UAi <]qB%qU'TZ]qJNnwY^F@wQ׉CQPESwExW|gzmfטr^#huU%45UIo2@]_W]տ̋cS| _}7A%ѷK,jI)4劷TNk%^/f h>A\@@@ nѽ[J9ږ^)읬E4Y"  V~?x3ai/)B Q~h]tRJKz fcCTb'$ZHCiHЎ.q=^w+#Դ`!V4q.uݺp5bv)*\ԊQ{CQ!ԜY;Gv6#  4Y&@@F4v U5smvoõqDp_Ja|P%FD)>XWČ3}M/woV޷:p@߄=wjeõif=]ǖ 5BT7=XPGoHƏ$Dݮd{r@'ףoPVR$4A   E&z+>?&gQ~Sn \d݆E@h`=U^w lr?Yj3c$Ns*={e:;AIIck9$*W>{XliB߆؇Uuf羧_[~:v[C[]53h\#tM>{Ps\؄ŭ-Q>gGAgbm1sWl[/>[ӡz@]XG}e>`۞ӗ9W_ bWi_Х|/R"WZ:"  Sx'Qf=>)ǬkpZ  -(&٬]묟7iڅ%f^u|zxgړ|PJ 1W:#Uؤo}^25@IܭXvn `WܧjW Plcޱ.2~jS߳sMU чӆFi}CLen\Yشe:"۞s^J>o|*rB8fbYd)Rݵ|h@҇?)AՋ#,'V=탛~ikSgwo|Sݭx ^ L l^0yI؃   ЬoC@@hg?1ͱ~+7ppqv}h^QǾحNgӼO.Nf%~Җ9o*ۊZsЮ''^/h뻫|x ʧϚI>9M~ofu{)Cw[~nDt_8sn[!j <$Lzgu?~HG9Oe"     4QV&i&=]{0>J]Yf~쪷zcZ$eko7z<~jyx;㋕m1!Z<;{ywK[W?\{y}^A kTc[w)c{0zc>mJPmo*[c`鹿3lؖ.gN?*da ~nZ<CwHQ- Ǭ~IGO ߕ%kYWCpgހ݉*Ƴ>@?~AC<x~)zk\ףu̕Qҙ{?;i7ïoP6+MSEêu~A%GVJ;ƴ1BvmJNd-f\.v~-O>3ǰ@@@F@@h@1@(odRGg,՘Cu~Ϻj">?Y<ΡQCIuPĦMяs^Q_(iMxcEF;pdT >yv\uo_Su!![)2g3s{xv:vq]({-9]e$%} B0'w9ߝ?^%ӲJ>C^i} 둏Y%ڦQi[i$X踀=G7W>QiddRP3\_Lܝe^-iX\fJfiqA-JT/Cޤ~0?.QѾL"WC}^cZ|t|k7p,Ux`}>nX_;&CoxsqJھuYjxkj6˼b Zӻձ]   !]'i{.@@.2OKsΝ[2+vy}@QG#;sB㻄 ֠sfe4q icbXqqLĮkx`flOHQ8ak5뱮O^~L¹D'JTMjyE?Ot_E٣}I7JURj.^cq&FBc4S=|xN@@@@@@>}(Ā"G       4@|%1MP@@@@@@@h$+C@@@@@@@h       Uꕡ]       4̀H@@@@@@@ڪm.@@@@@@@AĀf@$       mUĀzeh        @b@3 @@@@@@@*@b@[2 @@@@@@@f 1        @[ 1^څ       @3 @@@@@@@@ V B@@@@@@@H hDB        VH hWv!       $4"!@@@@@@@h$+C@@@@@@@h       Uꕡ]       4̀H@@@@@@@ڪm.@@@@@@@Ac3Ĩ3DLLLى       prp^ة@@@@@@@ 1u@@@@@@@"@byaR@@@@@@@ZGĀq@@@@@@@΋煝J@@@@@@@hZǙZ@@@@@@@8/$v*E@@@@@@@uH hgjA@@@@@@@p^ة@@@@@@@ 1u@@@@@@@"@byaR@@@@@@@ZGĀq@@@@@@@΋煝J@@@@@@@hZǙZ@@@@@@@8/$v*E@@@@@@@uH hgjA@@@@@@@p^ة@@@@@@@ 1u@@@@@@@"@byaR@@@@@@@ZGcTC-.pLG Nc7NSD7 h@MIӇS/jZf.]ii[lrOmXێ[U)"U%+Vݔnns$   -p*+ZK%ek_C@hN/lYߴ+u>5SY?ʹ曾QwVю6&v>I]֨MJnm;Vg5ek#Z}bMJT`aMfǫ7ϵޠf  |B@@@@:{J^wԵ[ӭWE9P   T*Vwtbj{ ;s Ү,݂+|[}CdTvO +S2`ySvCq@U|D@@@92tWdW]e7+?Hz"6#  P/A*pb{*Һ`C8].^Muݪč]#  n#9aY*)Vߖ(wp3Cާ/ p@  @m0@ D$״4p{j' gFld@@@f%Zn>oj.ĎG>9]Yo7;zb@@/N``pUپS~G^ԤZv*A@@lkf:sך'|%}9-7z-CL'@@+pgPїRtdԯz_ZwxR,'%vSMIE=Sغc:QW)TPTsti"RUib%X־2}yN|S\Os^z*.ؕ3OUCtʾ^QPrΜk5Xyl3mz9-OOqAVLkb`-*z8%th2 L,u4ekXv*OTfZ:YpiWJmGAyղb8 ҕ\Wu9"G! ш>`Zd`\ҽg>\Qj~doMTGc~C*;Qf?īܯݕ&F1(+%VݔhIߚQ1?v9,_aΜ8N=\>?d~4.yF,_a)   @pSʰL{aTCifր(+ "B† Kv D [BIXv*(J.PY&Zerޅ "*Em\a=! dٌǍBA3==33&߮ן.{K_n֒ kirGwT鈺d2vC$8O W*1[+Y^fq\w1yi?Yi酻DOBQzGՕ)>C0ęXGr]sVR͹]=ՠd7VWFKU%zʴuix Ȼ?wP|mڔK#/lq F9q~R ElS.2.WOko}="і s}^KeLƎԲ53;}Kpwaxfv0و_}{嫣b;ߺifO^jd%uꗢSZ<2'繿Xм?]l@u۱|q~Ulۑ\ݼ ?)xFby {Кǯ2͓Xq [dnd!+Tu'N?onoNіp˝h=T'KJ߭AJϻ%P@/ lgy>*ϛHqR7 L[cŃwfKʴG[(/WJ     jIptiFPP@LA>t$/ÙUS+˂1>ɱ*:s#}uj8ո.A2X{CJwNf 03dYO, \@]Ѧ(G7q>ޣ w (X?.qwr50ho3{2OB=Aи0 (oroAGr  4Ήy ??9>-AlH92-y3tJƅn42le/Q&$@$@$@$@$@$@$ީc(7xp@mR̮a7 t[M%܈$?(CgEuiېȾߗV}mD^epswgotS,o\ug7sӳz ^1}ØR*,}뒪f.Z32u|j1k8=ű>$ .c0պwUʪ}ePS+Pu-ހ!?3jeYɟs%}-5' qҋƒu9HV-vx -ۋ$h;쟢{h;XIͳZcD[8KM Ơn7æ5*?؄f Mcq.lo=E zjܻcQR7W#y*9i1?'\"+,sWkٸne~|m hҔ؀lJy6wus]-h/!UjLrlՒfgpPOK@vio.{s֢dً3ۄz?/c@?3)lގvc8S~10܇IPUɲM|uE6k^>{MqF]Rlm{i$c^ 1ʻkxoÛG?(1_PJ{a:$QC18^:w+cF5!ˣϗVߐL ϗQe      ;JGj%    /'00 4h&}epB6VaevlqjE \l{vشp-]ӝVϽ* lOk|ݓJHHHHHH H*Ė"v].ԕVKHZ&ˊrIS.{X|`mudxmʶ|K8dn)S{wcߐFp儚}adIn_]Lҽ0zLYT޾i2m̱Dq3{dYy^j<a؜Ep5hE%9}v'):OL3,ih%NDA?$=Aގmp݉?;#%d8|SnTK}Jv@!4nC 2.ϻ+V%b[R1`TiSޣ[@!K"6p0\ ZkGdZ(DւTd!w=VK֔Z6 XX^IHHHHHH` ;}A5HHHHtn_]*-PLjsCj*W @Vmc.Dbm|_yo`QsPBmчO?c<5Ine9'i TC4!N 1>mJiNsMudbH;V+Av7*Lq&6onM:}peXVՌ;BX.\GqyJ1p;)Ij,02ۇwdtDl>;j\ll #ml`Bn "@2-WH>=>xܐAޔyh̽aK7:u*Qˠ8dƨtWb7}gw )P~Ogq8 | uhp]sJ 8?DsyUy}J>muEq8I]ޑ=8ct%#N0\Tyo)wSpt}C xh >SHݾ=ۍ &ۂ8٭tP&P1rfN4pk'@It_6}ޯwREePOӒg+d n 0 ;n!0HHHHI& p35)s?98飻 #c;ё ~R$~LF1*_?8bd%)x֛?:t:O3mlS0 cD:#CD$ȶc\$-"O!        A\b` ؏: y}]10xE`p*vinq6}rﻮrPaL%m#$Vʓ/d"#S1 3#Q 1lx7 \تv$Dd18yȌOU[faJv ]6='7(ACPރHHHHHHH``qcd    $0'~pLuUWYhGa:TzMI N̐E[*d~I%H`uoɡ8<i4/!_+ȅfxnrHH{KGvz:Rq:Y $Yh34"EYBX>舄Y13GHHHHHHHK 0(`o $eu}'s΍OKѮ񉎏kF0[UWLW{oql~]X,%.ٷ=ـq^:XYIjt oBe^AuD /Gc|h\_mӄur΋-݅lyJHk1/B"iI      I (( ~ƠxHHHH!iQVzO8 rw{W3C>' =/NޜTCVϐ1V& ޢ]. g!Z,(ZJ[lwW#nD݋ (=)NWBp݁36]lh; =.b-UUOvn ,DA 8{54K'y?b8ܹZ}w ̴VWUO.a^n? 3Q={/\Y5mtfbaWuvJR=/ @J%()?ʑ xܐޭZsmW&Ѣ"4[D+ue'#!{it ~y!>܅ul H }lTRn4Wv7PiZ.>|5Dw.shC01 񵭪qҿQw|ڣ[-yYCˋVF6n9YStF[ es8deJݏV!Ȉ;{кvЫ|j8`~Zc8,ttKϋ0yw5q}{5(GY媼Nɻ&ZHG _kd Eh̨1]#3ː@~ X ( &+$@$@$@$@$017``zj \Eʧ1Y!|cz}^ףtůP*n{sNeqUZND̕>_yZc}KO q|=.]^-pmPA]U=[|ڻ|A %*$gRmC *<pzth{z+/ICUGO%G~RGo$=MpJOõv 酘[9>q+rC[Tڤ0ߊ@$@$@$@$@$@$@ $>[P>&)S orIDAT1mRd.C=R#P8~Fo EC zORaGXdzqܕ!n]L*q^RWa੍>'{6W:|)4:W`AQ| pY_w+MVgVpUu>CI֓XnebHHHHHH""ѯȽ|;rFf0Z    &pf nt ZVn |%;⫫v[nX_׀EH7""!}V_Ef =ĩvV;H }qJ6sGqtI1Aogrːnį Wl~aɳXҼ5Z)RHONbEZ *0Hf2mɹxSH5~,z LSRǨlAOoqz(64$&䞴k;m2KUMspQd?v7Y[Qx1 {ӟcwy sOXS[+M>E{gDf".G0of= x=;[=j|ϦWjl{vc-_lhM$@$@$@$@$@$@!`K{<~+2RyB$@$@$@$@$?rE&Ԝ9Ԅl\㴍Ì }V,1Iw3I8̢ѡ=ʙGV v~+WoCIY~UUPV!Lj6qv*-+n{2?=0*JTs,(a:zޢ$b6SiQ:&7 \u' ss$"0ב :$e:)hwʳ$F4g            S}}E6/E`$              2_T` Qb              ``Wly$@$@$@$@$@$@$@$@$@$@$@$@$@$@_-^8pfIENDB`graphql-ruby-1.11.10/guides/subscriptions/redis_dashboard_2.png000066400000000000000000005045371414121453000245340ustar00rootroot00000000000000PNG  IHDRqW iCCPICC ProfileHWXS[RIhH #E^%BBP# ]Qq-,6"u`CM N9g3fPefAĤdP(@DE(MHk֒X:_Eq@ N89  t@\ XM @%8]5$8U6~{@t%YtGQV ''ͅ>V99 V"Cl]LfaY.R!Eroa-C-YaLM *\8"?f l0!f|؞-B{4+ǩYh ;"\gy/dD16ia 3bd<3!eń}fEFMFl0h^ - 束,yQ\ 0X]rߒ(=V3v@T3{5lG(\rbep?X@ [*2sy6trͨGtD1 |Q=^}z1&4a07bqWǽ(hԸIVYe5EkVYjsƗ?86m=O{vNN&:L]oLuzz|z'`|X٬J֠~X~AAACazvA#=FS]377~obj`̤乩iii}3YYusy. "â%jdɷbmErXZݲYXX7X0mmlm^N033fO̻XձqJ'''$NH\x9I+ԒLJOޕ<45`ꆩ}L9tghȞqlLC)=)ّZPjHAg#כy=OH_ޟQ1W_egn|U5ݘCI9*Pd ҝ5gVwenInO[ކAapMcNLهk1wgqןd~" S/2\ToqKK^d[T^vibŏ DDXrk+VlZ[z̶JK?\ȪUV׬!kr&kZZ_톙.VLغQ2eѦ5>WeTݨnܬy[[xߪlmmmo5AQq~֮]_u=wwߣgu n;mo>}-ood65כr9GGJM=-I-GC淺6cV/>>rܓO=n~tg<v¹s?qBEG/\jtñtl|˵{R^WO]vz7"ntߌyִ[=ɾn{>P~PPa?s׿Q̣{9_<=W޳Gߋ%y18Jj7oN|>5]λ4?Oφg&}bk#9##l![z`Cx]= *^RAdE) gRq3J l`/9zzaE`/E7‡7:Z"22e'${y;D|\:;G'kꞍC pHYs%%IR$iTXtXML:com.adobe.xmp 996 2022 1 6<3@IDATx `ՙvpCHHHBBB !FYd+FVZ:)cЕʌ3LjmkVZ*RE $$$$,7yޛ|^s>yyn \quUxJgyHHF@߿=R,B$@7k4[L$@$@$@$@$@$@יuՑ "@a~\ 7;K$@$@$@$@$@$@$@$@$@$@$@$@$@$p PY "@a~\ 7;K$@$@$@$@$@$@$@$@$@$@$@$@$@$p PY "@a~\ 7;K$@$@$@$@$@$@$@$@$@$@$@$@$@$p PY "@a~\ 7;K$@$@$@$@$@$@$@$@$@$@$@$@$@$p PY "@a~\ 7;K$@$@$@$@$@$@$@$@$@$@$@$@$@$p PY "@a~\ 7;K$@$@$@$@$@$@$@$@$@$@$@$@$@$p PY "?zΒ 0{wp.Qop1ЫFo>>'Bw/(;'?CosCK$@$@$@$@$@$@$@$@$@$@$@$@q1$ ٛ0)T^#`Fsg!k_BH|,|D*/]Fst'ziHHHHHHHHHHHHF#@aFH)s _+,3>+]Ri9O I= AɷΡ(=L            qOnD,o@4zn'$,)ZE7>@?iHHHHHHHHHHH4 czx9[*~?uL!O4EeY,oNY~h1}G8- &@a~L/;Gc@w֔K"!4- J~:ʪD@>#xj4||AD]+'Սc佇           ||A 1C`@Ǖs)ߍKJQiY=Go!8>V֞3B? įމ^Txרj~]EaO#lVCGwi13R y5 n G̛R{qJ3\(Y~@||}0w H>Bb UЦ$`78@ssN$@$@$@$@$@$@$@$@$@$@$@$@ PwIng?$B?g}E@X(?4~t7\׷^BO~n'f82N ((:  (N= ?Eݻ0eɭ;8#ƻޓNa#_Axݟ{}tT [C~g^%_!:?럞Ĕb^y`f           w(̏!gI`G?OAD;Lu=&cU߈{6 ־ ^y_LŌgy0`<@$@$@$@$@$@$@$@$@$@$@$@$@y^$@c"w5?Cˡϙo?9󚰉HZLHl w$n=M-ٛ8^g& YCˎ8$ 岳8opgBIg}iFWm=+8oZ0!yf|盘|u6ʳ$@$@$@$@$@$@$@$@$@$@$@$@c8 g"AEt&bQ?y S:tpa'13a"o@A$@$@$@$@$@$@$@$@$@$@$@$0.PN8# 1Y֜|1$-qwDwԼGwq5xM4|ܴMO_`ˢP^#Ee(O?Cm_OE7$(SG׽-k˟kWMDxΗ~aLxOoSK$@$@$@$@$@$@$@$@$@$@$@$0 ԩ13vHDo/@W󂯄_&Lp@:S.Cwc3jH"wɚZĭ].μ \.ĶXeHHHHHHHHHHHHn oq`+H¯ O\Gz [t5\p-m87[?$|+FZP?Ы:&%           v(_;L$p@OS3>h>V,+>Qh?]?(4X_MR|G~~x&eπ69Ar aBTkCpL`"           (̏1fIEo|}EW<㢑Qw^1z͛_ͻbZDLz|e Ƅ8L> WjD XHHHHHHHHHHHHf!0|n^."" }om񊛅 +V{iOjr JjNwOSX+ \ ܼKG "B+/}vfty>~|O>~^kE$            \0!<|d&"Bq=}{j+Kw,<(o݅T#Çs7ᑗd ߈5 f"1&GCףV&YǦWE}H=#/hjn+Ыt D@׶sZ7QzzL> tu/wqJڬ3,85 '=p7RUhK~hpR$Z zt]M;XHHHHHHHHHHHH&0Ba~Owm5aHˋ= ,к5ˠSZ{6__XGbrQ#ҰBy~ /GYJ_łHj7ж4+" ]O4V`/k7j+1 b"+k78FƷ=`ęv"B<.#vQy+ZJ[HHHHHHHHHHHH.c)/_w>\CDϊX "2}~ ߊO坌,؂_|}garB[51io>"A .~r G!(&J֠2Ju{/Tw~*d=J_M X!|zX[?y=jKNJ'+ʧ 0yHSb .lwurڊlEGpa()c(eK`-òLh,o?mo6 EH}~ ]}Ĉ;!QaAhEKdY.RK z †hzou]>^N` \gNwcO>z\hؐn45c (1QU_^:j!/$ 1CƺnTt Bm \=`U{V?g-xe$3:|Od9YE߅sgPTRkܖ|ې3E85a_4%oh9t]7{MDrvŅϞ#؍1LeGzt;fb o̩KFhwa>\/ 7 ُP8|m]QCW%v=&SXx\kF{4E$@$@$@$@$@$@$@WKao6 Oܛ:؉/x([JӲ^)Lׇ:ܓYQ=孭{G896c ֧~'[ 4 u[7Q>hJ$|C{uq%?z -GLdn=6>wxc)s^ _჏0!1CԢ| Q f@މsRIMS m+ip"ތ4!\$碼m$`NMwpܹ(t\nJ b5kG:*,sML䪦ڦka'       83W,Y2/c{J4P W$?9 ӓb}xu]t]gI#Ќ]>vK S a"$}h4~t;w> }K9sM: (utgFl6< τɪ8Ǒ/(Yc qV<]l0 n[>Xm.%h]`\h=}¼kSG{fW`7 `KYE{)c=^$9 15`oT6o˺X-?! Ӑ`{~=lz*:A x{bwKoUhvhLVe4iA3j7l$o#gWb\i1a z:1x]?a/׵bXc_) "gQ\7N+|{q9__G-5o/Jhzo!wrgD]+)ap2T9@Hr>R,AN}B]]4P G~~{1wQ|}y7;uؽ;:PW]l,M6x +mjw֛OBh6LNLEN.O+MCіm㊥C߄0-kx |6d~盈Zr+mi~Ox{U먔uE֜/D7ǜ>kd dtu Uu ^Fa} 6PBͦ\P6t)p- 9юuhІׅ2Σm6Qj]5T go|ow;z#04R67Y/cc[~ "4$ߏζPY^]Pﮒs P{y4H vPb+~Z$B5J}O߅!AGbҕuy`]atmD&$@y >R=ΞŮ^[mLCXsvG[+ yYT^/?UH'Y{S 5SoY "*a"E fc$aMJ %/K0t+Mjڤd?s X {5hl1ݦM撵բl˜ "`'\vz}o]x7W3UĻ+fӰi | 8SLS E Ec~(-ާW;h~x՛_/3HC6;0P2䁯JD.^dMgO託Mif-!j,7=6;inQ^oML+oc*G.>9쌝CjجtLH;Hx33K*pz+w.y_ Brc.z.2Io}?.%\aqoQU4Ȅ-O* eP)db"pۜC^Ae(p\DZu[i)ȞӎÇPH,3=JmOvLd~SAi taԗ+=)E:3kmG"6zîBebS|E\_YB[n25Ac8Vn>WePыƒxG}K"^W= jSnyk 6t|!id,u' cL߁㇜1~*̝"É>de%CC;}\Љj(5fogQta}LԴ",1lُ%ԽE{@)@M&N8x^/md~$7ls--a~3D^jfƝR?,"L\7,҆bC2Gf`ɪ|D q;0+'- u'_]" ${Ƽ~S婖&(5w&a(} Hj%2&Y])ctxx^J% Pu̥+cJÄ 1=a\"4H[$ǒwLgdqVx9d6 cnnVºY;7}bc'~d5vV…*ӰQQO%޷>?rxs]')8)ÛlS^V%O +8yq_᭾{Xcw?RWfxnqNDy=xᱻQ36!k֌>dTLvqƹw>`xGg"7+bs~= 3?䥧{nK{>Q^۶;<)%ۗ~lW<;ىJm8"6cpK"x/g+-U>!Hs(~"M0?9)Ú߸"~ Pu'}AK לMMC }zƢA;^X5m"}DyB2Us:P&<:,#/O!Sڣ.Nl=+ڌowζ|{V ,z\Y=4c:²3pT)Oy-،S݌!c ҟ]J|s~2n){݇/>o_ E~Pp$=& bJWp_LnL!Z(%>N@qm>9) vUfH |cRO(E){h̘HxEH>ؙjBqi.i șM;U,Z U?cαh.}>TJg%}E'z튠 eo$Xۥ݅' k3M9k.ܔ,Lnj`q ~k>bMz|yۇ&8;lufW"3aiX1rXLA~G, ume %떹Hhg-/"P]zueVQ eoO4J:/)DiuqYB :qrq_9-Bm'8!–zCoO<,[]#6qJhcK;eSh9w\9ӣLؿ֣8=?qc7%VlWcu)Ebq[dҞ=kxYh](ڻ6byZꊱ[J^f"OAr|3g#t^(®#6҄Ov%g) D2g%ITm/N΋poY]U`/J\ܑ+"$%NE:v_Oe-0G^qG´YƉ r>)mOd/u-⽟`(9c&B(Qwj7NlRu4؞j3Tflv鎡*a RHX\Veggl#-epܶRezEdTʻ{ '<#1;#DW]6ׯy$⮆6u9A,1dKY6JM^խ5,5-Y3erOn})P ɦ"Mh ?UJT*=NARkV]LƪEf?Mb"uW{ن<$LMܗPٕg3AJEC#N_%%CT,7CF* XHEf< mhmߌlj֛92dTR*1H*[^& .C 綵º=!{ =?(4L(,?FHtc2M       qN6.Ը+ƎÐJ~긳Mw-<~ H͝'hw;˖׊0f׸ 몯/~Mx yüނl̏@?i}G$@:G Zpę(m؏OKmEpU}2!+PdH0ˬ`o]!b=%F^p@(o55(.B~;æf̚ DGmI2־IAߋ:j=-b9!ABrʤvņlA\JD]od b}0aęDyY^:!aeG]^NbX>`cxd Ö5|xV%&!%se=u;Q^=,I ve }]dW_R{c҆:!{x[Dk+kWכJ!sPl,$6J8v!OD$Q/:(omQ0C&5a@ pB-V`BL,ٕqݎzR2Lkثs)~ȹYT5$VFtspHHHHHHH`¼<:sGàoNhwdD._tʃؘDi19bL"q%'.N;9C:I3z>3:<6h_ x'(ϷW6y4q|^PzҳkHc^Si6.52@%as4e2οWtQHG# ǯ "oҟYWTT6s^- /5a4'S܊6$9>$ wظcFh .~Í#gmGyg}ޮj .¼2A!*{Q0Mkpm{q&?P)@kRC,s5>CP8"Lw%}a`uI0 9ZWsfx )*,z+޵ Ҡ+:]E8g#%>bxA]u뜴M5Ӂ# }W5UU:Ta,҅6D\/=36P₉6FiR ՝AXD @sXڸ3 !HՌӧNl=nW^ ClN^,QRUvuTrjG|G&|P C{/Zwg:$.ZlGwL"+"| %?z Tي (J} :}aįee-]Σfoh&]n@,NV4-:Ä*_mk[)B5hU~d#%OuLpqmc0m(BC VAȵ.5@1(J#bΑߋ63:ׯ1~/+̈SO;SpH{ T8W{l%v]f%>SQ׋/BL.Gxv:\ƑeäHʪǷ+ݱE $ īyY{*ϔ*AT~VEZ.8~H0G'qa0"RDuE&jp9f[dHqEii&Ǹ 7h/-i(Yf$ 5"ˏX3g'Ց2NzL0qU@vX~SnՎ= _ Kp*R& OGF:(Ȉ7:&x.F.﯏ϵǁ_>7d=M'D)Du L'Gﰧ5DCaѷ[>"+IhT3צqxοOC4rb#ET ѹqG֠j]{WFxgD|t$=3:MhdS³;OXљkM :ZP|hdNK ')yP8Bq^Dcm{ZCwU !A ?wqe"M6YhijܾIM(&,ޡNxG/c5QpD|} n:,{dd6aino+NtLGLFBxhO@;.{;z Ƴã>21|= L.]͘0S$@$@$@$@$@$@$@㏀f JBcRML*V=u^!ŋikEt 8{{joԮMAeHL0>F%\Y+֒[ܪFܝN,0"7GkGZW@o\6{Z-&Cb?9f>bVކ9?|1z] ;oX_^+% >![C>? .UA83հ2Ѡ#.Gy!MDv.zGK%[sU3;Иs qXd2۠xt]8Kڥɑy T4>;f\tP"`>ƱMțƏ2~+޼$}{!ݍ= H nl"Ոû<Iks#\0Fsvk_/#QYC6^4HsdYW"tC[[6pM pmhn4Mxp3ecnZ s ';PQz^ýGިXū[DyYV|ݽ=/k׌\{֌֭峼Ztz_eֱ'_%kV<4"K+4gaTtTTՑvIDeg?; "|u2'@+/dòdMo~"|q1vuRbǑ={F*%ʖ"xIhpynAOV_DZcX'Ydu]Ǡ=8_'GB(m-ӎ"7JJE;p 265-klm8@IDATB0+{ҟ^O ;lbXҠeދF5)&ʹ+ zrfaK\ |a~<$26.+0d"P/c b"$ߔ Pl ]t(ʛk3$}7Q74z/ujw/A bGU:p0#V[/ʐS%;y^rkoy-91wkHHHHHHHq)&s%垑z/k#R{v/n@*xg䔧^&MhMR٭QD%\Eԃ\ũ]E7uQmYԵ$$|xFgUtˁ(>ڝ}P։^6s6}""+«+ߋ/@f⫶e啌&<'^"D :RVw;L<1EI}]*xo@Hx9=o̧7t܀ދ-R.^垻=y" dQp)2g&Qx ܌M@(YR:xubYԇ3 HU]ΔbŃ ʵiL6ac9߅K%$r˵kqO@TVc@%Kܯn Y (; F[NU݃:?T-+֎1'Y~$>)"xVŠj¢XzfC/"$yvg{:HjJ\O_TRC}T?0иkWc~mB$@$@$@$@$@$@$@7գ ?z> yf7g VTywb[܍E鴸|''H%Q W[f:=܊y: 8wXk~M">ffm7T4zI8XHW#^[/*>hd z4E*Bk^YcÃȟy uS"Q,xߐ{ 8=`XW~PߠW_}}f\BZJw5H ՘z\ȯEwg, &mOKOؔtwV|P:r!KUTW #mddE}e2yL.4-2D'#rbcR:H.ErX`jLM 7vZZ\\.,tVYc>=ܣ[Hxj,h>[ƶV4+EIzWB|{jCӝ8w]cZetש{EIv.%zHf-Z_N4+ Mۿ-oqV2MjE};%ufvEblBrzN*:hnZk[#=阏>ˑ =U=~ IK3x\{ݪxRgvVw6dw'yK: ԦC]x4xQv9\!~bD_Jux55]kGfBEDW\+V5 Y)|b4Pi9Rxp77P+{"U߁ ףIQF'}(kqi`+mbRQXkHͱS0C5Oc4jzY&EYRH`#IS-vO\Nljr0Yk$$      i x&c'>7?u7l]XDG +kH2Ƙx.`H}7"WUGka4{D{f@X.^~WrjK&V l0wXn~Uּ2VGʍ%~h>)z.I(Fo^=b< JGEWQ{ C^ތZp*BBTO}CleU\rWs'r KY,6һ-)Y?܅j>YkSn[X'tlb'E"0(QVv|Ef!AԕxG( c#59SEEyBÙSvlb{t(o3ĹkqOA6o ̷Np\/╶O47ɁH>en$D|U*;n}ʭ5e2CuX,^6!!r",¼X$$ch)J9 ~Tĩ>4S`_.žŎJPݻ#3Lm*uג&?{uع"18OτN?i|E$@$@$@$@$@$@$@7+Ob o[BcG/y4l|m VI}xie=rf5zD܃oeWGs=10Oux<>gWXP)f]xbl?RU⪍تc/q-xn\&<cR0݄DgOY܈7n%V$mMly۶fɁڏ\E#G< ֦$ r\t2x(~ݓLPB+kkB#]COdXv ;4ɚ}׽¦hI\Hø"9o%ӚգIB!a5LZ>-vO)vrjDNƙv< H :W;=WtHPy_uΠP&}ܹ:UgP K$ 2V"/HdK(:{J](,!VVRhR>l@qq%_+TXfgLjCnDWn~qX0 1qfbRR45aaaujP"U~ץsPvh.KE-;)=# YkF9HZϗ|M^IsTrp]A&29#Q!"\ n"%,d'# m`Nf"B֌3QzθFR3mL8o#Eoe8URcdir^8ˋ42_L@bݙu[t ws /dra%{[W? c~m       1C@yaڎl⢇?ʹXb!¼8{Ͼoӯ`F#y>9la#Kܳ-@zFu̍"_Y=>?+_>">K : w[D ~٬(=z/BOsaMzs~= ЮC_&$`b4RfY3:PG,I |y9JQ#{ d1Ejo!_^_o nㇼ/ƕ?yJsU!va5KS2]Tʜw~; bz? /](,|'G!gA&J?4jD 뤉FL0ޤlSh& w&V'/G F n.7%׃3W?qHHHHHHH`BN1:/JDa76`ͣbxIh0aDQO[+aO.g1D_=/J&bxZ;jC^nƽ+\l't ՚=ɺlU ƛZztv^Dy3XXiX'4<A a+݄T 1PWi>ur{&0[?wyXuu,םpE GlܐFi_惟BU̒+` 0y^|'߇-NH(HH}ʕ2 ߉46ck}NOF: Z᣿"=?1a%K:w[M@WIf2eqۡ+_ꪐ>;@u%e$b8:SnX*{/-ȹe6'*j~Z'ēk 0-2!bWK:n Wh/t(s3 ə8}jѭQeayr=,܅ Iv`/$J"|'v53_aX5;|px/[dVb+ @r1hڣ1--Cb/ K}5hij"K?IYX:hԘ\9fҁ(,q݈imj/:bhQaM + wm Nd1Դ1[Fg`|V 32&s`021U&&I#4 cnk{$@$@$@$@$@$@$@㔀GNql>#\1ǏF#_lןE)<a9|#[ã۵ɹ%g4ZFeaO|貆wrGčg+֯FPjW+u'G]kQ&/8a{3EXZY] e["J>$y7 >+O5[ # &%]LՂ2iŤZle{}fΙ9g2gw 9s}뾮k+²G3v45k?'UԟV#>ms/hx>j fVO[.d^bp΁]~L p}\ FֆBxLm=/a</^ŻkHcю>Ow>.D!E|GDͣ;#Z?X o~ p' :-e@:ŏGHĄځ %Ώp`O>i?vߤwaH{R/2e47]؞p>~CRvƏ .Kq5qԿٲ(3];߅O@A$]u b{*I),$5$e-.81?~'Es7/ę{!|R[1.4qR8˯~#G>_-&#u_73AӼ>|xx͔ݾ#R#oFSp/qrMgFhxG8_[2^tX'L'b3q'BR^lۿ7`%XimNh$~>ܿUu_푃_^+bF_·n1Hd5g'k6e5DE/Z.NF,X2?|O||8J@=f?ޛb!(qA4._j>m7gi\Y ngO1sW&! SφL?v9L06Z'*?6Ky贂p;c>:.F<{~x'8&OD,w!XN.E Lo4NH/a+u3U|i_O/E^O_8O~J~wn|J~oS.Z':":" y1>       y'{t=&x0Gx|֯/2hbZRNK 9o$R΋b_22-F1Ư?KPPvgq,fVaƫ?jD_GBA$^}ޜ%&HJUyJf_3E5q>_y?Gh?D81~܉ 5{A'O _hhƿȂ 1}q+w/ِp^e(z5jov 4u)&.|#> s:g"y E__mg`7_!1?5߸Yt@v_FE$@$@$@$@$@$@@V|_kg/ʢ9}M؇'2/#w x/0 qt}1T (EU7@]7~8y(N/oM;>GA >·ez!8op>SļN$@$@$@$@$@$@$@$@$@$@$@$0a D0?aKnjx/6\νBg/sNEW*#:b?O`"Zf?no.ѐ)>>|4C?z~ ezݟa;,J[rL`̻qcet$@$@$?2F$@$@$@$@$@$@$@$  {F Xrk%WwoS%" ܟ+/~EL+uZĻb6}8xLGҗQXExs|s$N 3,XMp2HHl,r>bg`        (I&6_Oyi[DU8 \*Z/GLͿݮW=~+OΜw4&6hYk7"_R8k@`|G6 $lW1Әr'1 9HD1sW뿈;1i;O^z .@SZ߈?)|,:/+DT1>y5p^|˿a 4e&[ EvM-TH$@$'0^H]y= 3(BH ?Ο:3spao̘yL "͎>y8u2hHZ ፿G|~U,5wU+Nwh%) @%XGpI̾\{!K71 .oq'81{ot1U4 yD?U(>x _<"xZ| ݆Ngv?m;|Ex=            uܿvK!Hy6'SP>]=|M λ`*f\;W~ qO=/ã8-ᓆK3_ f?UGc=';7LHHHHHHHHHHHH G(HFC$&nAo^'OB3mqqyE9>V\zi'={?},1?e7| _݈6 8!9 ̟LH i/!yxo>onF/r.)~ ^zG.NxEx}6qꞏOk:וE1p`B>v* HHHHHHHHHHHH (!LFE$,Y_E`~OZ\zgQ>3>a"-{M}g3eomy\r1 ˽z4ͯd=?eHHHHHHHHHHHH!;ђ Սp45u(&b?/ppQ'Qpya{3PH=j& dG. 3@`ZLך-Q'yI<©?Y34i baHHHHHHHHHHHH @QO% x\0c:Qԏ::$Wu'/-{Y-7響:b#1c{&^#            CbI H`߷GWÇ-۷bg 0 ">K/MOz n1>            <`> @ky_gx,8#3 LvOv,? 8Iy'2n              IOI (w.&             ( 8Iy'2n              IOI (w.&             ( 8Iy'2n              IOI (w.&             ( 8Iy'2n              IOI  p2M$@$@$@$@$@$@$@$@$@$@$@$@$@$@5's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@`qLHHHHHHHHHHHHHH`2`~2>N$@$@$@$@$@$@$@$@$@$@$@$@$@$8 GHHHHHHHHHHHHHH&3 's$@$@$@$@$@$@$@$@$@$@$@$@$@$@x LHHHH802‰}'Ns дk%!         G 'p(БQ p ΀2ܮ Wf%Caa33 p$btO%_/4ӕVxVBc QjfH`RK[Q=BX6Lu=}?vM.C|^] WNF%6k:0>K\Lyf΢ࡽXt[#fy.+"T+2. { }6 qI(,}f1H.WJK<!SڷAH/mԁ2xRҶihoƌˤN 3Qs\/q"I3_=W|.~ϝ ah.]Y 4gP2V      ؜K$:mؼ|U ^U5a0<,nn ^8ٛ N w SŠxIh y;@a? W@QU jCNgq@ȏMd 4ne0kmZVmb m/lCsqF1ZNsho(g}I/غͷg 斡z8ڶ4]،e5XP!$;X^^ƾh4k^<<,Dϳ=ċ%YTB8ڌfT7660X]̌)w3Jn[9YDy=>mAkPYrNXĬLA@uEzu'3-*+J{kepo/:nFĈ<5siO9 uA zubVy}6wݾ(oסn`422ˆyjӛjM|gO2tJ`[+V4HFƽR/u`ۺ;܏5Ҩ؂T;8V9mϢ"cϤ9̞ӍX}罏qs䒗HHHHHH/GB&]6:Zɖ<"}jQǶ9"ݢ;>CSD=<%=|^ss|mTU;172YDp5&6a2I>=P>عkD0n(,^ kUh+o `~"c)<.,xF=c>- mh~eM[e+w)Z\Җڵ- {jƚ{*sn[Ge/x &I.֮K[  ]јў,jF(Ӻ.,1|wݑF^RЁ]UۯDnvcsp/nZ)й9]Sg\ce^Ix]S~e<B9LsM_l\1|;*8w{~Y?V=MQacֽ3'^ ֎m~pF݂gY|V.HoH37EKs~Gq1*H\R2 ηU>⨛Uf*foa5|U:˪Y 1Z;61-Dy4)zɳPʣێ!!.AhUW%taΜմ;5L(&c8,-Qn;KvP93ѾwSf&J_3xl=8!{cG=O֙ r)3U5u$Qwv0cbHՏD*#?m~ q:e3LNi,]P+?:\9UF /~oc,)c|3Vk_^%4Eַ3ͦBȷ;鋪,AzZEg&ލw kѮRQL2%|dwS04ΉbHHHHHHrA|TC ÚǂtzWcm_vu?Fz|2g+>DC]lolKgZ6"@Ek nEgZN am"MQۜįL Q}PtgS\";c)L.dWVdܜүm`Au },$ Q{1Ƥd䛤Ov'fb^xhGh]ڬo-^>DؤϩCMpQ o 2 6'?[8d)&Y.lƼUvڇ2궥sN``r[^VoEECR!Z[>N!?ߍ%+%"`_b8ҏE&ER]`S}Z`<^1[$$ގ;Dە'zyc7jGjޤ][#Y4U#Hj [н)j % } bu݋2w0<P,25'/=YɈ_Y-j#`p KƬ?Enji8~@鱧.1QyHHHHHH:b-M#I)&@,.xTJ. UfC 6Wl ѐS=hzMJ<2hRC&;Wy䦺Q{"Z|t/h"؏G1-ښ"|}26FY맮7 [uۆҼjzh:ܮixoE:E(xS;*8EbFЖuشIL6/2 aΥ(-!x}`r"BU>3āEY,P3ow-d'+Bv n-.\6q B"&[*[=(BVP7,7vA̓]ֿSm;qdh̸꽕^Gb`ʍ+ݤ-rIO XT%,r>^v5.|>Q|uU@IDATF"v4>}+3΁ %";~3kb*gΛ֎.RЇn1ǓǓIǞvV0P^&     HO)P!Da4B4% f.kwte'd">ЏBM~uc(mT_hS0uݷ}YҟdZ>liP[R%o=";DY miڹsX*J: ])}rFL6WP{O\++v߫3{,P>)S;_9]C=SmF`k^VI'1> |q-c7ۋ/SG嘻AN,$ wj)lųM^9{oeRJ*,3ʫ"ZзZ9@^r!;׭hU(D(IۙV>i%[`F2X:q\L%;(_֮C8!t6׷ϬGea.N#sj93ޱd``*$@$@$@$@$@$0 X̟E"Xw׮4I^<Ny'v}0wNH#4poìjt2#&ݧ&5NluCKzmhd]:^@-VUf~8lCQM.@|/NN?QMZ=g# 2Ifݕ&֖9WKJ9aʱփ{Dq$ torrsm.3~4dc.%8~N0{{NFy&G$@$@$@$@$@5]c%fB+Cla^]\y^ hEb8$ni9ȳ:8f]ps $n?6]0M]9z^+EC& ᨜xVEy+%bu%(^BB^r_)#A?NL|j.Z{s\p%S!1HLi' Z0dsS &Bt[ -WmKlL"sd/0JZ8]:XWaܗ7[9]<&W(R;C~aN<Ǵg1s=jWjLcﳹL_>1#46SPWHo ڦ9´nJjߠ^ _6'ύR'h( .+6Z_M: SX(揕4hcڨVYY|f$S]dzsè=-jBxup<ƙҮkH 96"c+u+ǡavZɖ6CGo?"m۶(2bIobLZ^uY[QsAj/=Vo/T#.Z㲶E0&4~tt4Y NRS"#47ټFryi,y?|gLW=(Vʷ iɿ$@$@$@$@$@$@ XΟr.JABѵoyxΎSc*+ffog +φ^.zP C8(u3h [(Be5ie F wcyĮZڲ,,^ meT[H.&|aXV#ܨT+HKW%cX,eLш瓵y%c-1谲K\k" C,9 k->ظ:;&fQqvȂm=wK \CKqRGp"ƶY^, n6 #*1wD}/{=Ȩ=>$qvXm[X"Ǫ@'Xj}$fZX==$%wbUZxQ3oҷm/e8NCk#Vo{Rr mںF~YĘzb|cOE-ځ͛|[7{kQ=7N-1ϐ dOd#)i3 '77juA48ScؑckYa2pdMJyiO+q2k`'V/5nI⌱Y*՟vJ=7h?1Өlh!?*LJ"N1[(?{Kc^hjm6X!MS9pT'?*>{zжkѓGpߋfmhKa ch~eL(61rTxTb=Aş#\f9@Uh3I;l/~@F{8>/k%wNDi׹A:ȯvN--7 ^D8[mP8|`dgӪ;@OfQ3cb2Z\+7׍ 9Un4=>$ *jEwn*X3h[3Päѿ5'85v&̷<5Y8;zN>oIYhĝ݉C1f$-,TIn]۠`Į[ ]ih%FH;/Q4GYuI c=F .9p0+vZ}ke~ݏ f}`=3:P'L\.+ #{{-nv gDjy{h2V F2f#vyhg*Jݴ ֕(lϷԩX|qZ [8ܞ{>yeX,^zlrq4eߤ_?Sz 3H;}Z Ne+b[%bldSChMZ}׈]O6DZ4k3,Sutl}k4Y5V{Ŝ39k)[0+]@ou5سņ_ JSE4Q]-j :)1ʋShVv|փ%74OQmoDY:|jb;!I;٭y!,1k2(%Le-ӹјJsyK }R絚&ryt}y)>4SD(yiP(O*5Ε󭥓zθ?mo0?亿Kxb /KմP 81Y%>&d$ŴJ]YhڭW ULV3iVɄK$S¦YBy5n &s+Pi1TY׳Gl7K6Ŏd܉W3I(ĭ@o*4moKMSs +'m0k}Fmf5h6s4e׌&Dl{Ji-+v\(d){NqGCNx߹b[nyW4 hr5Uh#]$% wL{ ZfYW+ixD(n* ,ٸ8fv^3/ `{KqJYY)9{+ϮV-ٶSN3߭Ak\5%sEcPJMfuE֣\&!M[L;< .^.S3{6.Bs*.` <3M'&2ЛP: h`GFW "PSbҗ̄^kכƖ wq]ADܐȘySb|8&.O1J !Rs bGZ3$+5w罝2&2sJhk 3X+)ΙN.PJ{1A:p  otEC}aUkX܅3]k(X:'%':,vZ3;X$k\-Xl;1`dm[,'CVc2l؍b^7f1nX:އwXnwcO'Ku+qI{n͜lY9wlqtۋrKkVm^c$"78kCرV'HS0:&Xv[q20=IBy-iT.nFpoA:Lw9'}Kn%}w&XN$*"03Gf=' h k֪"qٞ[_~6ʢ"OB%~=r.~]g5K]`f/ҏFN4m@O/g4ճE6oC̞7Y(MGd;s:uV4{VzrN9;ZG *MY;=X;W/}SUeh^~hlCU(3 [yg@[s;́>I͍vWjɂܶGajqCRVqI %3yTÙ;:~^~ZF{yqP^˜Xdί~Jrev{:ȰL&%b1s0b$     I`Ry+%( ֺooFYN̷ckE֟G "LUl66:'T/cKM (vmӏMSv5<_||>RQ.Η;mLd~J_IJdAS[SdO1s?tSm3ڻ z(+A1zםzIw}ɂbEHߥwZx1Hn|:cEMRAn2l> ݛUKY 72]k8hEjZPig*͛|o7"Xpuw&bISt^ <`pRj8}h"0Jgȿm}έh捊Łhځi%Bq[yh~mKɚ״R3Ε,Ǟ32*AK;eo'z׽Qp(2sM^~Ѝ_ǔCݎ3NjtC LF" FZQ}@D u) l0UלԳۢ,XQ1ÞѢYmrti孹7B:g֣>bUfUE?XL^lUX,[bU1u޲w_w7:&J* R[t{srC=U*++_ f q۶`id?ZzN֡ݦtMKKg2Wu&kCUPj j,fnMqO <-qx/XLUR# ϏOFX.}=h[jѺmGfZfn3Vğqtghjy7D}=b1fhnd<.h3{l6C[WUrȭ-܌QEj J'|Ui[+ lQuS+Ia=gΰщ)N<<,UOW}VѶQ_=&ϹXJ1,t89'{SSTAxZgZ@u,o/ `X7eok({U kΎCP, /֥dL-՗1lǞ2J-w&:Jލ@Ԝ LND~]/u9Ąx_@4/]DHg\Ӫ; = WR5\01=ԉC R\\~qdW0I@/8.4*qnfY<7.P3'm/?ۢ `>Mբ4MD0j4|)< vD*Ls3Oxjŗ(fE[ _+U冦aR#j6WƄ0&YE اh\ٍ߅h=[zExنq[W֡B6?ٟAlʸNzߟ"6XgòPNg B-1?:6t=݅Ֆ2>1U=avD>p#=$q~\ˠmF,qѼlqJ'v▧CڽJݑPGЄڛr*1[mq[ :hon^>)؏e 2돳q]/KܶU-[T]wF5h{1ZtwD89- 9[~t\c-j>ƞ32t]gO鿭{_Xc+$@$@$@$@$@$@*3<-k}}p?,Z9Ff^L|>">KJ|;)bށ!{‹).P2_wC_cr[n탊VgO MÃ-"f2/~Z2dE\&Bj8u_4 dϘehD<[jM{V-? VUD z#&4ݭtn2q"Zp̈z]^(Ó@K,aRpj2?B_& V,kze1bd!#vCG;qWLڭ?w%vǴm?:χj`0=Gim'z6ykJe r4|Ukcw>Pξ5"B4>MjOz,]\ w);D(,xs bqku|*="4ЦOFU孏TTNf̋3a&FɭuD* P^bcA[S͑26epysJIK,qL=mqN>4*FRA#sQǽDr{M7J/+{ѶM=+LƚGн^L:omS ,fGͬ-__5Ll"eQVE~U bf6bH[~6 T)30d*є߼ TZ0hS+-K{>>S]([W{U߃>YlB7jtdK-P,_g!Gr@,XXhU:^,,\}XyĪS* ny/iMǒaglVP3մ+^5/:dZ{_ ](7U^]i*\3KQj27HX )*Y6R;֞N{_<7,&34/8VV}0v|M(js"8*pK7a,mk޷ 2/(;-Ihi;݄GhJ;zp#bzGj.z+mf,[> G?k gĊƍrm#>n ڕko3f|_1sbEY|Llg~ے1(}okD<"     Ȟ@E"ǚw`Vm/ ւIir|jAvmմb_h,5$޿3:_~[ڌZﵢ)*Uj`` ro|͏'sr'ZnڒK31͞]` qwh*l&Em,F΅d3O0ϏiTJ&Ӧ#ñEG"mxE3"M{Kyoܥ E=s x9 {z Dٻ8h@[;v$׏g.T?rE y8QZܝdq܆̩4?tF. }tme))(S!l]H}ضغ@,w\bҷ?Ǯu*mI Yq)j3Ԝؗtw։; w-R|ϲr[G4"2FE?hjN#ᐅP"4d(&F>=)v#>}l$FI:g(o^ݫ3噄ﭭ<3łLV{[࿡n^ r~NInT4dqP=Vz*hM!w`Ǟ-h|X-*ץ󨸣oY~OЏqZb/FE%ɚ_rD۷ayFE 8G8]Rk& z:}m@ ;~N-F,D(ݰukS'z7nbm~(,'&Oǐorfbה?3ldܼHmQeAEU,Oj@FS.бЖlU̪UV}&Q(Uꩬr}v=(hۅι R&EIQ,<쉀eLj>`yujt%4Ȃ[1b{Ch4迒b=IoH$?a.;g»؏AT={ea߈6K|֊%^[[XaC;\W+FP4&oÎ"'<,sC)eucL14=x1MF1^ԇpH*YUMv㶘3P(_>1iYbuֿ$@M$.߳oMA1GXW-l_ Tzj7.U|~6 %;)k2q;zPc7*Oy? !,bnXc|2SV`դHHHHH& xZt^GqSLUk{|D[> 61ZRL3v`s=@A#ڎU- or\ߥ\ܻCIFY޳kz>;1S"xT e= MXS_A̧Cbvw~=> 0tX{ KѾ!P@KDeFK&3b~]#?[̄mY mo7Y޺ѥC&>PIm̅TFcd5fV<6^;|8Pސ` 7gkNXIla Bt@; ܳQ,pPfpr-iZq&O}SB8sL2rc%     oD0]68\`i`tf YӞgL.FX~|F~Yܚ*kE՜_ds$ޘ/wVRjdF lY 1Y75;Ъ&C/F0Է9*RB4ZY][uZ r}ԙΥ?(E3p뭰z6JrIcAizV]ݲrtgChAXO[ ZMв>~#IYGGFur,S`;shbvČtC.0ggFI1{#,풅.{P;&jn E09Y.śȂIhʘm2+ vKqm cI U\c*e/>2 %5\3+<[@ytjF[x7<]?ieu&/~łBǃB$a8W n9J%ճ+UrBdz{柑t\7\OG$@$@$@$@$@$@ L'ENY#)ءwX#]>yDhILۊiʒ擵<򫹧YLg XM|2%gN,jeID5X f2{`3P.[|nfMQ}{K ؊%F'+Rf$զ# 7P>8[v=[^ Mlքa'b"mC}bR\zskak9ݔ:l]×)TJOT|() D%Ij1ᎌb { 6f/ܵru93~gNs DZ@#%Io?цMºf"divw 嫱shʏ8*f+ߏ= -v>Jb #FvMWIm4fGI VO ϒ4Ÿl=(lf֜å;6]&^#     0%Y||Fdb-ˏH.aTyѺcZ ٭߮Ya)rC%[s=( kf;|'jTSp0,Z0$~J&ߎ:qޜ&\rE+ -dx2cSsA P};E3W[ -s }e/MBrLZz"XrM6L+0˥W|Yct,-VQ<#v)Wzh߹F%7%7*"C"UP9YԳnDTfm?􄫚iQj~g<@gnaεcV&%?ҷWq ˒9fѿY  ]R[la@.nlY +tTQoYOCͧP &bLJ43H^uv/ " D -B-(/L勎}o%_*y\ݳOJ ǡ=1E,l=b];/XJO*Q(ozOC ƵOA[&&ɏK\GO2Cd"Xݓ^EMt(׊H7FƷhPa|ss'm{aL#-g-˅3귵xHHHHHH̟zd =ӌcCğfs_W>n Yhb~jTpFkPsɕUcV<1xE7 ZtռDDf[976}=h,خ,"K=3{Ro+n[E`UnrĽۺP^n cXߒ$^;0Wb ':pcw'E1DY 8VJ~t59o9l%aV=+B / aSw :Ydcy]YpbZ FFeUM|`Tk~f-iAL݃kxaeՠy}o#1h,*m6 Z%GY!nq`ؼkHtYo;iBW&tnǚc,]̮̏5r'M>d;8V*qqg^% thOv'>&?z!ՒG%,- m%VB+}Jz!aWV,,X׆ZWbIEZ?gf9s$>ϓ̙3||q [R`ػ6r>L֋ެ U[G+h$p}ByRm`v}c˥vAԉ=tG@@HV`@'>3_n:qD:'ݍ+exV74HR9VgQ+KkA:|tИc6֜ӱ=7mx71}۲-mq÷at&:e9bo\ey=Y5b/ΧF+en}*+B,zx}3e9XKZ<@.Z2y~9X+aӴڶߴJkKJxIoٝ 0VY-*5oXc XI᭡58hܜ#9㥮%퐋'b?@Շ- ~/9-f cJh֍Whh\$n.`Nsj$/N/孎$rF3Y Zg?I$jeo5;]8b!3u^\5K^\|CK>Xjkm6m} cu9CNۡ~/֖;ྙ2sLbCB=ksک˖OY+cݑ zis[uU*{eR3ڦM"'s|Fmٵo}Q=˺.X-3]ܛk x2@|Q}R`ɳ۶,'){`ɯ'}ȳ+Yג\'z]-ڂ fDȓCK-!,5s @IDAT7 |pIQ3тooMP޻=a*.rKJ7Q_״N WFY#|oM8b h@oɆyl~X'y>`Sʂ((; e֠Q/n^&ϬǥL޺RbT޸۴)MO9C&bjV)852Ңh#5I7T1-:]Zm:\YFwUZNZ AarɝaтSHҒx=`|&2e%hitcSU-(\ZkẐb HEWHU"Mw1W 5xj );^ӔtmRjnxgú¡?VӷK FI׵VkΑO<+گvXҸ7K^oʘj~]ăbiqIڨX+wR@n\ @ǡHݲuEsKeBxqZkZ[tV0kx)^e JӲ`;2fyHPvOO` 5ָX d:~u[1훣%ѯu-ݿI}ak0vziP%ﲶ8pՖuH*p˥8%)BIi2_'?z/ΛX oW۠{GmˋN>ԑ+Oh߇Z@mFT ݇^7 [#nc5Z8#g\yB87~U2XH&ٰ{M!׿R͛5>|l.F;DA|Y9ϗD|{w\6t{&M ޜok-传_BaRE@@HJiZ[@zTɘǮx>rc|TISE1K_gR. 7j-."E*eZ>_צC;re* Jdr"CkrvDXWXѴ-)V̕")ϻNESy[ê'vB["X?V+ZcE.&&o!ݝ|4R/%~Kxp[r]T9N{VL`h!3tSliݬ"i91At"7Q/?kyT+'9h]%u÷xU0e"yS R(БydCz]2>H۸0O=﷜ɺ:?ÆDΙ@u{g-d^!WaNrnu.mRܯ[pH5YZ$GIzl(JˑW%7]Nըe{L&tnIj==9URz'x>4ҹ馸=KecZk4p{Nɔ!r@ E<mv['&In'H朕f22MrH ϖmڔ{xlz.6FoBOzVY_}sp!6oC=ߩ>d˕{/@,[FFbzN*4UG>CHˆ 3a,פkIUѢb=i_h=J¹ -Nhݧ:Ѣrhb)-#h^(ۺ\[7m]"MoKl +A@@Zή  _|jXSkEN7_S,Mvhsվ5?1͕S:dm 4{\j Ofɚ[(ZA!}ڦ5c?u]QZkSU6n:jg˩W~X*˘E=p·|"D9ͷ=j=KַJd~`;D|->LFlAy##$C<)nׄ\цǷF]hez`{aZ2HgfC+V"d =ou:b)x6T\"&k`~`κoDm8^_մs봬~}H==/(7Ȓ"/ۤ1fYݵV\,[s6Yco@/\- %3 c6ΒQ7}O}1:c0hT~3E7F=fJy!KޤޒOL`4ZyIj?\Tq.\TXZ)[wkChSҴ9sR\j>7cyF +%؂H8WzczsB|A:oVb9 nͼ.ʒqN)dHJ9v<#-l| +Ѡ#SJ]GJ7ۜ8?jp{H.*}.Ƶ2E Z%q\krkM%zO#StzuւxjɁ]ڏh; NmN J[dc[NT1_9G^]O ӸN}[ۍ9o(=u"7O{k=.sv-齎DOoq-;崞75Bg7z/שr]# Br[@@@_t?0/؈+)/0%ƺ@@@@@@@ $ݔ}wW       |m@@@@@@@:9+D@@@@@@8 8}l+       @ urV       q 0q궞?Yg@@@@@@r?lp ;y!C2͐NU1d@@@@@@ {@@@@@@@ Д}Z9I @@@@@@@y@@@@@@@Hr       vv!       Vi$1@@@@@@@C@@@@@@@ Ib       =x       @Z̧@@@@@@@ {@@@@@@@ O+'!       ` 0o       i 0VNC@@@@@@@.@`;@@@@@@@*@`>$       ]݃w       U|Z9I @@@@@@@y@@@@@@@Hr       vv!       Vi$1@@@@@@@C@@@@@@@ Ib       =x       @Z̧@@@@@@@ {@@@@@@@ O+'!       ` 0o       i 0VNC@@@@@@@.@`;@@@@@@@*@`>$       ]݃w       U|Z9I @@@@@@@y@@@@@@@Hr       vv!       VAIs-n'8Gdt+`rWb}%n]SWvx>p tXd8D@@@@@@&|yK7ܻ2~bܜ+1Wi2ݞ,fx_<ߖ#79%s       Q )#˪=:ז.eur[3!cu5U<2ɗ̫)w>A@@@@@@@yOG|i l~n);ho];~eI`e+8=#       t_ K( 9(%L q ͐ }rt)^\+& 7ɚW]eA@@@@@@@$P`ϥگRrԖKVӝR ejKM\?Nl+       >A@lV$ '3_֮,U:媔QבC"9125kd 9[ڑZsKZ/GȘqc$H+Jinok's6C .C[u??!KD_O|V;3cr^5ġ-9}BǨ7۔m}@@@@@@@rOu,j}AǞK7_, Y'e_]oM@zVd/3zR$RE&K*(@\ 9E"!ʥxde8RQ_I*fmI-R)+9!uu;n뒢)#      @='- CSkc'3L̗"),(u4?] y!A‚B) e2}|iEyc Jee η -In/y!Ay#֡zQ8u;dr9_<(9$Ay#1aֵ1       טxH*vy+R~)n*)'}ҿX/_c9z>&.+j rjcW׮5j/7 2@Ru3P)}b١Tlnyc6eMdWOH6z(7/:(w3n 5D_9ٟ- ¿}%*@l<֦,̑c[cI6em_!MٷX+j m>wn40+       @zטoP=N d&)\0(o"clT<T6.+V=<'1S-tp+".FF_"p&O t3>/gAi:Qn卥Yzد="f#+H-Mb֛J4tf e,pͣ?oE35fm{жjٲN6I;1a@@@@@@@aZ;-Gku+^|JC6P_f酲Sm0?j}m4G5Σ R*ɵ- 9&7vȳ;:-hFlUVvUٍ_l^ >?]_Pę       @ю@s쩬 2x IqSwҕt?5E dzz'{j 2'RiGvf?񁽧yc#DZ֙Hc :,eR@8WC&KQi_"ŹcRN@@@@@@@%Fc>}Y2!jNLMbaIG8D  2%N/C96il:$g>4T#lu$ߧm$?JYVƸs1       @:ӹ6ZwrORK^k#wM7@-<(Z/zU*O`*En(=0(Sj֛|qg@@@@@@@/zGpӧMK<4 Ƨɝ]$b "uѕywֿڜr,ݢχ:^7vjb W\-+п|1       @;[s!ymbe''#RW;>I>5'w[&c`{vL.,+yӻ#;$֟Nݯ/AZyr}(j@+'@@@@@@@?qL曫g-*mn$?fL֚os[G=q6>!~Q($79Vc'kK7l5&7e !       @O $ɛ[_lon=j\Ͳdz 9Oo{ﰨA&{ed)XO2Yi~M7mּwFjKnͿgѺYtjϖS%ϗX.-ZpdKsyeC`r7Yмy =RhɒqZ*&       @7rO˻6ϖj[qYaX\Z^Zmz6__"akS {k="K'GODk`}㨔XKmtuT뮖:ə]e%xA])1WX![c3祖{4Jj9ERI\"}0q0jH?xL7?D88󥩽Ij+K:kj_™W.G)U_P"/yKPޛ>/U/ ?}]| ~ )oon]v& 4yo|6ٙ       ) $Uc>t-[wcdxU229Ba5C^wryyPךfFY<}m7'41_-2^Ek@@@@@@' 6@@@@@@@H) #       O/B@@@@@@@.@@@@@@@~ŲY       }C|       @? 0OX6 @@@@@@@o@@@@@@@f!       }{        T|?b,@@@@@@@!@`o|@@@@@@@ _,       7\       Se@@@@@@@= @@@@@@@~*@`~l       @ 07r       O/B@@@@@@@.@@@@@@@~ŲY       }C|       @? 0OX6 @@@@@@@o@@@@@@@f!       }{        T|?b,@@@@@@@!0(l 0@,LdvA@@@@@@@_\tI.\ /_Ny{֘7C !(21 "      \Fv#fnS.iԔg@@@@@@@@,Нy<w-@@@@@@@N$}kXWpVUO^{H߿z_9S>su|o8 _('S']#^'Ӳ\<eće+Wʍ }w%ˌ"   УW,0>ϬJ[E ҵx0NF>|g:]&cwΔIQ3]sߒWn"[NUl)9Zy*c#dώ{qĨ 2[@NYKW ϯYWI7Zyq!YZFV i8[<䎐3x7*ee %uy×O?ISm\ȟkđK 0Z9wE~ 9w7o9>-HQT#9jmGȔ&}glC-|>.O; e8RܞG䵯ϖ_F0jew"z`}Jt;1B2FRLZzޗfRX2;*c1Jԏ}}fђW 73stFo;{\e5{>`kj`>6?q@+>uȤqb?^e%~PJګ"'[wt 3    qH1S:> I7gzIC2uuu<$EWj|DoՇV%O']ku`{YsuH͒.sY歖; P_n"FkoyZꃅRښerɸ^: wEeȵ@36lݚM N֫ۓQLݫe ֛ckl9k\N}1h~KNOεˍzm2y|E~ gkq]c+Z)~j;}m #COz/?!צr(S/Ki.*   |\c³oMAkE7# im^ ۊ<݇1v<qtS[Ե7k=D:wQKq8'= 8;OkvF&;SnlEڿ 9g;u[q:>Dv>!AyK1Z`/i{ش7>Ղ@͖z>swJ-_!oSΞdmʟ|uI}r{UEbH#_-M%3>'j cɽgуFt{s օ ďnTTnZҦf9=}p\i\3,Tu<j4藯F&+ hȆ^Z׿.w *y+b)fc3;ȏhU}S1 <=-ΉD ')vKU-`D_QRtF}&iܐbډnWw|Ϧw?eLqn4JzЗRhX%=mWI-@@@@bBtul#9I/i_yy,0zU \#VY|џ9Ƅ¥RoPx;@kp&3ӌFBycvXtУ_{@IDAT/O}w .{P v=Xο>%cՃ!6-1qiݧ44(?{Van=oZFv!|[r0zP޻mSh!Sxo:_ 2?vdzc +п ٽ9jj,.kvs߮M΋дn[7,k|_w-@p?kYexX(s7xDTysɊ-v%wؤrN64}CJ"`%n%#g,ż   I ƣ^$ݳ -=%^NɅ͚!1JscG˘ c孅:DF;CD[N̾GȧÃ-=(?j. -rC$RFt䈜om_ӁCOO~BwѺM6`1݆._^ !fҮƅlrNۜ<~D:(|}^S8)pρ}cq2?t]6N:̯uDsPi>Ĩuwْ5.$$~$sσcFg~tY[[eeEע>&:,ߵ9|D71vZwgx]:ǂ699V2gh0(s%IgGZ |6coPё -2MkYZF~{=X::lcdĞMƅ4=oɩό{4ε]&ɯU^<" |FFP~R~ :PCڴVv5r.j[@W/g{׏ zx[Nw]2j_;a SJcdM Bwh3;@mKރ,g7L]e >=A&yr`FɽI u_}uwN'D]u|P\?MkGiHr>-Ays5 )=.Ʊ4lDɊH< e2c].Y@7Anbh"crZH r =*Sͯ]]v{㜄279}`r2r݉qg'{u12_kLz]F~c|@ͮk12Hy>=X S=gl${Ieh_!}GlKܾZ<[k~\tFx'CkA3?X)]d}5оf{Y>dYo4W~4fpLgKU1]/Y `\8oY?-؈RlI@keӻ<`Y6ڝm=MurN4o#9嶭" oH?Y9 ƨMh~vb7>][l-\2Kk&wwoVWE<.,xE1yoY\j̘c´] sa!N02XcRy'Hn &_>]191fh& +WJNq~BNaȌ i!+%0vҲgL?ES~c)ڇkaKZh²%T,<ˬ\Q \# 50/(˝h`>u)gk^#Z&uL߼'ܥ5bg5ҹ$9]4ZE,`d;eRN}kԬAJCU #Iߜ*ߟL40osYh|c}' zy.9$kqS=W&tpI3 eriu54ާ^1ZF 8 JD{`3Lc[`̅qp_w4g鮎|K/kG Nw~8Ig_^+,V԰z9?\fc^TkwD2 \Q,d޶4ky0k!9/IsOkCSCwjph9/_Y$F,FH1m"o#mam7zis\#9۶kNv^i)7=Svgo'J]Z쮤juw7DO4m[C #_->__*cnRѾ|#e=Ck^84=XHB[<$+J_Xu%k]6IO5Ǫ11E`2^뽘Ays.Toy3?=L˵zmsѾ5_/H 3eܜ!)wd ʨ&Nϱg,tz]u!qȅb`;T,c!HpP|X.l~}:Ckv+6?#6z0Z$Y"6Y`sf2xMu:[ [>!rAyoZ[=[MЖ0nBGNCu (9v|S-<ħz!U_# MA܅rSiuFpoT = LZu |쵶;\n,olI5GL?].RL6([0-ӭ&8n\ !   Чy lMRC:96)9.RԯS+Od8+ʧUFMz=#'6u,DNu\]-(2rzA6N`Hڂle[e\m:yLM >5Oj;̓N?߇{j F qs$_۬AryZ۶LyZ+z-xKYdq0o"ZW!9.4@N˩y*A|1ߺW.qΐkYNj޼u|8bږeQurrģ};Hl1Q27i}M \~g[jG̿b BZCqbߍ&,]pJ# ²;thI57X2\,$o p97wr7BmrhPgfh嘁cڢEM0K?mV=89رX'^SvYñ׌ZXpq>xh+rqq Z. 8W*}a߰As>OBL>6={fYro(2GnmnMywphh3_ >fhߎA9}I:^b7/Ay[{Aq>\ C[ ~K޲cb>m߼O'Xmͧylq[uז?nXݦ&tG9;`)?Y.qcɲq /Pw I@ٰW}}7ʅw^גu9qxmom^ϙ]\ߴASڝM`8k=ݹo I[oSR`p|2¯- \{p,u3΀f+dHǵ9z00̟hݹֺ .ׂH/kKgg끎 wtȧiDt+F^gШAy$ֱ-ɢvi_>3Ia@@@@,:XK5rFμu*9ztMSm/IIuuIg?8K!~vthSޚ;*>GoGkwZmuRGimy=mHq]j_k4h0}pvK , (3Bjռv2/u[ɟ*}.oiڗ9G{x#-9fk NwJ#g3@N7yd`Ho]ye40jo~v 6)4L ^=M86mG fd'3]?,V~C^Rkv۷DV;3c^:6i:I#AfgaTa,5in W'\^zL'4޳Oyʌ5] L<3Ņy}=o)$]P''`hO9ϖ:轋5b)sEz>6/Uh4?6l]rSSƅWY2|yuh/qSLk7=/w f'Zmd ^7a8eJ[+O;z iT3Z j3nX6'#`3mt],7~'K{r:veo27)+5s eWc_Wޣ0%>CM!0C4}-CYc9Gz8۹zN_,.wT6O5tM?7Cݹo#/Zc>8\m|}@^{D›'I C,:bvyŦ>B`v(z.8t -çe32zoX"H**R5tjaiݴI\\6!P}/xD;5mtZ{k{^1km|C68W I'^${ȟu*!#   |L g{6A82Ȯ55@mnEAy;"PX{~{mZ/mҾ׭lIpd=(o4C['p& ^/r%(o$3/&x.^kӋ8vk2ikgoռO5\41{zq?"m)8aU v8䖦 8cy::Unq0(9,3j#$a04P^~rB$yy7io &W ǬIeLet%Ԛb4RìpLQ W=K~]|Yad0+𴐂8glAan- :N2#S\-/X0 Ňˌ~|ǵenK*=?+f{w59h-=~sJ71N"NIkAˍJ5} pȤKa?mkƔ%j[8W占oE RSM]z£-(?yXE223L!;9Jl8r_qaò~d$u ~cLlcO X2,>=#-\jڐ|SqPyQ=tT$J)$D+NAߏ \]Pڂ GgU*DV+P*RI 4.(Ӡ )CHܙ3L&`;wםs35[͗k XK[cek=}p %}O᭲odHP^G9(Xlw\Fm@NHPJɭ }>NM]hT6Q;î-\k^z+:҃ҰsѣAy9wl!L7<8/$8ر^&@ R[ǒ_VsG Խr0$    H]ώex&ݟڣ^6@ZR$޽~sLyܓ:auƇM^`P3=jA#kX~_7ۻ*73|E>QphZ>Jk 4[в{WKIWo(Z@ αo?03#sܚ?;iѼR)7CKa~Tc?([%^ɷng @̷ S-t}?Gf^~`П/Y5zFG)t$e`89?פTŒRΥ9GS:XFXFn mKۑ-impS2X npXi}-- ,Ü'PII7}i~^Fƛ#r\>7NQR;d#aנ%K*%w1?xѠw1 CD?{p0H':͎V'QhPhh]lQ|NQ1wƬ<7VYYZq3j$Q&ÞTps/ͥ6vW~LkrsiGɫ)qH?b6Lm(s_}YG|\w6qg7\cϺZv]yZ* D=V3˞ۭxC~v|2xOaձm9:jYh ͔vKPڸ/(ߩ %]Fkص"}v=xVr\*Kjt;L`48~=d̐쯛G{k{[yrLv&8#uj:e9QnV<    @OŽq@g响?NyE5p}(0x~@ G )z}}dyz 2ɩ~|2tV9:= ;I2$g0CAg։=k-=*tEٶh:u3/˄3^x:DnƲ"rTpcsv̵:ׯ5({ K,x-N Yz5Y9ty-8p@[2:I r잙ykktCF^ercȰ"G#$h-R_hpYKzל˕m2\t{_nA-嘎|ye:jE,olI-|%Xׂ 7܂荵{;ҥSJmБQ*ZZ<,坷;Xjܷz3o߿"oܣC[Jdׁ]   |ybjtn*y^~u|佱Z8~ݢsMR4޲Sڌ/?e!Qy=f/Y?|jEϪs9iڳq}8kXv =D$(c{W%f>+v*n1k\yS?dFxKL,Lièx-x<حc>F=jMm(Lԝ!C_Ρ2SG{@~-Y)e5Ϗ1zD9i 9G$/  LrCR!ygWʍ﯑#`OwZ~M@ɀ4~H#aߧNm$m:,|5}[%iNqm=Wd 7 '}l6֚LxP~R<aw=-'Y?y&e4}c6 +Xz`[ҢkhTc3ftJZIڍQlSR8qiϗ/e_[']whz;zP|]G,XzmPN2N GgZ'ح_m*e4r za=>\V>9OxKܵSŘf9*Z,}RfA)Ke ;w9Z>zJoh/qFrV/Ǜ+g1wꗓۍe3F=o{^1^ݠoi~58AG }@@@/@O?qDd“G9=Z[Gp[2)bOިK0ϒiˤyWKvZ1zuOw['#,;?ح\qH_*=Hot=PVn3/˸ޡYG~7˻|/~4v91#kfpm˱emXe3pXMzQ{'__pC3ae-_#7eH{Iu(xblP^s~yv^C[ldIiQ>L0q>(ܔ?t=k? `ҹo Eb)2N2׹Xpt2"B\!]H'H_+:ɋ)7>FDyX䯋զ<,$תچ*u(d8++ߐgL ]Y/Hぃ日?XM;O~?*I:XgJb2ߵiby6[[QL=)ctaNѣ~G0[@@@ 3N Z[{{<S\"*Sȴ0q4ɉk1n9N1RS 5), !Q/I?:6ɻ6Iћ:Ľ%᣽?@ sJX޲×icR=F _ab қ,]gm\/S:|k9ڬӡ-t5"n 3rݴCnU`ANgخK5oi~όI'K` ֻvԚ =`Yo%EF=Na $=+'K;F֫hyLײkU Gv oW-."! oXzN}eRN2eM·@ɯt~ l0)Wo.4|aO`"Z,]漤ZaD1chOq ]gMDe$u`扭yNE{auT֚D ʗVFm8Nc{, | N\Ǣw=f33%< ^aA2(/#ycm0C2$]Bm~n9ὢ$xFKOyú_x4."yjt۟,-QaC>KaAy#ALb;~㐁Z5NuqO6!f̛LΐZ$V#}^g?' R%MOIea=^z,fױ}/F|$#{Ar\Q4Boo3]#E7FŻOh~C_88(q/WWˑ#21Y;&K:l!|BG,I7"zԍͻ6خ16ha*/;.3[Lu.e#erUh69P&mҼ0Oʨuw;AB<Q=糔~׮YuE\#L5wK3޼uԂI}^{Rw cOt-/$xÝyQuvy 攟!?Y)wO{z4uڐ߹pLx+sF,O-Ŗ›f";Ķ&g&un/ ~g?X͠aTASX^諣J7YEPq \?55˥}1}Cٟ"/~bqp({3swm:}Cλ=iŸZFZ"QLJkCtN~   \a?=eqNw.ɖM C5K:y [;Gr%iA$'zEj;]-C~m9(س+k#὚tx_hOo9^tr6o{gˡfOy]2!~TW/r`V)Zsgb;kgۂg{̥{M; {n푛a\shF^0gVK"aW/YgUzr7,_z5 I ;f˧n:9:]w)kڸ$ !4`__@ʷ.I~ zrC[HxύQE^[tWea('ΐFy-ГU~p5sy; 1pjfٸ)i]1//_|'ڿs̷<.'3K,kٛDuooٯcڠ%XïHWu/nּu݁h}\{Chv ^pGsRs)lyXd3z~ھk߯ o5x'}|nG{6o^;[25yf˵Pۨ m(K3ꞖC>3tkOoamnRg^YZ}䟤;HJ^ J&,#7͓b~Zy齢usw#eҲt׆ϗIPlwḋ,י2V&w[/~*:_*תj ;R.=S|fs׮ fݾ9Xe27RYk}1wL񡶽qwm:} omعI7@@@葀S2v!2ɮWP[4[O Z6mfЦa4/'UKL:^xq_:N{zNaKt6ئohO{{h7kP} k1}E輕&9l|-R8q\Y2on&{**%om@Bu];^S}vA>o3:(t.Z(UP晧ǻ( &ً&JhxI>_fȢ50 =fAR6=#m<vw1p> >,4@\Aqq. }I ?jTƻx-~gn|!=S%k ^*7HC2 9wKO?v8|R&Y< }] C>)ȗ'Q_G->ybrfZf]/ gaf>6l#g÷1sO,{@&3Cw]4?wm]gC4+]W&͖^0q,S{i(+kqDO=SiiPylt h!:#F>S+ gВo ds#JkGxg+-VhL?F߭ZRh WvU]!GH; MFè}+WIU=!KeOS%Y_<K~g{_m5RD>Wt)=1޿S|=+b>f/o\n̠v_܉׉C}:teObK#^+\#ཛྷ\E}Z?AȐiu$$^llAy3ZjIZ=_ң>rzcKb߂q``է/˱ߖ"{ű-Wt.ƓbܟMKz}nn]a,uOM!e>l-{^ή;*&OHe`>"7o#mHQaD뭥Aafh#g1qw?ݧ3ɻvF YR:+G Z3Kݗ8``Y:%Ss~4bۏF\& q~?JF4?dNe~.R-Uligk[;pwf!K*Ko)eⳖmc:@x^/kɔ9ìJJ1H@ce;˹JZY?#m2(`\I:N-'Խ&nq`MF crt0Z{l[y[ջD Whe\Q*&-mt4;#+7ۊ$윷jsB7-R.3 Y{`qelk^XVw}DKel^F 4,JҺhAEeH{{ ;a +L gPbo;M/[eo?;׵"FCGhT7I}kĒҼ1 ?ZCM 3{}2xHJԸF^/_,[~tG=5&%mXR2J4\yE[IWGYYz[.@Gx. Z]ݞ{ŞƬv]lzNXl@@@@ XGaMkƗlf:oy ~$wnd\(B^˱~:k 0ed䍙-o gHvM?]ȃ*14[jyL= f2#Ww{9":ɝN4[.DžS\ n }̊;leƔd/Z Eoi @?.|['.:u"߰!h#)wHhZG:!u k Sk߿d=\?7 ?}F:/uOʍ?2:@IDATtu2|hpDs%8GoiC_ &(oI)Qvvc]-LR׈u0xln>{eF&5,W.p"˸dV ko,M+u=2_YIד1ҍRn)UcL^'#_3/ߕi1z~~GFl_α1J~W71(oQ/_hhJwmjR Si,3jui|KAy!e՚:ϱ=_F ^lxf2L&4kH1"~_ 1IޕG3˕!5Mtݷ&TtO%{[ߒّ<]]1>YvG?>Ck7u-,qFVo{oL׷dD4_c+#aCʌdp o{Ds״+wmsYFz-(x]Z<"   @oXz9ڛwoO&]{p};s՞m92KE##yyCc)J;3us%7+Ru>(7{pk]!Hsdj;_}ǔ*:7m^hp#6Fn52AZ'j?(Fʠ|H>XtW'9:qeCe8Rt1]/ݐy# {Qy[{{|k.}9"1ug뀁97xĥ | ]ϟi.Yz=n&q1>sQ?"F988/V;^dwʐTl>ni7ᆳ^Ý=ζ/N{F gJ%{L;emskd\ߴ׸Щ]D-+4Y}k"A׷Ϥ׽->ש$pMS뽼VILH'-תJJ,w3[k^O+ٞu;6Xo量.ؕE][:W;@@@K\#|\Dщd#XXZt\}yC;6JSZIlIjpnQz[Jx0*L HB i9yJbJGE[z #/AOzI Ҡ%U!*O K@}K{c -4D*؞k}zqčxK<;ꓦ{*GR9߷Rh~&{LZ0MM˵*޿Ҙ_Y}} k.ESj   % ' -*6l_(   Tm4?G@@@ 7=+kD>v̙)>,3na2tM; c@.ȉIۣeWbdߎ6У`g@@ \rUUVZ{ϐkKU%h   @񕲿nu{rbͷdbpX@ye.82ɝ#";]E >c @@@7**=2ib~oB   \6r;VmssscgNoL iQ}\~rV        滺 G       E'NKQ"       z;KOp       _#Vn̍yDv4 x<$%        Xe@@@@@@@ 0$@@@@@@@HF|2ZE@@@@@@@ II@@@@@@@d'EZ@@@@@@@ 0$@@@@@@@HF|2ZE@@@@@@@ II@@@@@@@d'EZ@@@@@@@ 0$@@@@@@@HF|2ZE@@@@@@@ II@@@@@@@d'EZ@@@@@@@ 0$@@@@@@@HF|2ZE@@@@@@@ II@@@@@@@d'EZ@@@@@@@ 0$@@@@@@@HF|2ZE@@@@@@@ II@@@@@@@d'EZ@@@@@@@ 0$@@@@@@@HF|2ZE@@@@@@@ II@@@@@@@d'EZ@@@@@@@=K\.w4 &ִq= M2@@@@@@@K^ v`KZ(7){O.~$Ky"i腀?]ʿ1> {FrG       @IZ}5]U['Ƶݍ{dw{{A˚$}ry9%*v49!      L v`~(X_lC%W{w*&s:RBn֌Gs@:2%@@@@@@@|*?ٽ5= yJwM[ʁ_z\-g2B@@@@@@@ !yGDYjsuLw^~fސPeH       M >2 7.1xDJM3K>cr:>!W -F#Vxe:       @߁\F}se۾?| Egd/|*>ddu!;O+dZy GRYT/2wC;=Y;orwPG;GU6Kf6Y+67[]]V-aj%{{Lyge·Z2qd dd[cuj͒¨g-uVɜ"g 5~:[Uu6#      $#w({of7j_u+o݇垑U|yLV%Y5JjbyXWmqTCJU-w˶ WHZmH] QJAy=rKNe7]MFo^ko+u")J"9ڃF-u&Kg{-}f@@@@@@@_ $׾{V ,k;;|枟%ilv:]}}Gȁ⟟KҬC775?C߇.Ry]J,.Wl׺Ի\.YZ~l<I/Taxw\ߨF:_Ґll}@fN^ff!Z/ͧ\RW׏eV+ki  K@@@@@@@ q±̴7:!5XXTXRo9g{L9|0#[%ml7W5|tp.Y:{p#'>Qb4g0J:?{92*3@ְA{2 q}b}—FJ4P_h++       )$&LW䟌ʞ.lσ.(]%wM^$5:b"aY)1,_%uh;?Ǻ\|7~U.9o{Zy\S>%p/++o%C.rt5GV egn{ר")RsǼ[ҧwꀆHݾU:z?UzR^^z%SGZd@@@@@@@ YK9]ʴ5 ^á>%uvg0WKV3(_!/[EJgdKzPO1~/Og1l6iuQ^m]**@U5g!7TPɟȥ8R\V7U_Yyi,z7>=SS#V@@@@@@@JzKa_|޿{3eTxO?#( 'Ç++fP^+#u/%6ߵƭ~wПX&&p: {e;vJ!cXz% [?2b@@@@@@@@JyY%}yl4]'޻>`|3<qȩ{,G]*978lZz|5Ruko)M`Gl^+KY*~cIy%{(/8+@Ƭ        Z`^RkKTn+m6C˾/Qخ77J:S8v[VX׶OE/) H^W⏼xj-K' ,9C7R7< Vm[ɮ#Zr#d#sǻt(V9`6Dok<9~Xv%{_Eɗ       @?H10/2zkLh%1kpU7:Tgnqjnmϑr ʉqHwVsYrv}$u[JgI@7 UkZVqWٸPf=a+\X6^ 4GX/\=oʪ[YakdQ|JkdERz?ؠZNj34j3|O#0`k|68uKCy+K7`Q:.\ i,yo$Y_uʽAii4pVߚ\%Yg s2_*'       @T{̋%@L rUa} */I)1_+"sSPlܜĥ_eV=Cy`kݫ| IO Kez9uKWjw׏zE VzXu؀       r.}.@;]84جúaxA]O{h40 ~mLp(#Gr!       3"      dT|F) @@@@@@@ ogE@@@@@@ȨrS       @ 08Nj       Q0@@@@@@@&@`q@@@@@@@ 3Ma       M|;/       @Fg@@@@@@@v9^@@@@@@@ (7!      7s        0Qn C@@@@@@ox@@@@@@@2*@`>       3"      dT|F) @@@@@@@ ogE@@@@@@ȨrS       @geeIvv 0 A@@@@@@@K!%ݝ1osrrʧL̎       с݈T{=Y@@@@@@@= 3|}~kq       @Obq#       @ScO@@@@@@@ KD@@@@@@@R 0{"       W|\"        @@@@@@@@@@@@@@̧nǞ        0       .@`>u;D@@@@@@@ D$@@@@@@@@ u۱'       qM†8#CR t_<+AF@@@@@@@@609.d}v*|]yLgW?dFsH^/_:#d}Q90۝Ҿ2 m}($3~4ISbHy!9tˊȡʄ&zPVn,gĢ[&? f)3YxAμיz. zno?(I?mt`       U?SCD8 ,}C-Es:fx0 ,q AQ{VbU)39$DK2dNbP FeCƒ![?ͮ-ߜ=2$abfv `Y:~@>_ia 92J&&̌G@@@@@@Wf4}wҶ%Xd3/ȤRW ] ;*/Ńe[W}QJCŰm64ٺ+C׏1LeȻzii<       ]Ono=.݁z̖$] \[3,[i,/fN;j_q;p%awq $n0'(rB NѰ>.9f9ˤxۇd        yFP=ܧg^Y1QSQ$C'7i7}vc1ŭ\g^k[%6P"WTJ[<(E\2kb[gz٣,r#Mг2Y6̶%fUm)y      M"ck:rur, #hw|d\S8$|QvϹ3IIi?7c%mO12LuH/kkQ_'7O)l~ٕe($"$/z|2CȒIpGu fc[rHn1X@@@@@@@X ?c],lpq):Mi\Xʤ2:ɡW_H晵)S2EOn=j 4Uvȭã|x>XnƤ>/Ce "-8ʻZfC        D e&g%+!nV040 ~^l۽=-#U7yWm-ߺ9ZF oO1%\XȤ#J?]»d_1T<9ɽ?z`=|m?vٖ>۵!PG;FFݵ"zG/       @1x`I69pPa^Xy(s{AR52`H+ZӞ,γwsY#E7sm󍛥!NF=>H0I^ILx1et޽vx樂o_ #|8KQsX7ٹrbI|R^ZB/mg'4~2IZ@@@@@@rg?=<^p\[1~obJ~V<Βd)i^%c7/mqypx*4džDaero;?SjCm))/g$zrdZdJ۰ ǥپO>MI֛       qxI{g 3g&/-= 9'T%.oHwo} z"4YsV:)lIpPN}Ci-dl[Ro!t nO3@A       p do&ݟZB{v{T]6})]!yU$HN(F_#Gn:ycuZY`[Vkgec->Cy~^BYd/Er͜y2: 2G9       '̟O6v3Ad_PLF&}"@ TwLgY 4?ҫ'Ƒ]IwɾkdRwCzg[,c#c߫̋X)[L {3ҹ7%k f@@@@@@@S-{"Mzity^/)PN>K*f x-rʝo/2WxtZh : @@@@@@. u$<"'/YIiپJ\+u8{]:>*kޒI.Ŏ#@}{ q&j/ƺIWcU2n\Qb5xRytm$-cLJ2-K|o-/>Yst#Ad)zs\[&}84      }/A),GT#'W #P^.J=KҝlKɛ:D}G~ⱦ[FnaoRto"Z52|#       P̵ҿYhJw݇妾2Dtu O7]+m&Bٵ/Ȕ %r}T>#a}\G@@@@@@KV 3=CZ`gyxÌ!êoxb^==%L-2Y|/svruzL={Ӓ7QE՚*)m/@@@@@@@߇]`=@M_'gȳߑ+-!ґi,ANn%J):Iƕ&iYdF*A=,aB㒰J@@@@@@.gXs^/,2 |h}T͹[Z4[.\X&=T%}D).?I 9|@5Eұl%/3s\<0Sqծ-c-Gf6ςVCfKqI       @ wۤ<;j ˹rAr3/ȾWuΚW\]X>ٻ(-@:F#w08$EID45nO!V0O$`(6)xOR )S -/}~:s3s \^s]~֠/ĖA{.&>fז/-1t?]] @ @ @ 3jS!:0}Mf">~Z|gx̽87.*_;3~/|G_3)zձy~L<3 @ @ @C8#Ou\WM>YJIs=k6ƁpK/ɅYGUJ/8Θ7}4.qa+ @ @ @ p :]Le[㯭J8.L\IRLx@ @ @ @L8m#KQ4]tsbrӬh0}),pkGZN bҴ ;z  @ @ @Z᎘?#Jk @ @ @ @v&OTߢ @ @ @ @` H̏g @ @ @ 0$GU @ @ @y#1 @ @ @ t @ @ @F{fzL @ @ @#H@b~=,]%@ @ @ @' 1? @ @ @ @AKW  @ @ @ @` H̏g @ @ @ 0$GU @ @ @y#1 @ @ @ t @ @ @F{fzL @ @ @#H@b~=,]%@ @ @ @'0hbرc# @ @ @@7;41ȑUU @ @ @G@IDAT @'Fr&?7yzL @ @ @'W͙gs `L&3P1 @ @ @ @@#Kb @ @ @ @5 H& @ @ @" 1_X @ @ @ P|`  @ @ @ @@-h%@ @ @ @5 H& @ @ @" 1_X @ @ @ P|`  @ @ @ @@-h%@ @ @ @5 H& @ @ @" 1_X @ @ @ P|`  @ @ @ @@-h%@ @ @ @5 H& @ @ @" 1_X @ @ @ P|`  @ @ @ @@-h%@ @ @ @5 H& @ @ @" 1_X @ @ @ P|`  @ @ @ @@-h%@ @ @ @5 H& @ @ @" 1_X @ @ @ P|`  @ @ @ @@-h%@ @ @ @5 1,6ݴ:6|jAԗUS~t}{lz`AXWgǺwƏf5tt)1{k @ @ @T3ji亻z.e䮧cU:~ziFd~^xZg>YjdM8/M3@k;G3ѵc{=LNK7\sf2}ʥÞGܵTWKYZ4LԍNO3ψ *4n\tT`aOWw\iXĸ꣮ddUJIt{1eZiL~mdcO;cg2ufz e}+bػ=nUvƒߩybљjz ǔK,ԟֱN7]'?zyi).Cz^}z^=ߧa'@ @ @ @(IVTJN]fukqc5*%ccgJt63F;REsYtqRlmAcۿH,ݿem{N{5әseo6=wk+J,U]K?QŚi,%-|WOMk+?qKcsO;}Pl-bU#Uձ cZKX֘㿈W5'Ģ1ls|=7h}eęDz>T?paZ-fQ^jmi{=<\K79TݛrY4 o獼y @ @ @NS}9| 4ۚ4/_̟hn]KO-lG.pJxIʷE/%Zk)ʖ~IAGjon|k˂bd7ӽ,aoʀ1@K4Ig￴?8^V?6_9,)ߔYPfّ>pOGؽ/uZcť?y]#q˓Ok>Zfx-eIw2G'^ 9%团=+ez[nW @ @ @8'K&Fi7!?T|cN> P޺+]ݔXT(B\j߭G5~!^_]wkPccJ;MMu[gtvfJFH롗֞v=&+^ٶ%Vߑ( y1<5S+3^i'r>Q]=_Rl:)䋯[[|LoݫήؔXvƆ AnK)־IS,_ jI|Lߣ;c.yStJg/{r<;xᙕݞNdlnbsյ);7{, n}Җ](a @ @ @'PSbzz,>J-zpK}A4F+nZ[$W\cW~'$.Hkgso߲6^ĺiN~`F,ԚXsSԥu{kHnbEiMvҵtt}|S2zMذmS,z׌SWbmtֶX5ߡƒĺҴ.fԧYҲ%,A?+m?X]^hpH;yǶƒMQ_\pK<6w}/?8XOz~~gQlx4ɾsek,Ko]kb9o.!ÉyXتXXċ?  @ @ @N@M{Y[3#?n|Q5dX񞧢$aٖϕ\L{4&Kkw~/}:yzgղgbFzRo5ܴ(Ӛ)'_ybUD5>-mc1cj|o/W?^}Mo;JGևt~*-o0@+TXwۢ%$ͼULLƂݗDUv(}PĢܒ|Jļ\'-4f @ @ @3B|=p^LBe_=qGݷǞd[r-,ES%3Ȯe"K?2\мMz/;ܳ/4^ Q=S]Syo.]clܒO qOß>:(yý||0A c fĒ?MӹPeh%Z4]VX !MaNÒuˉzϟ YnZ>3ȿ  @ @ @'^` h૱gjZc[KF_tlߑY=;s-LAҩ'Ѹ %ӷvM#Ѧ lFLwwt{v;wƋ[IA@@.ru\<]C.hSrfąPqSL(> U ~4l{h{⽻EC#߁\ҳc]ӎ(.F;mSSlM bƴE}NМF? @ @ @g@Mq5?AL)UEfL88]?͵iq4ܰ~tV;6Kۯߝ_XRI=Nװ]|ik~zO>`rnp_ٮO|X>ٿ=zb}_26nj qS#KN[ @ @ @<[~r,>NJ* uOi>}ohWbCӔψ]㾻8~oyNmn(ϤoS<+b}scUjz=-]_k۳nMz͜h|n%`c,6SΫ @ @ @j'Ӵj]KΟ:u3ٱpoe.W-.nue\JR'l]4] " ݯaۚ*3-=2V+WEkW[ Yn Ew[oo^t|}M}<=V @ @ @ 8Z[߷:"e?Od?-$bIJkf ŏ+$YbFK.1=%n[ZwoYUH7ݱ&{AWQ̼t%WiÆ nkK?\u=Wf/w? L<ڟXKֶ/ͱ8,QteOOםj @ @ @ho细R"ҘWMwĖ+}[r /|OUyȏ_zR>Dǐ;=P@=w4U>(S1ձ(Gg+st?h9@R>սus՟{ҩ~+9 @ @ @Nue^tOP@›}o4 ^ycrZ}=/1־fZȟG]cTk\.d~!>w_~ƀUP<ŗ57ƃ>pͱ MsGẄo#6nҭ‡LOU*9O{oQ @ @ @~XJx7,hha)"X|"V_X}MŎoFUiC _禮-UGbmcc{`+Gl$^h{黚c폫|>JX yS٭Xp42ű4%~2Cmĝ7Xx:mqtW @ @ @.0|mϴjߘ5k7l`wtgߒm |̻PN/ݰ<%w2dkX[x~h{ߴ>uO)[yܸcwwO DEs}S.lv{\=kxgoQi~v=tdڸqV6{oݙ>/r瞾w/D*#}ޕhD&=lZ.j淭UtFhԦX0@(}r#Bgz7ϥ;xc,X`'3@K'҉y_X%xTD^} @ @ @ .0gIl}fJp\h+u=)]?䦴yǦ*ݴ2nLXV'c=E7~aqlBZeYlhr9{zsc3+=5'}pOSl?L^wO]b+) ^/<soZs9sM-O*~+|o |]S|>Y=_g} y;[ߩچt:s"qSOWܷG @ @ @iڈ\7~gaJouip*w-ۺX/|me=XVjh^xCߔ'%-x[̨0X091^y:rskjkSjGM-KbͳigE5}/ŹϯioW~>U bErzxQW!1X.]А{[i|mS3o\q}ZwGijscMz?W߽X~Ud{kaq%q{OF>(֍_8*S gK;nBөҦ[i  @ @ @85j*M}_Ŕߚ1Аט\]8.H5lӺєlHI!Wbv}{pvsݸ U2eqNmeDwߓ$55wԡHAީ!M@ο.=|F|OZ @ @ @@`*ʥ4}(!@ @ @ @g@MSٟ7o @ @ @ @L?> @ @ @ @Y#pF$K @ @ @ @(p$ϏQ= @ @ @ @$]]]z$Zsza @ @ @ pRNoJ~RaUN @ @ @gT @ @ @8{$g @ @ @ @ ?. @ @ @ @+ 1>[wF @ @ @gt @ @ @^ٺ3 @ @ @8$π  @ @ @ p H̟֝ @ @ @ @ 1<] @ @ @ @W@b} @ @ @3! @ @ @gug @ @ @ pH̟A @ @ @ @?{;#@ @ @ @3@@b x@ @ @ @gl @ @ @gC @ @ @8{$g @ @ @ @ ?. @ @ @ @+ 1>[wF @ @ @gaԨQ1v=zP @ @ @ @Bرcqȑ8~g٤q$M  @ @ @Tl<;6hHy @ @ @ux#Au~; @ @ @dH|1?v% t|\~nq>8ԑ=3gјt?v񝷯' O(L{W1+xB~m'G;xt[g*:'&.9k OV)8zl}ty/Hcsߋy#xvqGok8>6 @ @ @ 0|S4EvmΊGzٓ^xh僕*n_wOR>[9x]d6?D|(OKo^\듸=ToR>pjgFßcGNʓ\xU3 @ @ @_S[sqUѸ+:wkZdy ޾}@6[֪wⷒogĆb]zͮxp+⭍3KQ޵ rKŻ֓-&^jp׊8^X~v_g/ƕ (K |O>-Va{8+x 3FYG+[ Xj6~Pt1ys=ehYoyŤ|~3]swQ>?[wRL|W*K{t[Wbrޣc|T7g?0[I1y~z#@ @ @ @L$ Yv$Sr4<4Ҹw;'._1̎N# {Fo4vŖvsDc1yI#,D]ؙMKV޺jRġ}<6J9ńe y-o]+'V>ToطX8w&<_8p.x'K?obx[OW7H}qbCg~IHl5%q֚qOљ?O>(s5NB?p켷ڇZ>]m/IzWĿt<Z)|7{ύϬ8EL.'?3+[Vro  @ @ @@I*{Ho(~R޻B u&Ņ% )IZo.xqgS%||=$?">EMN}kcޏǗ~8^177{VJ#sỸ{?QLj_4Og8lhw3c|*K?WJtn٤|SHQmy6Λ.;GcGgv"h(D׸|eK\sLt--z{o6'58Py)~~}řq_d"'NkɫHN0tso-& I3@ijw.O'oh7ū/=?OIhGv( -v-%KFWJk;'jV+:G @ @ @41?pW_=ɯ^O|2MhTsDlKj|]+ u˾X%kTK)񭔘?s9r>Dt>;$㪸=Z)ή^?ۯҔw璺ωv'矌;Ŵ*S7oWPOS:9M”Z;p*P{0vN156Lxa/7.>s0=ޑuO}.榌npY?$f xo) s-,G-CxƮ gm;76d /Z.»Si @ @ @Q']G*4zz%Į&w{&6L/MnZ<>eYoƱO?X:xˎϔ'U49-!?6>-PH..Ɨā_HԾoK ~鉒|M-'ΒJQ}1[Hj"~kb3Y<ݻi!w԰od-_-1n/ƞWar5b$)`Tü|mC8{ @ @ @x=8\r`h9ȁ|jgKϗ@S!}~ Ӎl=0=7DmI>1iz}ہ ##E> ?8IN7WYOY]:oZq{4VbTCKYB}VwߜfT{xX]! @ @ @8IC$$jB4e|09b͗ @Am};^*TV|6  ;4PX.97U+}څwb]ǗϏ _U>[?4^Mts̯=v8b kpD @ @ @I>I ڱesblgb/Ʈ3}?($;ZqA~Fuw $󇙨>]2D{Y?xJI\2I=h{wrlK>bxN_> @ @ @ pFDbndWJ9#姱9wO`桯gɖ-y1x.51!GQyA4\lu %f{/Yw}8p|oJ?"8Zz02Ϩ NcwE @ @ @d |B}]RH}`N\r{}/wow>@l޺_^$^)p&N=m_vD'!US?g{MQ?ŠQ#_=1hBxusqxYX {'y _i9C`T{9#@ @ @ @t :!?z4~?+:)-%\k{1~Mcg\RLo}.qhPߤ|S𛦐/$ͻNA3+JFo\h <󷋧6>/ueEo-vyc*'~#bmfot YW{Ie @ @ @FHםcsVfcqqO>x}Ċt3d}xxiSFx$ѵ.ys+]8=.+|bce}]u_٩AO.Kwҙ fsuoCK voɈ[>'~b?wC{ @ @ @$$ړ/|EaɗCM1eyBޝ#7DOߙ{ON/ߧ@u?|.~5SqIC%?~C|d=/ @ @ @#K M=l4xӋMǾkeiLJ6L|*ѻEϊwšޘqYѹ4^zJw6>?_qdϮ881'f+gVyǛZ,L-uu<4'1$;K@oϵD4uύw|/~_t_C|'MAJ1QJwqHPoص>GE1~ܫ'3'9W7ol=-qo2}2O'hV?_}Wk1.gb)`ۣaeј2.Q%ŏS%nz[󣘳3QZo(7=ŘyiLnPxL^ܷXxV_ٸbv&{GoZ\axeްxy1y1 %%4ܣƔC]W|Go,M}vҳX9qz9\Jj:'ZүkCy%v  @ @ @8uaGֵ:{_ibrQx=3Ǧsj7{uG;'c)[TIgń.~<~B N"9ǒs-_1~›cʢR;OncNjb2ʖ]~?'?z̸4qnPC @ @ @8thbqx#G8 @ @ @8#l<;6f( d2!@ @ @ @J1_k @ @ @Q@bF0 @ @ @ @Z @ @ @ @$kN @ @ @jEK, @ @ @Q@bF0 @ @ @ @Z @ @ @ @$kN @ @ @jEK, @ @ @Q@bF0 @ @ @ @Z @ @ @ @$kN @ @ @jEK, @ @ @Q@bF0 @ @ @ @Z @ @ @ @$kN @ @ @jEK, @ @ @Q@bF0 @ @ @ @Z @ @ @ @$kN @ @ @jEK, @ @ @Q@bF0 @ @ @ @Z @ @ @ @$kN @ @ @jEK, @ @ @Q`LeᙃݝWq8uꣾ>Nшz\?QW\NS =b:3:3aJ4xE{ @ @ @HfvH^<6ndK]71ujtu׿H}am墳ĊO7ŀEd:be-1ղ{ĭV߳ŽggW]4pwL @ @ @\3;6ƍScՒYXuhnlGt|'?<.R=)֧cGZ⢆?@FŔ)s)i @ @ @>b~Ƹ7N|k4_63pcCcզްώ'F[sCI9CU|BWƭ[̜:%Fċo[3 >bU-'*^hN ]9;ov򕙜%@ @ @ @X7u74F[.tǚxS LOGmr.:eDST{vk5mղݱqIvԼmS#;!41}i  @ @ @ @l]_HMcIl M׷Ǧ㨳XW{w_͹e_{||6>ZZ+[r)!@ @ @ @*07B'ݽpHӕ7ݰ,tψlf?WGGBw2}0byiAzݽ{{t'jƤ)]Pmyo8㢾>#r~OHu(}ġ4TȒM1ۣgSti_޻'2p6ϡwO"][ɨ;SsW{Bm:oʔogDÐASݱ}[gܕ#ۧŴgƌPx?z}P @ @ @N )lWR"zwK׬y Cޘqc5*%ccg6wgW]4H??7E>%֥$-ӹ9~|aZjĂWw,JwygϭH>_[_OcI=U+_7b7Ax}ؐ((0!%Շ5{I92֘l[lWNJ)z/zpSzcMuse b?Zszw:?3#VAK|n[k>('=w|myܾ25V>xƴ*?>% @ @ @ @d >їcK6o$yL*v/RgdhIȍG+˓M-iucH+m5Ko׸*>ו?]ksoR>E4NG]}Qee!潿5ZUF (h雔[@&8g&团:XYCm1؞H_qOIʷ=v\R>[ؓ_J'  @ @ @ p]C/6ήm4|߄ҽ98/Qke:b-lUr[c>(%Iy[LfSrg*c cA4@XSg(^xly[*NUߩo%i,Ͷh+6}aI45 5[u]vun*-{c[)S6dG.Ne_\i^ /VB /'_k?JS8ݝfLXfLXgD~~!d?(Kiy(K @ @ @7 0ʛ..9tCv(\vFbR>{.rWb۪N%b^ƌ|} }݌\xk޽szvăzro~bG!=)M>OV;#]~[i)b%G]vܽFWrC[bӟ. T=$XWφOs[>DZ?G/>_Z`Ql\ |6q^{k,+/^Y|lUz(%c#Ku @ @ @ pjJךfN^H,lGr僚7ĢQo]~ru޻})o_ekpVqօZ_;;HZy3kM̍6DkkNk:Fi"ڮ1ڿ3~P\Ȧee!ϝiڲh$l?!@ @ @ @@5%k~y_C-#n\t_U]}a 0_}SV -߿Z&y.GXpWt>!VޑrIXӱ(A(uƴU?Z5+;-H#ڦ qu4ĒkŒŢ;i+9E @ @ @$pr<_ '"=%&Dr}H RI(VP"z0X`EMR  &@ T d _0{왽疙m8ξZg'^kՠ*ꭘs]諽rƛle{/лĄkͽٷa&}n/?txC0w n)X6NpOe}^*sxiVp'nÿrg0A݊vrM2Fx\`!G),w/uzl{){4LOGI5s{VN~f:eKM|t\(@ P(@ P(@ P@z@K:z"Kt:"ɼu)놧# ˭sg/+fߚ}Qυޑi+oHt_MAc22@ ̇U2o8(@ P(@ P(@ P@ 4ev^ ~a >SJԁC{grg>{@]g?wXM axY(/W| X9 4}-<sKD0>!bşи,[ۂ^~3#g^j^a8`&`[ѷ]YHh]?I*&^Z;p=m P(@ P(@ P(@ P+p?37-'!i.~J\Y[-S[ȃΝՈ! x)2|9v6|eB_3[a:Z5z{/1=(@ P(@ P(@ P(@#0WeHЯٿ}Iݶxr\;wS^%wk~Mmߙ$Koec~||;ymX!ԕens^6n .O#XS2̾Li6y ,ʒCoy nZf>!#U܆n<$VwYz9깓Qv}p!UݞtSَ|~fIP~كH"棠D Q{*4}COeD}6#MoO ? +P-r'[W2fPO?{sr{~v+VgQrضE(PgG)@ P(@ P(@ P(@ 4I<$=Sm{>E~8WwND }_w\Gڌe2ՎM犔9 v>2)K¤2| b6;3~ۅ4w'Q__s헅xjX,ҟV7Y"0- w# ~+~Y7gVszq` G^"[%>n~s~4 Jye(/^Cw錌W "I(gNt+W_9Hp¬e[FCf(@ P(@ P(@ PhmC灳h=f=P^WOw݄RF(SIl[7H4^ 8?uU۳./LJCk?wcij e=2;}za&mq[zqҽmCH|ֳǑvTb!?r3ӑ鬿kaFMR0hr(@ P(@ P(@ P@S1o?K^5>;JRg}}q -ݎ>uq~r-؎Kk ramcj/|t#pY#<&aGຫ~_2t:i.[ #| [Pw ۿn-O_DDklqo*Q5mko[6cݏlwB/k5wOaJTẇ&~2? P(@ P(@ P(@ P-%pɓ'7es2|x#ѹ3GJ VW!1||BtDϪ G^}l;;CDuq m~NsN)RξKe(~[9RF#eftҾP' szVJ[z6!k6T@ pLXGP^)Ⱥnz*G`& =֣dsP$`gKGq(v(<u(@ P(@ P(Zgst,ɀ u $ʃ?hL3VZ6R.0N{6ycK.>_V;.c}xlK'$q)q-ytyng ]gOgk+È_9}V5Rs4a&^-<\rKq 0fOݖ^ʻG>|f'1 hP+WNT,<݌xn} ye܍ݟėھw$Upݳ"mT "/wՒ`ڵu3](PV:j6:]b 8O}i5wvXdݎC!W =-,T&"`*؃J҃h؃9m'j}O2Lx?y9 F1 'L}Cˣ'>^ٚB;CP_'cx_LUثvFA?X`"&mR<+bjk 7~KK0|I 0 X:p>imJ3?(@ P(@ P(T'2 O'M[mۄ`PUuG;ޱ[[?z~N#*q<2MGcYY+Ƃb-@ :o^o0 :o7tt6޳1qqUf?j. 11rEF$tֈݬy,QwއEb͙4 w8{_XOo_|ьVzvͻj/ԓ(@ P(@ P@ 4f?_`6XPg+Qoc VD,`3, 7ǤԎI[H:*Y/ :[䤣TǼj .5$AGDE5pʺ{{b}Vyx 9O+זB," q~Ie[{,u a rw XXs9^H=Z/Y;!={'[_g6*~Ի o>^J}A &ʫA@W\ԩFrO\%1۹ZJl+cx 0'ş@[^ Bkw ȡ*ᤄ$yS0^ÿ CdE}B=16;W}eMO&?{qJ:kc%9sH={I8i쟜Cs_n3sk)ؾ% CΧXt?o ,$Jej P(@ P(@ P 4O>0,عi>*ڊf?v@2|n)=?U5&!g"KYVf8_JTuU(6͙g0Ϟ}P[f۩-ʱ^SQQerVV ('`H ~0UN5GasYH.k*ϫf[i\0In_3֡0Q<(q-dbIF#zH䜕b5(>ogܐxpTkFV^kUӞ^)"FC0K}\%wkvV+6fUQ䙩,U J4uҀGⷓch_(G uOOo]KtMys{yn bG k5%!(r5Qcft<|S3wBAcet2"Qvp(b^waX/Y~M@'׭\٭+ UW1*7nq 0'a@%bȢ|s+d#Ic׳Tk{b,|c![]gGd%l$tk:nk*Wvtlxd{Dv>R/QuB&,SmFXQ?>F3\D9`>1؃ioi[)ozf7wvw]Q oo9WӤ5S10-YV=kSq<=IǞ7rQ󪦾[[E/k6Qtf!EsxST`wz,V~un.kјrńҿmņ(\űϨh˭ {tR#>,~$sJ2C:K(@ P(@ PBC%Ѷ2_QC}TVU102'Jl=<3{%9"Czbkxխ*&#b|l%fjvVTċ]w;-bRV*87UM *q>(Cb*+{b`L?HIxШܾum?K$^bl\rŮuxtW1%+k׾[9!9l`MMڹZU: 7P[y@GQ|Y~M_"G^<jU *b:]7܏iYȚ Ur0/wiMshtw sf{kku[nt3~p v ;8 yf3ѿKLۃTfyUm }aqco=<(rN^m.+xőTYDo/A v=; UO{ y฼6@ v~-G\+u(XfBV+K[{]2"ePG's%ZǚW';|{=b,ǩjY5]hW$0.P-xT"4KAy%IQv:2zCd/9qPYWנ>yވk(>%͇e0^HEѣE2/@9"w~D P(@ P(@ P@# &'AyyZ"aFt>VJ[P^[Tsvf)?Ϡ2K{P^~kPYYjǚ`z8 hנ3_%{M2,?oMݪ{PX7EPz᩾L݋6@&ShԎoAyMK}IjY`w-Dz۹.Ac:lxQ"rp$ Y/?]އǞgk#הU7y9XOiR9=9i4Zf ؒ,r4^YVx ;'ߩ@CFdh2lZV=CP>,=GQ}?QNuMkPޙysډ!]oӅXjK|\RuՍ~|_kև= .wd+ڼv?ܾtW մ'k#q&u"v0wez@ P(@ P(@ P% y9=g7f.23"6U6F؂={u5u02^3Ad] vܼ҇ 2RѯW,"d ;7úP[;ünjĒG}P]1,+%wiAQ~rN~~QbG"{ˋx*MG~d,\.,[>>RYP4dD)-gb=<^ʱM;;IWG/ܢ:{|URS{18! \ՙ*_rfMUXP̽Y ֽRggbrJ=BXak)F3ŀ%)QCwVpF?M<a.Aj5>8~&ϭ s&|E|goFo{1hj­֝QpWo'1֠<*KEyzFHivʜjfޥH`>=0&0P(nrS <{T [>!ǡ3QMOōC%0fy |O$P#4Zo5bj 3=?USТ}'b J`QӮZ**@ *ǾSQxzNsWbCmU.Be'ʖAf :>)=e뻄_?CSc1ѱ=$czڍ2rAigqg(@ P(@ P@ Cm]:T Y#Xo0D :&jG +CkodeW!*,Ifm̿6Qшyɕ׽uD/ju=b8JeK`^ǖs&#C}QF+罂#b-[SbXW}L^F9=ʭΎxY1DD S+ [b,<(lLJ@^]"b6y6^,.)[Ҽw^&=*3_}[)49\%nDZ{P^)KzT9; Ԩ6P#} qwgu9@k]$z&!ˆ2G:wJ֝w.ʽ\]lqse%qyUX$pۥ

ď;1w5&M %@IDAT?1v7CWlt ߧA<(@ P(@ P%$:Ck8usRWfHƒw97Sz~ob;+6 d6:G!|&(6'Iꆼ߶[ ʫU蛆pd+6r ԼnĎ@.2k4y TDܘyv^G4h]c6(7cdњG`h?_u=Q٣KPkxG*t[]߹ ]TzJ.x f}rKP޽@=g>:z!3-ȃ %r=zEr]#^嵥{+Gk.Qw>N] ոQFk]<8XoOAyk݌cj&y_4F@X] i>n3ߊʚFü'|U[| NՠI;{ڑY8^Rf!k~>QKhdh'z-3AsFy!g]߂^8ss(@ P(@ PB&0cy:8#XCR_ݢ;)~_SY;}b l^¤F<|pW>n}8B4tQZ]d#%p#53}=9ӡĬk@13tj_nV*g9pt,E u_ьFWRl2y ~|gOy]ptMi 7{4"n}fh˴l#,{5F 4@@ R(կ:t\>}ǁtͺ#/ g9ڵ6`Pwl 2)zM ~v䱮b1s&"Si~7Ɋ sN)@ Q\\[\uuպow2", sd2LafK}x l]p$`d$m7~Ma P(@ P(@ P>M,92DdYΙ^A^X3C>^Ό-5 W(p].<}F`;e~}TԏCSt5g E{ۧuJc|72:1ERW&Ǽ(RgB&;0*G%׷:u5 >JnՌ=X@2gޒC1=|ߔ{KP/%x)8b1sˡ|Dqk\8^ʩp?ה@Y=><^J. fL Ŗ9F+]ן>֓/]?/d.Px]NqNӠolɨ 7[I2[_K2ȫl]ݥMYMܿm+%/ݵGwl8_S 4|[A;5䚣`Tfd)%2l? TaC P(@ P(@ @%8̪͝mm["G5' EmK9O;5kZ(@u!}r{5'x0W_f{yW'`+~J;f+98XT}ZnGa! (|J]W+| ?lae~tb6Ri_%?mcmKzE<vs6_3DdXÛGNYghYc@;U}*z_T AwK8z?Wӵx%XzR,h bZ>$ʦ+N|y[wVN[7~!ukz~8jKKKQ5P(@ P(@ P-$pe TqcYH9K~sp{Az"R:6K;ՠ5=ӻz{ӆCn~V?{ ^ nd8S]79a+z5xWW tqܶRmRB+(TLVhz46]cFMGX$fݐ#uR۴QnZ6w[T|i^5ҥZ2ZG]5f.ԗF4cI .z1nΏ 8{N܌ Wy˱oGq]a\(@ P(@ Pڽ@ӉI0S.b' eXJ aªE#w0؏.[nTjMqqor;2_%#W[ W ǥ Tonƶ?^~2C/H\utNVjpdÇjG ĘCmW2DX;ҸGcXt5Q{{FD" gs}ŮoϢ/®r ѠȞɺ.ނf$<jGQ3{K TpOת3YXΩ[2UCS qٻ)%H^alb᳘t~ssL5p(P {񦿗n^xΪmqyZuI_ | 4Tk?7u~Ln쓎}ŷݡG=*_mcFNg? fwUZ<(@ P(@ P(u5nDD#ΙXL](m)"MyBYۚ4 ݍ`a J y&ۧ)暄4啠@͡iҵꕺޭ;Q q ]e^s;T֌("wylAHeyc_2싲4TcmQ I zJ=ݼ-ꑀᣆ[_q1;N?Ҩ a٢-%Zú<=k?=!E0b{yΌcRѯn/yYcO P(@ P(@ k סx:*E:xVRS uٴm)Vrk:O#5ԡtkq%,Pl9T`}ƽ0i# bkR:6y"c/M,ǔ4 _]^ `&"{:#1}Df$f=5*URhV;ek{Ƹ#{~ba/Q~~)s>96co]6|Z-ovJAfYGWt6}Ww֠1c3z [G\ئI_3U-m>#X9=g=& h}Xٙcooi!r(@ P(@ P@.aFӷLx#o놮Q2-F@=#T3Hʋy+5\GDH'*t!kY-c1ũ(ɔZ $E{|4`)[QkFG\3_谹#1Ҹ[Izm>uRvlL^ u.bØ,|8+I GqmI{y#"9# :vy)qԶ{g3]f1ҽ>aC9cpq ^] ׌G'(3M5Kva"S^R~GTz[ H4Wcg$Ha},A\emt\?eDu}6=aJ>(,-Lo].意ĭDۘ nu1S᧽;F6@t\${4n\A8|]"*ғѠ.Z}w"o|5zc1==}qBudyUyAy| > Jԏ2Вu[*#!er?տS2QtZF#٬ܳ֍r4 I*v>rvd7,,Q=|:ؐձrstb;ɿ7\i[xڋoTw5F}4Li+5םno3~szrQv ˁkRLPR(@ P(@ P^ DzZ$(Zye|ܔgꆳWxAd&oN )=+</Ò1䚭lRlDxe*,MKAKSí37ۊxi}4AoggJuWF'ȯ }]'i^JޭOe`|HZO٧I䌜8H?~8BGCK;g]zK-%Aѓ9/f_F̣H5t; cWzcK>(wsiǩZ{G/偍p޽~cqgkU9.y{L$nzI܂r0nlǯ$f!芰m(tMP^v?`K8{g-s`~u>(/bz LҔUm_;!s,|MZꯄ] pB\wQ7ByOw>(̛=r7:>ץV}ᬿL_Yt r,H'Āw_zxB*Vy,,u[H(@ P(@ P(@1 cts0HoG0W/$+Gw{IcfbGKc3-ܖL}d=6iCwIJ50Ay4w;3lzY#ևaTL&?p{ S)@״?>N9j1 g2 ;ӹmg-{7 ʈ蓆ш!#ƽv{ 3i)oKiX"l褽~ϥKۥSmRKޱkDDC Dp)K.A.|ݿہOz 0ߘu1j3.! 7΁uh}5~c]C{(&ԗ!axv+,},0WvDdp7s47 :w| Rկ)ȃ+?nF챚.2ay;p׋m~1+<Q7`cA󐑧vv׷wKF[:'6KbCWoeo/])$zyWzEv:Gkn#&<.e#~`9$I}bapMy0&ϘWbӪ%;؟i}f1vwKUE"6͹Sz˛f}`1j?1%>$ٜv\(@ P(@ PtNmPBN *(.8y5ۢW lvj)[y WPrݔ,*F^IGڳ5^Ȍ`POEJ9+[Z7 wf}5Q]%ߵB~0Rkݎt/Bc_:j5?}Ȧ;t0>4;ꏀI3Y,{@/g}PC78. P(@ P(@ P Bc(aU(@ PtyHM?:'I1]D\@P%+gά ^-/rVk(@ P(@ P@``]_^6(t^B$M9+ӑDU ,i)jP%~hf:&ĩ+C?L*'Y>R(@ P(@ PR olTO <(@27UιB2ז㥻_? SR_{c}3fDShW2<@fWM6^](@ P(@ P*11l7(@ P c?U)kfˏU~(1I[irb +5M/ij_/P?q(@ P(@ P(p 00_v߂ l (@KD fv..?FBёd.kCսfM(xuRk#{kAN P(@ P(@v#ͥdC(@ P$`6Fc+@`Z=Cm(@ P(@ P@ p(8(@ P] 0(߮.g{k 튲=(@ P(@ Phʾ}\G(@ P(@ P(@ PBT0(@ P(@ P(@ P(@ l(@ \N24Z[Py휑ш2[lr/lO-r)@ P(@ P(@ P@`y(p UCI%TvPU?_Ŝs9Z4,)Tf8r%{(\ 6(@ P(@ P@``}__@h\<ũ69 iXG9ŌD!c/c1Io@=+@Ns5K;=K$WёVjJ=}ƍS^jWe`H]wd|LLFiLO}9H s[z+_<B0`->l8nIPws;< P(@ P(@ P腻䪭;q6+L?S6"_OL[b:s5@@? ; (Yyf]n嘪]QZjV~~1JfzolBiLQ\F蟬9',>!\iFr(@ P(@ P@`vwICA 5cudnja+'12]Ghϟ)m?ectGC8~ 0YVEȟ-Wv[0mߥYtPk6Zg* !MP2}|Oa(:%rAkyz6|2L 6bq}w`d={,*ŗ{cuCP,4& C4"ڲCHឃVdp6\Њ/5~/J+ց(@ P(@ P(@& 00TBm&XM ֪S[Զx˯QeD+;ranFtnlH;>*l=.M-K)G{w unNb-F\*-{X1Sk{?>S-N=}v^ a{.e5yQN[neiw[DƑXf.bGA9`_>qRFdwՀhLw~]~, /-7;އa]uxcwlW(@ P(@ P(p *$Vz}PQ蟌 w"tL jӨgak|14Acf`31v0xBq O(yuz°kՊ(f=eJ@x9#\Ue/gaA:G-Oב$+4籥>~lGMq!%`g\U˰,;Oo/g.{3,C{]4u4Blbѕ%stEkի^Z~Ҍ{㥥Y٬Ӑ^p=ic=V`FfmHI/oz ssvS{-BknM/L8(?qbogTK?Y3zw*u ; /?SMkgX~LF8K.'6/vfE,@?o߉J9铤̵6 Ɂv@nv?6>WJ0`)@F89r]@6d&AM}lsW.;E-)8+@P U*`/)؁x,,<2$8 P"pfkH ̜{99 9wzcם|'u{>s?<wܻyםꉏ'/r%ͽq}fO듑 7߈w/ڝiLfӅM7Şs˾9cߜ?[1m2 @ @ @D`˗UXfM÷ԱџtGm3.DةȖ/MɫFNFˆ2%ܶOm;W\r$Ƣs:sqU~c}ؕԳ3]Oy]1ֳb`]Uw}/%mXvEvU0 hCQb˘OEKޅhq߁d|boh.Yvm7qz%I?:y)S_,xF{-y}S͎ qxVO4t? P~Pڪ}1]sSn}uyOդND{`DHTj2c(3bO%kjnx<س){X53\6J=qG?_doךkOtǏ qwf8^C*&nx{lVΗޙZW=x<6B*cλ>Jy~q~[sOA @ @9 q$g㤖7/{bG M^~t-O-SUѮ;7OhHNJ}Ks6k<SZ)9ͱ땁)'v-ړ_щ 軨]w5MP>߈#/{Vm_;-ՃF?#U^w\&(oֻ-s _noMlXӟ_wOնCo%ʧ+'RA֗ZgXok!v PI~wً|R|SĻIO׃uw/}>hlYv6tgz!ڞޟ}#c//Fbp/7P>@1ym7cI`HgHˇ7K2.fS]qaxl}#oFq3zAL2xp0Gbdx0 ϧZo4l`Rnlܹb\KNzrܭ$ٝL^Xs )Kڗϔþ:7 Q'\ΒvgR3'bR̨FNGރ}78bS݅,ko&k$M6&/S&Wzb]w:Tb8?r-zWWwAC?ឪi??ꍖmſϞ #7k߃}a*.~jW~f$KG".P?O1֖;sf3|σ'::?W18zh|Ol^v8 [>T>ܛ,mTDS2 /AssQ,FG$ojذ-Nylhj!֯MXMIqˢGS{[oXllCa_tp'gT˳o&˳z3ı]i=[&m_JǙ+*꺭֍x3 vr}-ݩ2{J 8O^_9߄sߦ;NtE˺L4M?Gg_ ۏHAsRLSxzg4ɺj޿ڗ)QN~5O=~aBr3ov[fߛ-q}LSBs<55u1}>K>/_Gə﫣1}O.ݼɻߓti;׭+6{ϝM3~߻'o<'n;moٚ7훣cS\OpBZr?+ZQamQ @ @n]3jI~'\TSo{+DVw}xk5yg2i|vKfμxmWjsljÑjJǶ9=Px-*J<⓶VJ_3 q>og=P\|2M_ ۗL[\HvX^_?J?ܞr0ڎxpJ=SfyHAH]Bs?4>8ܓT4"k]ggAa!ߺǝ'3ոi5̱xn n{jCr.@T{6Ǧ(F @ @( նP[.tБb\ҳhVNI978>a/T;}Y+ŷ4Dj,NݔP>Ma˙OSOӮ-{$@0m!o(Ӑʰ}K= Ẫ;㵉173wu-hvfAWRYm{'f?|ft%X;m,sdqj+)̦{I=PpR0S8jjwY.h=Ga4> wƧ>8H|$~IE>UF wfcuM96GޱCWNG @ @fsCG'1,I\~Yb$?jpa4z{j.J*d؝M7т;&ҽG|zqpaZ,lX-uJg,9y Cc4݂dpG/F|xv~r sPe/MTxglX{ۍ;KnlĻ8q|Lc0R?O~LR[|65d=QMK%@ @ @ X~: pO9z}tJnIJymz.ƣ EZ<%pJj4N<9zXC1م7ҽ:x]gu[kL4~pNB-=U~{k3q ܸ:wH>ju*sgJRۋ\~2Vo_?HL1V+mnq[lؔ~яc_&6?c߼Ss[Q1-\AY`V27)VH @ @ @lk-<=OnT>&3G#[1</'S,7)9{lؐ/ӣ/LY|u(|icWڃӡCqgf`Wo}h‘Z>_4a|8kcݍGν%.?(=HGMɥo|/o+ѕfͱdG"| @ @ @` O7 [t)b[1T ΍Eǣ-/J*l}1hsGqx{T4Mmh!ܣŽ/<*ڞff~¤>-z Qub5P\Toܸ#5P\Du%Kf}8M^0:::2S2WnՀVN:u&S)={l{%'?Gۣ;ܥ kc_l2sDsPNs2E>O8܏E܏IV8 sbk{m/)t39~޻K;\؟/OO BW%O$0@*OcoǝoM>2˱G?o2zM| @ @Mu׌-슎ޏd[t?4uSK9^,5:?:{#=IIdiw*:™H2;#[w[u`z&-nbest c4~ EuMYtݎg4ŞEvyPלN۳It1~nd)m}Oղh{p:oݏWߡnPFݏ?F,޿H6O6s2+ۘZ^<v Roҋ{G{gx@*P?a݃q[d$2qB>/lOGOc!~=ۮ|#5{)=3q7n^$@ @ @iY8nt9]-\2n"@7yB Y~/"kK]{opd+,pOGt~4mJ]Ҟ.D{wEs \Ė'NwEgIz^+vUv5dص6y(G/:Ti#y`։əWݟ̴|r,iWbZ]eNP /̮ZgەҼ{j0Yxb햒Pmr_I~,gT-p8lol}23w%+r;fGS<Չ߉CqGqg[pocqW㝱a]!̛1~BKt~U][ @ @T`˗U˼f͚joc7-K"|8 fVHB%w%~\ƒi!zfIfol0bUJ7$dPͬ;O2IC3_U/)3v%q9)e gu[s:Ϣ{t~5o_ZS5gP$?@oSCSy]Ᾰg[Qc .@?[M^ZoXM3y]^ūs}:[Ģp;?;;Џ:c헒WW|/N=VSt NO o @ @,)0?'6 @`qFH} gz/ L~.y0ga>y7Ag|8~2}_س/nqs^~y>/ KV_  @ @ Pk`RC);ɢVɬu2qۓ˽H~N$x/뻅 @ @T@`NF @g[K)#qM\Ǔޖ,fjѶg' "I @ @} @@#hbwW3(j2I*ⓤ+AyýE~->]h]1Ψ?+ Hm&Xrbo @ @XHK/  @& dbwT3%S LaP>Eo:(p à|~X7 :# Ĩ`= @ @3KـhTX߾p#87`4%\i//Y{zZŽqm|hI?qL;2 @ @ @f Xf @撅3S멒 @ @ PF`KٛcU.cIPiKLLs  @ @ 4,e4M  @ @ @ @`/L @ @ @XKsܴ @ @ @($@ @ @ @) 04ǭb~x(VX11tbֹ:-cǃ;bG-[=J @ @ @ @Xqkf͚j'cůž8;[o^I`SF%-Nϫq  @ @ @ @`TsbB=?싩{TL&ڒ$@ @ @ @RR%Y9{7T{ @ @ @ X%ٰ3|pMO,3_"@ @ @ @ cp lɸ'1z5 0Wjڪ& @ @ @ qW7DC-5_-Zʪ @ @ @[@@u)_/?3>w98F鯎F[=qs;'Ѳ=gr[ @ @ @N\&?W-?rFϝl{{.W> @ @ @$ 0_If):6m+3ᡷmS @ @ @yV9M =FGbddo,F~fdЏz$(ɸ02׮]kLs] @ @ @ܲO}oWfeWI Y{j_QýަBs5E-"y @ @ @@ -,CT(7@  @ @ @ p gdj΍͖zxi @ @ @ @}}{SggJO$@ @ @ @ ky:H䮦6%  @ @ @ @`^[:YYi @ @ @! 0?Rtdt+f{"@ @ @ @<灷l{Ru)q8- @ @ @" 0_V9vǙGS۩ՁsGj$ @ @ @L' 0?-paC6~>bOL:G1I @ @ @ @wWr1n$Ğ>>Ni{vmNsƾ/}Sy$  @ @ @ @`F3bL#V|jE_I5wWxH'3}ݏ;essk9Wt @ @ @P@`~P-CC3oaLֆ{ƙg[}-#qÒ @ @ @ 0E@`~ X[y*o~"c]qbۺX߼ybw~ @ @ @TXqkUǚ5kvl9 $hhh445Efr> @ @ @ @`3Ϝ)ܚ!\ 뚮o  @ @ @$`)9)D @ @ @f& 0?3' @ @ @ @Ħ @ @ @̜"@ @ @ @sB @ @ @ @`f3s @ @ @I@`~Nl  @ @ @ @ I. @ @ @ 0'9)D @ @ @f& 0?3' @ @ @ @Ħ @ @ @ʙelrgmq\ՙ)sW.F) @ @ @ @r_n#Z'}#+'`Pn @ @ @,QK/сf_8 @ @ @zD&v:cHr,|6RC @ @ @ X~o3$/]~g6'5 @ @ @/`c=$@ @ @ @(`MįS_MZx5wrdV&sEY @ @ @X~f/1m&-;;@JypGt̒8IcŧVĊUbկ%w~{ǃɑj]A3 Wؑ϶ow`&@ @ @ @@"תIYan@''b3?cNBѷE]/HB'ZVEԮjM\"*e~+6~ۓ6Tox$}ޖoũN$Ƣs8c]'vFC @ @ @ p ϩfω|%oJᵮ:i>=@Td6}:c݋2sqw&S>>%wb_?P&(ݭRh|Ģ6  @ @ @X&Kr ɽ'ϟ.Iᣩ};c۔)hX_<}126~y-ŵ8s`$y-߯|qغ[-jڵdߧ]N菶 3y]@Q6 @ @ @ p]@`~^ V<>lwT4HQGu˳Iک؟nh7fbCcEeG,|+'Ճ=6D4}5N +P;po! @ @ @ O5Y{Vo=Ozn_|ڞLFG&"Gۦ)§r&GGjgO`jΓW/F#oOںd칧EӶ8lja=1A< @ @ @m_=V8Ao;Wb[4 _(Q=U.g#±S2~=;.6%  @ @ @ 0ChgxFj&ٴ#&uިɮD6֧FS/hOJ'sLd .DZ&].}ݱ~KC^-3~e&r?+i.WZC\ @ @ @] ]s;DHt댽˳n2F֦*'2mONW%<4E=+S}#.( @ @ @,e_Ne k޾3s83ܹIKW.۠|edt`f.+efϥ"e @ @ @̘_}ط;荎ښ316xՈL8m5 @ @ @:A]2ሣn뉮"}0^xPS˳\i?`ɻ .J*y>;gs92Z>V>({53~FuƉ7XxzK&@ @ @ @@kNz*\>ڿZ8Ѷظeb}{h63HQ`?*ힼPkOڟ' 9\tٶhmʁű]V!E @ @ @ ߰.j>QsK6YGTph}PhKVLd37ZhX'e4v{4=[ؑ<,ƒ¾I? IrC1Ͻ1^  @ @ @J`˗U+f͚j\HSgwc\52?[cWIн% mGӋwqrwsΡؼI`k􍝌lI[Gy!hqM-Ѻv0OW>˱Ƣs%%5_y=⥾=J*n- @ @ @:hP&Zx6x٧; s5DNjOkǧ[:{7elWO>펆{:kwIs=EѮ.'JML`K-?8̡8r3+'r&@ @ @ @gU׹~5-%{*lkk}qxwl{ۃq/ƾ'S旛D> X[l&v8g^_m-O<燯ϭܥBUeJlWޮ@K@T~f1IC @ @ @X~.j˽L.CF!yz)VI&m`ŪX: I3'u&%K**6Au¦ @ @ @_`K ^  @ @ @ p 50o)[E @ @ @y7ޙ  @ @ @ @Y  @ @ @ @ <{g&@ @ @ @[@@`d]$@ @ @ @' 0읙 @ @ @n[`u @ @ @nͳwf @ @ @oAE @ @ @y7ޙ  @ @ @ @Xy q]?>gժhX>5̻^ @ @ @ @=SsSGOu}1- @ @ @H X>1іm}?M)y  @ @ @ @Xqkf͚joc3?r1ϥ}#"k|E @ @ @N`xxxN}[]%[_:'<-A @ @ @O`yKZ>r i @ @ @ P/X"-/N~?'9$ @ @ @ @ % 0˜M2i,dRHJ @ @ @ @|ZC @ @ @X@`Ơ#@ @ @ @i4 @ @ @|-@Wעu @ @ @ @rJ6 @ @ @ @,-UHQ$ @ @ @xd6F g7ehԊ8Cq⭡9֬ @ @ @,'/_VCk֬v?޿X8y{ @ @ @ @`I ϩfωFK]\{  @ @ @ @XyK:wn#:t*Z룡!VM$@ @ @ @[U@`~#{TJ2= @ @ @ @ Rs .Jn?) @ @ @mP`BZ @ @ @SsN^sI  @ @ @ @` /= @ @ @7  @ @ @ @` /= @ @ @7  @ @ @ @` /= @ @ @s JnhLaK @ @ @L OR"q?>3YydZ @ @ @3H~kstn{mjK @ @ @VR^wF6~Fo4M @ @ @̘Ʌpu4U.(gL!@ @ @ @)`}$+-Ehh $@ @ @ @R/_V3f͚4 @ @ @%oKωM! @ @ @ 039E @ @ @$ 0?'6 @ @ @ @g$ @ @ @9ZB-ZkGg"S?-\^ @ @ @@݄szUc%m @ @ @ @!PKǾͱݑ[ZI @ @ @&0Y5NfzW]2C8hQm @ @ @ @ ת5t͚5/رw/c=;#`gT1 @ @ @,0<<\`#u3cMtěO̜/d @ @ @Թ@n[w7.z|C @ @ @@]#*8JK @ @ @ @!P߁a @ @ @ @|E @ @ @ @o @ @ @ PQ` #VV @ @ @ @.;05mv<ޖ&@ @ @ @/Pׁgё2lP vH @ @ @ @:|dcK7B󛲑=WĎGšo#?8[缚G @ @ @˗/_f͚j7]9~lg~,z:"3 @ @ @,j.]g&;7-A @ @ @R`e]*ըkcsOwŞXߐU͖/H @ @ @ @@ w`>/we$56ͱvejS @ @ @,:G 7F|C @ @ @@]s ol] {G)E @ @ @@AϽ"V5GvSDRl-;m @ @ @ @SĶR  @ @ @ @ O0#x*.\h @ @ @,)/_Vk֬v1 @ @ @ pK ϩu;c~NQ @ @ @ԙ|  @ @ @ @_^7 @ @ @ Pgu6 C @ @ @K@`~y @ @ @ @@ ـh @ @ @,/5zC @ @ @u& 0_g9 @ @ @ x  @ @ @ԙ|  @ @ @ @_^7 @ @ @ Pgu6 C @ @ @K@`~y @ @ @ @@ ـh @ @ @,/5zC @ @ @u& 0_g9 @ @ @ x  @ @ @ԙ|  @ @ @ @_^7 @ @ @ Pgu6 C @ @ @K@`~y @ @ @ @@ ـh @ @ @,/5zC @ @ @u& 0_g9 @ @ @ x  @ @ @ԙ|  @ @ @ @_^7 @ @ @ Pgu6 C @ @ @K`t. @ @ @ @@3+M @ @ @j! 0_ Eu @ @ @ @ `&@ @ @ @: @ @ @ @@ 0v @ @ @ @ZBQ @ @ @ @|  @ @ @ @@-k @ @ @ PA@` @ @ @ @PT @ @ @ 0_n @ @ @ P Z( @ @ @Tc7 @ @ @|-A @ @ @*W @ @ @BI[<^ IENDB`graphql-ruby-1.11.10/guides/subscriptions/subscription_classes.md000066400000000000000000000251371414121453000252450ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Subscriptions title: Subscription Classes desc: Subscription resolvers for pushing updates to clients index: 1 --- You can extend {{ "GraphQL::Schema::Subscription" | api_doc }} to create fields that can be subscribed to. These classes support several behaviors: - [Authorizing](#check-permissions-with-authorized) (or rejecting) initial subscription requests and subsequent updates - Returning values for [initial subscription requests](#initial-subscription-with-subscribe) - [Unsubscribing](#terminating-the-subscription-with-unsubscribe) from the server - Implicitly [scoping updates](#scope), to direct data to the right subscriber - [Skipping updates](#subsequent-updates-with-update) for certain clients (eg, don't send updates to the person who triggered the event) Continue reading to set up subscription classes. ## Add a base class First, add a base class for your application. You can hook up your base classes there: ```ruby # app/graphql/subscriptions/base_subscription.rb class Subscriptions::BaseSubscription < GraphQL::Schema::Subscription # Hook up base classes object_class Types::BaseObject field_class Types::BaseField argument_class Types::BaseArgument end ``` (This base class is a lot like the {% internal_link "mutation base class", "/mutations/mutation_classes" %}. They're both subclasses of {{ "GraphQL::Schema::Resolver" | api_doc }}.) ## Extend the base class and hook it up Define a class for each subscribable event in your system. For example, if you run a chat room, you might publish events whenever messages are posted in a room: ```ruby # app/graphql/subscriptions/message_was_posted.rb class Subscriptions::MessageWasPosted < Subscriptions::BaseSubscription end ``` Then, hook up the new class to the {% internal_link "Subscription root type", "subscriptions/subscription_type" %} with the `subscription:` option: ```ruby class Types::SubscriptionType < Types::BaseObject field :message_was_posted, subscription: Subscriptions::MessageWasPosted end ``` Now, it will be accessible as: ```graphql subscription { messageWasPosted(roomId: "abcd") { # ... } } ``` ## Arguments Subscription fields take {% internal_link "arguments", "/fields/arguments" %} just like normal fields. They also accept a {% internal_link "`loads:` option", "/mutations/mutation_classes#auto-loading-arguments" %} just like mutations. For example: ```ruby class Subscriptions::MessageWasPosted < Subscriptions::BaseSubscription # `room_id` loads a `room` argument :room_id, ID, required: true, loads: Types::RoomType # It's passed to other methods as `room` def subscribe(room:) # ... end def update(room:) # ... end end ``` This can be invoked as ```graphql subscription($roomId: ID!) { messageWasPosted(roomId: $roomId) { # ... } } ``` If the ID doesn't find an object, then the subscription will be unsubscribed (with `#unsubscribe`, see below). ## Fields Like mutations, you can use a generated return type for subscriptions. When you add `field(...)`s to a subscription, they'll be added to the subscription's generated return type. For example: ```ruby class Subscriptions::MessageWasPosted < Subscriptions::BaseSubscription field :room, Types::RoomType, null: false field :message, Types::MessageType, null: false end ``` will generate: ```graphql type MessageWasPostedPayload { room: Room! message: Message! } ``` Which you can use in queries like: ```graphql subscription($roomId: ID!) { messageWasPosted(roomId: $roomId) { room { name } message { author { handle } body postedAt } } } ``` If you configure fields with `null: true`, then you can return different data in the initial subscription and the subsequent updates. (See lifecycle methods below.) Instead of a generated type, you can provide an already-configured type with `payload_type`: ```ruby # Just return a message payload_type Types::MessageType ``` (In that case, don't return a hash from `#subscribe` or `#update`, return a `message` object instead.) ## Scope Usually, GraphQL-Ruby uses explicitly-passed arguments to determine when a {% internal_link "trigger", "subscriptions/triggers" %} applies to an active subscription. But, you can use `subscription_scope` to configure _implicit_ conditions on updates. When `subscription_scope` is configured, only triggers with a matching `scope:` value will cause clients to receive updates. `subscription_scope` accepts a symbol and the given symbol will be looked up in `context` to find a scope value. For example, this subscription will use `context[:current_organization_id]` as a scope: ```ruby class Subscriptions::EmployeeHired < Subscriptions::BaseSubscription # ... subscription_scope :current_organization_id end ``` Clients subscribe _without_ any arguments: ```graphql subscription { employeeHired { hireDate employee { name department } } } ``` But `.trigger`s are routed using `scope:`. So, if the subscriber's context includes `current_organization_id: 100`, then the trigger must include the same `scope:` value: ```ruby MyAppSchema.subscriptions.trigger( # Field name :employee_hired, # Arguments {}, # Object { hire_date: Time.now, employee: new_employee }, # This corresponds to `context[:current_organization_id]` # in the original subscription: scope: 100 ) ``` Scope is also used for determining whether subscribers can receive the same {% internal_link "broadcast", "subscriptions/implementation#broadcast" %}. ## Check Permissions with #authorized? Suppose a client is subscribing to messages in a chat room: ```graphql subscription($roomId: ID!) { messageWasPosted(roomId: $roomId) { message { author { handle } body postedAt } } } ``` You can implement `#authorized?` to check that the user has permission to subscribe to these arguments (and receive updates for these arguments), for example: ```ruby def authorized?(room:) context[:viewer].can_read_messages?(room) end ``` The method may return `false` or raise a `GraphQL::ExecutionError` to halt execution. This method is called _before_ `#subscribe` and `#update`, described below. This way, if a user's permissions have changed since they subscribed, they won't receive updates unauthorized updates. Also, if this method fails before calling `#update`, then the client will be automatically unsubscribed (with `#unsubscribe`). ## Initial Subscription with #subscribe `def subscribe(**args)` is called when a client _first_ sends a `subscription { ... }` request. In this method, you can do a few things: - Raise `GraphQL::ExecutionError` to halt and return an error - Return a value to give the client an initial response - Return `:no_response` to skip the initial response - Return `super` to fall back to the default behavior (which is `:no_response`). You can define this method to add initial responses or perform other logic before subscribing. ### Adding an Initial Response (__Note__: only supported when using the new {% internal_link "Interpreter runtime", "/queries/interpreter#installation" %}) By default, GraphQL-Ruby returns _nothing_ (`:no_response`) on an initial subscription. But, you may choose to override this and return a value in `def subscribe`. For example: ```ruby class Subscriptions::MessageWasPosted < Subscriptions::BaseSubscription # ... field :room, Types::RoomType, null: true def subscribe(room:) # authorize, etc ... # Return the room in the initial response { room: room } end end ``` Now, a client can get some initial data with: ```graphql subscription($roomId: ID!) { messageWasPosted(roomId: $roomId) { room { name messages(last: 40) { # ... } } } } ``` ## Subsequent Updates with #update (__Note__: only supported when using the new {% internal_link "Interpreter runtime", "/queries/interpreter#installation" %}) After a client has registered a subscription, the application may trigger subscription updates with `MySchema.subscriptions.trigger(...)` (see the {% internal_link "Triggers guide", "/subscriptions/triggers" %} for more). Then, `def update` will be called for each client's subscription. In this method you can: - Unsubscribe the client with `unsubscribe` - Return a value with `super` (which returns `object`) or by returning a different value. - Return `:no_update` to skip this update ### Skipping subscription updates (__Note__: only supported when using the new {% internal_link "Interpreter runtime", "/queries/interpreter#installation" %}) Perhaps you don't want to send updates to a certain subscriber. For example, if someone leaves a comment, you might want to push the new comment to _other_ subscribers, but not the commenter, who already has that comment data. You can accomplish this by returning `:no_update`. ```ruby class Subscriptions::CommentWasAdded < Subscriptions::BaseSubscription def update(post_id:) comment = object # # if comment.author == context[:viewer] :no_update else # Continue updating this client, since it's not the commenter super end end end ``` ### Returning a different object for subscription updates (__Note__: only supported when using the new {% internal_link "Interpreter runtime", "/queries/interpreter#installation" %}) By default, whatever object you pass to `.trigger(event_name, args, object)` will be used for responding to subscription fields. But, you can return a different object from `#update` to override this: ```ruby field :queue, Types::QueueType, null: false # eg, `MySchema.subscriptions.trigger("queueWasUpdated", {name: "low-priority"}, :low_priority)` def update(name:) # Make a Queue object which _represents_ the queue with this name queue = JobQueue.new(name) # This object was passed to `.trigger`, but we're ignoring it: object # => :low_priority # return the queue instead: { queue: queue } end ``` ## Terminating the subscription with #unsubscribe Within a subscription method, you may call `unsubscribe` to terminate the client's subscription, for example: ```ruby def update(room:) if room.archived? # Don't let anyone subscribe to messages on an archived room unsubscribe else super end end ``` `#unsubscribe` has the following effects: - The subscription is unregistered from the backend (this is backend-specific) - The client is told to unsubscribe (this is transport-specific) `#unsubscribe` does _not_ halt the current update. Arguments with `loads:` configurations will call `unsubscribe` if they are `required: true` and their ID doesn't return a value. (It's assumed that the subscribed object was deleted.) graphql-ruby-1.11.10/guides/subscriptions/subscription_type.md000066400000000000000000000032071414121453000245630ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Subscriptions title: Subscription Type desc: The root type for subscriptions index: 1 --- `Subscription` is the entry point for all subscriptions in a GraphQL system. Each field corresponds to an event which may be subscribed to: ```graphql type Subscription { # Triggered whenever a post is added postWasPublished: Post # Triggered whenever a comment is added; # to watch a certain post, provide a `postId` commentWasPublished(postId: ID): Comment } ``` This type is the root for `subscription` operations, for example: ```graphql subscription { postWasPublished { # This data will be delivered whenever `postWasPublished` # is triggered by the server: title author { name } } } ``` To add subscriptions to your system, define an `ObjectType` named `Subscription`: ```ruby # app/graphql/types/subscription_type.rb class Types::SubscriptionType < GraphQL::Schema::Object # If you're using the interpreter, also add: extend GraphQL::Subscriptions::SubscriptionRoot field :post_was_published, subscription: Subscriptions::PostWasPublished # ... end ``` Then, add it as the subscription root with `subscription(...)`: ```ruby # app/graphql/my_schema.rb class MySchema < GraphQL::Schema query(Types::QueryType) # ... # Add Subscription to subscription(Types::SubscriptionType) end ``` See {% internal_link "Implementing Subscriptions","subscriptions/implementation" %} for more about actually delivering updates. See {% internal_link "Subscription Classes", "subscriptions/subscription_classes" %} for more about implementing subscription root fields. graphql-ruby-1.11.10/guides/subscriptions/triggers.md000066400000000000000000000046051414121453000226270ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Subscriptions title: Triggers desc: Sending updates from your application to GraphQL index: 2 --- From your application, you can push updates to GraphQL clients with `.trigger`. Events are triggered _by name_, and the name must match fields on your {% internal_link "Subscription Type","subscriptions/subscription_type" %} ```ruby # Update the system with the new blog post: MySchema.subscriptions.trigger(:post_added, {}, new_post) ``` The arguments are: - `name`, which corresponds to the field on subscription type - `arguments`, which corresponds to the arguments on subscription type (for example, if you subscribe to comments on a certain post, the arguments would be `{post_id: comment.post_id}`.) - `object`, which will be the root object of the subscription update - `scope:` (shown below) for implicitly scoping the clients who will receive updates. ## Scope To send updates to _certain clients only_, you can use `scope:` to narrow the trigger's reach. Scopes are based on query context: a value in `context:` is used as the scope; an equivalent value must be passed with `.trigger(... scope:)` to update that client. (The value is serialized with {{ "GraphQL::Subscriptions::Serialize" | api_doc }}) To specify that a topic is scoped, add a `subscription_scope` option to the Subscription class: ```ruby class Subscriptions::CommentAdded < Subscription::BaseSubscription description "A comment was added to one of the viewer's posts" # For a given viewer, this will be triggered # whenever one of their posts gets a new comment subscription_scope :current_user_id # ... end ``` (Read more in the {% internal_link "Subscription Classes guide", "subscriptions/subscription_classes#scope" %}.) Then, subscription operations should have a `context: { current_user_id: ... }` value, for example: ```ruby # current_user_id will be the scope for some subscriptions: MySchema.execute(query_string, context: { current_user_id: current_user.id }) ``` Finally, when events happen in your app, you should provide the scoping value as `scope:`, for example: ```ruby # A new comment is added comment = post.comments.create!(attrs) # notify the author author_id = post.author.id MySchema.subscriptions.trigger(:comment_added, {}, comment, scope: author_id) ``` Since this trigger has a `scope:`, only subscribers with a matching scope value will be updated. graphql-ruby-1.11.10/guides/testing/000077500000000000000000000000001414121453000172205ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/testing/integration_tests.md000066400000000000000000000135161414121453000233150ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Testing title: Integration Tests desc: Run the whole GraphQL stack in tests index: 2 --- Besides testing {% internal_link "schema structure", "/testing/schema_structure" %}, you should also test your GraphQL system's behavior. There are really a few levels to this: - __Application-level__ behaviors, like business logic, permissions, and persistence. These behaviors may be shared by your API and user interface. - __Interface-level__ behaviors, like GraphQL fields, mutations, error scenarios, and HTTP-specific behaviors. These are unique to your GraphQL system. - __Transport-level__ behaviors, like HTTP headers, parameters and status codes ## Testing Application-Level Behaviors When it comes to _how your application behaves_, you should lean on _unit tests_ which exercise application primitives directly. For example, if postings require a title and a body, you should write a test for `Post` which asserts that invalid `Post`s fail to save. Several other components of the application may be tested this way: - Permissions: test your authorization system using example resources and actors. Dedicated, high-level frameworks like [Pundit](https://github.com/varvet/pundit) make it easy to test authorization in isolation. - Business logic: What are the _operations_ that a user can perform in your system? For example, on a blog, they might be: drafting and publishing posts, moderating comments, filtering posts by category, or blocking users. Test these operations in isolation so you can be confident that the core code is correct. - Persistence (and other external services): how does your app interact with the "outside world", like databases, files, and third-party APIs? These interactions also deserve specific tests. By testing these (and other) application-level behaviors _without_ GraphQL, you can reduce the overhead of your test suite and simplify your testing scenarios. ## Testing Interface-Level Behaviors After building your application, you give it an interface so that people (or other software) can interact with it. Sometimes the interface is a website, other times it's a GraphQL API. The interface has transport-specific primitives that map to your application's primitives. For example, a React app might have components that correspond to `Post`, `Comment`, and a `Moderation` operation. (These components might even be context-specific, like `ThreadComment` or `DraftPost`.) Similarly, a GraphQL interface has types and fields that correspond to the underlying application primitives (like `Post` and `Comment` types, a `Post.isDraft` field, or a `ModerateComment` mutation). The best way to test a GraphQL interface is with _integration tests_ which run the whole GraphQL system (using `MySchema.execute(...)`). By using an integration test, you can be sure that all of GraphQL-Ruby's internal systems are engaged (validation, analysis, authorization, data loading, response type-checking, etc.). A basic integration test might look like: ```ruby it "loads posts by ID" do query_string = <<-GRAPHQL query($id: ID!){ node(id: $id) { ... on Post { title id isDraft comments(first: 5) { nodes { body } } } } } GRAPHQL post = create(:post_with_comments, title: "My Cool Thoughts") post_id = MySchema.id_from_object(post, Types::Post, {}) result = MySchema.execute(query_string, variables: { id: post_id }) post_result = result["data"]["node"] # Make sure the query worked assert_equal post_id, post_result["id"] assert_equal "My Cool Thoughts", post_result["title"] end ``` To make sure that different parts of the system are properly engaged, you can add integration tests for specific scenarios, too. For example, you could add a test to make sure that data is hidden from some users: ```ruby it "doesn't show draft posts to anyone except their author" do author = create(:user) non_author = create(:non_user) draft_post = create(:post, draft: true, author: author) query_string = <<-GRAPHQL query($id: ID!) { node(id: $id) { ... on Post { isDraft } } } GRAPHQL post_id = MySchema.id_from_object(post, Types::Post, {}) # Authors can see their drafts: author_result = MySchema.execute(query_string, context: { viewer: author }, variables: { id: post_id }) assert_equal true, author_result["data"]["node"]["isDraft"] # Other users can't see others' drafts non_author_result = MySchema.execute(query_string, context: { viewer: non_author }, variables: { id: post_id }) assert_nil author_result["data"]["node"] end ``` This test engages the underlying authorization and business logic, and provides a sanity check at the GraphQL interface layer. ## Testing Transport-Level Behaviors GraphQL is usually served over HTTP. You probably want tests that make sure that HTTP inputs are correctly prepared for GraphQL. For example, you might test that: - POST data is correctly turned into query variables - Authentication headers are used to load a `context[:viewer]` In Rails, you might use a [functional test](https://guides.rubyonrails.org/testing.html#functional-tests-for-your-controllers) for this, for example: ```ruby it "loads user token into the viewer" do query_string = "{ viewer { username } }" post graphql_path, params: { query: query_string } json_response = JSON.parse(@response.body) assert_nil json_response["data"]["viewer"], "Unauthenticated requests have no viewer" # This time, add some authentication to the HTTP request user = create(:user) post graphql_path, params: { query: query_string }, headers: { "Authorization" => "Bearer #{user.auth_token}" } json_response = JSON.parse(@response.body) assert_equal user.username, json_response["data"]["viewer"], "Authenticated requests load the viewer" end ``` graphql-ruby-1.11.10/guides/testing/overview.md000066400000000000000000000013321414121453000214070ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Testing title: Overview desc: Testing a GraphQL system index: 0 redirect_from: - /schema/testing --- So, you've spiked a GraphQL API, and now you're ready to tighten things up and add some proper tests. These guides will help you think about how to ensure stability and compatibility for your GraphQL system. - {% internal_link "Structure testing", "/testing/schema_structure" %} verifies that schema changes are backwards-compatible. This way, you don't break existing clients. - {% internal_link "Runtime testing", "/testing/integration_tests" %} exercises the various behaviors of the GraphQL system, making sure that it returns the right data to the right clients. graphql-ruby-1.11.10/guides/testing/schema_structure.md000066400000000000000000000030071414121453000231220ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Testing title: Schema Structure desc: Make sure that your schema changes are backwards-compatible index: 1 --- Structural changes to a GraphQL schema come in two categories: - __Breaking__ changes may cause previously-valid queries to become invalid. For example, if you remove the `title` field, anyone who tries to query that field will get a validation error instead of response data. - __Non-breaking__ changes _add_ options to a schema without breaking previously-valid queries. Making a _breaking_ change can be bad news for your API clients, since their applications may break. But, sometimes they're required. _Non-breaking_ changes don't affect existing queries, since they just _add_ new parts to the schema. Here are few tips for managing schema structure changes. ## Maintain a `.graphql` schema dump Make structure changes part of the normal code review process by adding a `schema.graphql` artifact to your project. This way, any changes to schema structure will show up clearly in a pull request as a diff to that file. You can read about this approach in ["Tracking Schema Changes with GraphQL-Ruby"](https://rmosolgo.github.io/blog/2017/03/16/tracking-schema-changes-with-graphql-ruby/) or the built-in {{ "GraphQL::RakeTask" | api_doc }} for generating schema dumps. ## Automatically check for breaking changes You can use [GraphQL::SchemaComparator](https://github.com/xuorig/graphql-schema_comparator) to check for breaking changes during development or CI. graphql-ruby-1.11.10/guides/type_definitions/000077500000000000000000000000001414121453000211175ustar00rootroot00000000000000graphql-ruby-1.11.10/guides/type_definitions/directives.md000066400000000000000000000061661414121453000236130ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Directives desc: Special instructions for the GraphQL runtime index: 10 experimental: true --- Directives are system-defined keywords that modify execution. All GraphQL systems have at least _two_ directives, `@skip` and `@include`. For example: ```ruby query ProfileView($renderingDetailedProfile: Boolean!){ viewer { handle # These fields will be included only if the check passes: ... @include(if: $renderingDetailedProfile) { location homepageUrl } } } ``` Here's how the two built-in directives work: - `@skip(if: ...)` skips the selection if the `if: ...` value is truthy ({{ "GraphQL::Schema::Directive::Skip" | api_doc }}) - `@include(if: ...)` includes the selection if the `if: ...` value is truthy ({{ "GraphQL::Schema::Directive::Include" | api_doc }}) GraphQL-Ruby also supports custom directives for use with the {% internal_link "interpreter runtime", "/queries/interpreter" %}. ## Custom Directives Custom directives extend {{ "GraphQL::Schema::Directive" | api_doc }}: ```ruby # app/graphql/directives/my_directive.rb class Directives::MyDirective < GraphQL::Schema::Directive description "A nice runtime customization" end ``` Then, they're hooked up to the schema using `directive(...)`: ```ruby class MySchema < GraphQL::Schema # Custom directives are only supported by the Interpreter runtime use GraphQL::Execution::Interpreter # Attach the custom directive to the schema directive(Directives::MyDirective) end ``` And you can reference them in the query with `@directive_name(...)`: ```ruby query { field @directive_name { id } } ``` {{ "GraphQL::Schema::Directive::Feature" | api_doc }} and {{ "GraphQL::Schema::Directive::Transform" | api_doc }} are included in the library as examples. ### Custom Name By default, the directive's name is taken from the class name. You can override this with `graphql_name`, for example: ```ruby class Directives::IsPrivate < GraphQL::Schema::Directive graphql_name "someOtherName" end ``` ### Arguments Like fields, directives may have {% internal_link "arguments", "/fields/arguments" %} : ```ruby argument :if, Boolean, required: true, description: "Skips the selection if this condition is true" ``` ### Runtime hooks Directive classes may implement the following class methods to interact with the runtime: - `def self.include?(obj, args, ctx)`: If this hook returns `false`, the nodes flagged by this directive will be skipped at runtime. - `def self.resolve(obj, args, ctx)`: Wraps the resolution of flagged nodes. Resolution is passed as a __block__, so `yield` will continue resolution. The return value of this method will be treated as the return value of the flagged field. Looking for a runtime hook that isn't listed here? {% open_an_issue "New directive hook: @something", " " %} to start the conversation! ### Directive object lifecycle Currently, Directive classes are never initialized. A later version of GraphQL may initialize these objects for runtime application. graphql-ruby-1.11.10/guides/type_definitions/enums.md000066400000000000000000000043251414121453000225740ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Enums desc: Enums are sets of discrete values index: 2 --- Enum types are sets of discrete values. An enum field must return one of the possible values of the enum. In the [GraphQL Schema Definition Language](https://graphql.org/learn/schema/#type-language) (SDL), enums are described like this: ```ruby enum MediaCategory { AUDIO IMAGE TEXT VIDEO } ``` So, a `MediaCategory` value is one of: `AUDIO`, `IMAGE`, `TEXT`, or `VIDEO`. This is similar to [ActiveRecord enums](https://api.rubyonrails.org/classes/ActiveRecord/Enum.html). In a GraphQL query, enums are written as identifiers (not strings), for example: ```ruby search(term: "puppies", mediaType: IMAGE) { ... } ``` (Notice that `IMAGE` doesn't have quotes.) But, when GraphQL responses or variables are transported using JSON, enum values are expressed as strings, for example: ```ruby # in a graphql controller: params["variables"] # { "mediaType" => "IMAGE" } ``` ## Defining Enum Types In your application, enums extend {{ "GraphQL::Schema::Enum" | api_doc }} and define values with the `value(...)` method: ```ruby # First, a base class # app/graphql/types/base_enum class Types::BaseEnum < GraphQL::Schema::Enum end # app/graphql/types/media_category.rb class Types::MediaCategory < Types::BaseEnum value "AUDIO", "An audio file, such as music or spoken word" value "IMAGE", "A still image, such as a photo or graphic" value "TEXT", "Written words" value "VIDEO", "Motion picture, may have audio" end ``` Each value may have: - A description (as the second argument or `description:` keyword) - A deprecation reason (as `deprecation_reason:`), marking this value as deprecated - A corresponding Ruby value (as `value:`), see below By default, Ruby strings correspond to GraphQL enum values. But, you can provide `value:` options to specify a different mapping. For example, if you use symbols instead of strings, you can say: ```ruby value "AUDIO", value: :audio ``` Then, GraphQL inputs of `AUDIO` will be converted to `:audio` and Ruby values of `:audio` will be converted to `"AUDIO"` in GraphQL responses. Enum classes are never instantiated and their methods are never called. graphql-ruby-1.11.10/guides/type_definitions/extensions.md000066400000000000000000000132501414121453000236410ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Extending the GraphQL-Ruby Type Definition System desc: Adding metadata and custom helpers to the DSL index: 8 redirect_from: - /schema/extending_the_dsl/ --- While integrating GraphQL into your app, you can customize the definition DSL. For example, you might: - Assign "area of responsibility" to different types and fields - DRY up shared logic between types and fields - Attach metadata for use during authorization This guide describes various options for extending the class-based definition API. Keep in mind that these approaches may change as the API matures. If you're having trouble, consider opening an issue on GitHub to get help. **Note**: This document describes best practice with GraphQL-Ruby 1.10+. For customizing schemas on older versions, use GitHub to browse older versions of this page. ## Customization Overview In general, the schema definition process goes like this: - The application defines lots of classes for the GraphQL types - Starting from root types (`query`, `mutation`, and `subscription`) and any defined `orphan_types`, the schema discovers all types, fields, arguments, enum values, and directives in the schema - Non-type objects (fields, arguments, enum values) are initialized when they're attached to the classes or instances they belong to. ## Customizing type definitions In your custom classes, you can add class-level instance variables that hold configuration. For example: ```ruby class Types::BaseObject < GraphQL::Schema::Object # Call this method in an Object class to get or set the permission level: def self.required_permission(permission_level = nil) if permission_level.nil? # return the configured value @required_permission else @required_permission = permission_level end end end # Then, in concrete classes class Dossier < BaseObject # The Dossier object type will have `.metadata[:required_permission] # => :admin` required_permission :admin end # Now, the type responds to that method: Dossier.required_permission # => :admin ``` Now, any runtime code which calls `type.required_permission` will get the configured value. ### Customizing fields Fields are generated in a different way. Instead of using classes, they are generated with instances of `GraphQL::Schema::Field` (or a subclass). In short, the definition process works like this: ```ruby # This is what happens under the hood, roughly: # In an object class: field :name, String, null: false # ... # Leads to: field_config = GraphQL::Schema::Field.new(name: :name, type: String, null: false) ``` So, you can customize this process by: - creating a custom class which extends `GraphQL::Schema::Field` - overriding `#initialize` on that class (instance methods) - registering that class as the `field_class` on Objects and Interfaces which should use the customized field. For example, you can create a custom class which accepts a new parameter to `initialize`: ```ruby class Types::AuthorizedField < GraphQL::Schema::Field # Override #initialize to take a new argument: def initialize(*args, required_permission: nil, **kwargs, &block) @required_permission = required_permission # Pass on the default args: super(*args, **kwargs, &block) end attr_reader :required_permission end ``` Then, pass the field class as `field_class(...)` wherever it should be used: ```ruby class Types::BaseObject < GraphQL::Schema::Object # Use this class for defining fields field_class AuthorizedField end # And.... class Types::BaseInterface < GraphQL::Schema::Interface field_class AuthorizedField end class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation field_class AuthorizedField end ``` Now, `AuthorizedField.new(*args, &block)` will be used to create `GraphQL::Schema::Field`s on those types. At runtime `field.required_permission` will return the configured value. ### Customizing Connections Connections may be customized in a similar way to Fields. - Create a new class extending 'GraphQL::Types::Relay::BaseConnection' - Assign it to your object/interface type with `connection_type_class(MyCustomConnection)` For example, you can create a custom connection: ```ruby class Types::MyCustomConnection < GraphQL::Types::Relay::BaseConnection field :total_count, Integer, null: false def total_count object.items.size end end ``` Then, pass the field class as `connection_type_class(...)` wherever it should be used: ```ruby module Types class Types::BaseObject < GraphQL::Schema::Object # Use this class for defining connections connection_type_class MyCustomConnection end end ``` Now, all type classes that extend `BaseObject` will have a connection_type with the additional field `totalCount`. ### Customizing Edges Edges may be customized in a similar way to Connections. - Create a new class extending 'GraphQL::Types::Relay::BaseEdge' - Assign it to your object/interface type with `edge_type_class(MyCustomEdge)` ### Customizing Arguments Arguments may be customized in a similar way to Fields. - Create a new class extending `GraphQL::Schema::Argument` - Use `argument_class(MyArgClass)` to assign it to your base field class, base resolver class, and base mutation class Then, in your custom argument class, you can use `#initialize(name, type, desc = nil, **kwargs)` to take input from the DSL. ### Customizing Enum Values Enum values may be customized in a similar way to Fields. - Create a new class extending `GraphQL::Schema::EnumValue` - Assign it to your base `Enum` class with `enum_value_class(MyEnumValueClass)` Then, in your custom argument class, you can use `#initialize(name, desc = nil, **kwargs)` to take input from the DSL. graphql-ruby-1.11.10/guides/type_definitions/field_extensions.md000066400000000000000000000067531414121453000250160ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Field Extensions desc: Programmatically modify field configuration and resolution index: 10 --- {{ "GraphQL::Schema::FieldExtension" | api_doc }} provides a way to modify user-defined fields in a programmatic way. For example, Relay connections are implemented as a field extension ({{ "GraphQL::Schema::Field::ConnectionExtension" | api_doc }}). ### Making a new extension Field extensions are subclasses of {{ "GraphQL::Schema::FieldExtension" | api_doc }}: ```ruby class MyExtension < GraphQL::Schema::FieldExtension end ``` ### Using an extension Defined extensions can be added to fields using the `extensions: [...]` option or the `extension(...)` method: ```ruby field :name, String, null: false, extensions: [UpcaseExtension] # or: field :description, String, null: false do extension(UpcaseExtension) end ``` See below for how extensions may modify fields. ### Modifying field configuration When extensions are attached, they are initialized with a `field:` and `options:`. Then, `#apply` is called, when they may extend the field they're attached to. For example: ```ruby class SearchableExtension < GraphQL::Schema::FieldExtension def apply # add an argument to this field: field.argument(:query, String, required: false, description: "A search query") end end ``` This way, an extension can encapsulate a behavior requiring several configuration options. ### Modifying field execution Extensions have two hooks that wrap field resolution. Since GraphQL-Ruby supports deferred execution, these hooks _might not_ be called back-to-back. First, {{ "GraphQL::Schema::FieldExtension#resolve" | api_doc }} is called. `resolve` should `yield(object, arguments)` to continue execution. If it doesn't `yield`, then the underlying field won't be called. Whatever `#resolve` returns will be used for continuing execution. After resolution and _after_ syncing lazy values (like `Promise`s from `graphql-batch`), {{ "GraphQL::Schema::FieldExtension#after_resolve" | api_doc }} is called. Whatever that method returns will be used as the field's return value. See the linked API docs for the parameters of those methods. #### Execution "memo" One parameter to `after_resolve` deserves special attention: `memo:`. `resolve` _may_ yield a third value. For example: ```ruby def resolve(object:, arguments:, **rest) # yield the current time as `memo` yield(object, arguments, Time.now.to_i) end ``` If a third value is yielded, it will be passed to `after_resolve` as `memo:`, for example: ```ruby def after_resolve(value:, memo:, **rest) puts "Elapsed: #{Time.now.to_i - memo}" # Return the original value value end ``` This allows the `resolve` hook to pass data to `after_resolve`. Instance variables may not be used because, in a given GraphQL query, the same field may be resolved several times concurrently, and that would result in overriding the instance variable in an unpredictable way. (In fact, extensions are frozen to prevent instance variable writes.) ### Extension options The `extension(...)` method takes an optional second argument, for example: ```ruby extension(LimitExtension, limit: 20) ``` In this case, `{limit: 20}` will be passed as `options:` to `#initialize` and `options[:limit]` will be `20`. For example, options can be used for modifying execution: ```ruby def after_resolve(value:, **rest) # Apply the limit from the options, a readable attribute on the class value.limit(options[:limit]) end ``` graphql-ruby-1.11.10/guides/type_definitions/input_objects.md000066400000000000000000000111341414121453000243110ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Input Objects desc: Input objects are sets of key-value pairs which can be used as field arguments. index: 3 --- Input object types are complex inputs for GraphQL operations. They're great for fields that need a lot of structured input, like mutations or search fields. In a GraphQL request, it might look like this: ```ruby mutation { createPost(attributes: { title: "Hello World", fullText: "This is my first post", categories: [GENERAL] }) { # ^ Here is the input object ..................................................... ^ } } ``` Like a Ruby `Hash`, an input object consists of keys and values. Unlike a Hash, its keys and value types must be defined statically, as part of the GraphQL system. For example, here's an input object, expressed in the [GraphQL Schema Definition Language](https://graphql.org/learn/schema/#type-language) (SDL): ```ruby input PostAttributes { title: String! fullText: String! categories: [PostCategory!] } ``` This input object has three possible keys: - `title` is required (denoted by `!`), and must be a `String` - `fullText` is also a required String - `categories` is optional (it doesn't have `!`), and if present, it must be a list of `PostCategory` values. ## Defining Input Object Types Input object types extend {{ "GraphQL::Schema::InputObject" | api_doc }} and define key-value pairs with the `argument(...)` method. For example: ```ruby # app/graphql/types/base_input_object.rb # Add a base class class Types::BaseInputObject < GraphQL::Schema::InputObject end class Types::PostAttributes < Types::BaseInputObject description "Attributes for creating or updating a blog post" argument :title, String, "Header for the post", required: true argument :full_text, String, "Full body of the post", required: true argument :categories, [Types::PostCategory], required: false end ``` For a full description of the `argument(...)` method, see the {% internal_link "argument section of the Objects guide","/type_definitions/objects#field-arguments" %}. ## Using Input Objects Input objects are passed to field methods as an instance of their definition class. So, inside the field method, you can access any key of the object by: - calling its method, corresponding to the name (underscore-cased) - calling `#[]` with the _camel-cased_ name of the argument (this is for compatibility with previous GraphQL-Ruby versions) ```ruby # This field takes an argument called `attributes` # which will be an instance of `PostAttributes` field :create_post, Types::Post, null: false do argument :attributes, Types::PostAttributes, required: true end def create_post(attributes:) puts attributes.class.name # => "Types::PostAttributes" # Access a value by method (underscore-cased): puts attributes.full_text # => "This is my first post" # Or by hash-style lookup (camel-cased, for compatibility): puts attributes[:fullText] # => "This is my first post" end ``` ## Customizing Input Objects You can customize the `GraphQL::Schema::Argument` class which is used for input objects: ```ruby class Types::BaseArgument < GraphQL::Schema::Argument # your customization here ... end class Types::BaseInputObject < GraphQL::Schema::InputObject # Hook up the customized argument class argument_class(Types::BaseArgument) end ``` You can also add or override methods on input object classes to customize them. They have two instance variables by default: - `@arguments`: A {{ "GraphQL::Query::Arguments" | api_doc }} instance - `@context`: The current {{ "GraphQL::Query::Context" | api_doc }} Any extra methods you define on the class can be used for field resolution, as demonstrated above. ## Converting to Other Ruby Objects Your input objects can be automatically converted to other Ruby types before they're passed to your application code. This is an easy way to use `Range`'s in your schema: ```ruby class Types::DateRangeInput < Types::BaseInputObject description "Range of dates" argument :min, Types::Date, "Minimum value of the range", required: true argument :max, Types::Date, "Maximum value of the range", required: true def prepare min..max end end class Types::CalendarType < Types::BaseObject field :appointments, [Types::Appointment], "Appointments on your calendar", null: false do argument :during, Types::DateRangeInput, "Only show appointments within this range", required: true end def appointments(during:) # during will be an instance of Range object.appointments.select { |appointment| during.cover?(appointment.date) } end end ``` graphql-ruby-1.11.10/guides/type_definitions/interfaces.md000066400000000000000000000177321414121453000235760ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Interfaces desc: Interfaces are lists of fields which objects may implement index: 4 redirect_from: - /types/abstract_types/ --- Interfaces are lists of fields which may be implemented by object types. An interface has fields, but it's never actually instantiated. Instead, objects may _implement_ interfaces, which makes them a _member_ of that interface. Also, fields may _return_ interface types. When this happens, the returned object may be any member of that interface. For example, let's say a `Customer` (interface) may be either an `Individual` (object) or a `Company` (object). Here's the structure in the [GraphQL Schema Definition Language](https://graphql.org/learn/schema/#type-language) (SDL): ```graphql interface Customer { name: String! outstandingBalance: Int! } type Company implements Customer { employees: [Individual!]! name: String! outstandingBalance: Int! } type Individual implements Customer { company: Company name: String! outstandingBalance: Int! } ``` Notice that the `Customer` interface requires two fields, `name: String!` and `outstandingBalance: Int!`. Both `Company` and `Individual` implement those fields, so they can implement `Customer`. Their implementation of `Customer` is made explicit by `implements Customer` in their definition. When querying, you can get the fields on an interface: ```graphql { customers(first: 5) { name outstandingBalance } } ``` Whether the objects are `Company` or `Individual`, it doesn't matter -- you still get their `name` and `outstandingBalance`. If you want some object-specific fields, you can query them with an _inline fragment_, for example: ```graphql { customers(first: 5) { name ... on Individual { company { name } } } } ``` This means, "if the customer is an `Individual`, also get the customer's company name". Interfaces are a good choice whenever a set of objects are used interchangeably, and they have several significant fields in common. When they don't have fields in common, use a {% internal_link "Union", "/type_definitions/unions" %} instead. ## Defining Interface Types Interfaces are Ruby modules which include {{ "GraphQL::Schema::Interface" | api_doc }}. First, make a base module: ```ruby module Types::BaseInterface include GraphQL::Schema::Interface end ``` Then, include that into each interface: ```ruby module Types::RetailItem include Types::BaseInterface description "Something that can be bought" field :price, Types::Price, "How much this item costs", null: false def price # Optional: provide a special implementation of `price` here end # Optional, see below definition_methods do # Optional: if this method is defined, it overrides `Schema.resolve_type` def resolve_type(object, context) # ... end end end ``` Interface classes are never instantiated. At runtime, only their `.resolve_type` methods are called (if they're defined). ### Implementing Interfaces To define object types that implement this interface use the `implements` method: ```ruby class Types::Car < Types::BaseObject implements Types::RetailItem # ... additional fields end class Types::Purse < Types::BaseObject implements Types::RetailItem # ... additional fields end ``` Those object types will _inherit_ field definitions from those interfaces. If you add an object type which implements an interface, but that object type doesn't appear in your schema as a field return type, a union member, or a root type, then you need to add that object to the interfaces's `orphan_types`. ### Implementing Fields Interfaces may provide field implementations along with the signatures. For example: ```ruby field :price, Types::Price, "How much this item costs", null: false # Implement this field to return a `::Price` object def price ::Price.from_cents(@object.price_in_cents) end ``` This method will be called by objects who implement the interface. To override this implementation, object classes can override the `#price` method. Read more in the {% internal_link "Fields guide", "/fields/introduction" %}. ### Definition Methods You can use `definition_methods do ... end` to add helper methods to interface modules. By adding methods to `definition_methods`: - Those methods will be available as class methods in the interface itself - These class methods will _also_ be added to interfaces that `include` this interface. This way, class methods are inherited when interfaces `include` other interfaces. (`definition_methods` is like `ActiveSupport::Concern`'s `class_methods` in this regard, but it has a different name to avoid naming conflicts). For example, you can add definition helpers to your base interface, then use them in concrete interfaces later: ```ruby # First, add a helper method to `BaseInterface`'s definition methods module Types::BaseInterface include GraphQL::Schema::Interface definition_methods do # Use this to add a price field + default implementation def price_field field(:price, ::Types::Price, null: false) define_method(:price) do ::Price.from_cents(@object.price_in_cents) end end end end # Then call it later module Types::ForSale include Types::BaseInterface # This calls `price_field` from definition methods price_field end ``` The type definition DSL uses this mechanism, too, so you can override those methods here also. ### Resolve Type When a field's return type is an interface, GraphQL has to figure out what _specific_ object type to use for the return value. In the example above, each `customer` must be categorized as an `Individual` or `Company`. You can do this by: - Providing a top-level `Schema.resolve_type` method; _OR_ - Providing an interface-level `.resolve_type` method in `definition_methods`. This method will be called whenever an object must be disambiguated. For example: ```ruby module Types::RetailItem include Types::BaseInterface definition_methods do # Determine what object type to use for `object` def resolve_type(object, context) if object.is_a?(::Car) || object.is_a?(::Truck) Types::Car elsif object.is_a?(::Purse) Types::Purse else raise "Unexpected RetailItem: #{object.inspect}" end end end end ``` You can also optionally return a "resolved" object in addition the resolved type by returning an array: ```ruby module Types::Claim include Types::BaseInterface definition_methods do def resolve_type(object, context) type = case object.value when Success Types::Approved when Error Types::Rejected else raise "Unexpected Claim: #{object.inspect}" end [type, object.value] end end end ``` The returned array must be a tuple of `[Type, object]`. This is useful for interface or union types which are backed by a domain object which should be unwrapped before resolving the next field. ## Orphan Types If you add an object type which implements an interface, but that object type doesn't properly appear in your schema, then you need to add that object to the interfaces's `orphan_types`, for example: ```ruby module Types::RetailItem include Types::BaseInterface # ... orphan_types Types::Comment end ``` Alternatively you can add the object types to the schema's `orphan_types`: ```ruby class MySchema < GraphQL::Schema orphan_types Types::Comment end ``` This is required because a schema finds it types by traversing its fields, starting with `query`, `mutation` and `subscription`. If an object is _never_ the return type of a field, but only connected via an interface, then it must be explicitly connected to the schema via `orphan_types`. For example, given this schema: ```graphql type Query { node(id: ID!): Node } interface Node { id: ID! } type Comment implements Node { id: ID! } ``` `Comment` must be added via `orphan_types` since it's never used as the return type of a field. (Only `Node` and `ID` are used as return types.) graphql-ruby-1.11.10/guides/type_definitions/lists.md000066400000000000000000000111541414121453000226010ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Lists desc: Ordered lists containing other types index: 6 --- GraphQL has _list types_ which are ordered lists containing items of other types. The following examples use the [GraphQL Schema Definition Language](https://graphql.org/learn/schema/#type-language) (SDL). Fields may return a single scalar value (eg `String`), or a _list_ of scalar values (eg, `[String]`, a list of strings): ```ruby type Spy { # This spy's real name realName: String! # Any other names that this spy goes by aliases: [String!] } ``` Fields may also return lists of other types as well: ```ruby enum PostCategory { SOFTWARE UPHOLSTERY MAGIC_THE_GATHERING } type BlogPost { # Zero or more categories this post belongs to categories: [PostCategory!] # Other posts related to this one relatedPosts: [BlogPost!] } ``` Inputs may also be lists. Arguments can accept list types, for example: ```ruby type Query { # Return the latest posts, filtered by `categories` posts(categories: [PostCategory!]): [BlogPost!] } ``` When GraphQL is sent and received with JSON, GraphQL lists are expressed in JSON arrays. ## List Types in Ruby To define a list type in Ruby use `[...]` (a Ruby array with one member, the inner type). For example: ```ruby # A field returning a list type: # Equivalent to `aliases: [String!]` above field :aliases, [String], null: true # An argument which accepts a list type: argument :categories, [Types::PostCategory], required: false ``` For input, GraphQL lists are converted to Ruby arrays. For fields that return list types, any object responding to `#each` may be returned. It will be enumerated as a GraphQL list. To define lists where `nil` may be a member of the list, use `null: true` in the definition array, for example: ```ruby # Equivalent to `previousEmployers: [Employer]!` field :previous_employers, [Types::Employer, null: true], "Previous employers; `null` represents a period of self-employment or unemployment" null: false ``` ## Lists, Nullable Lists, and Lists of Nulls Combining list types and non-null types can be a bit tricky. There are four possible combinations, based on two parameters: - Nullability of the field: can this field return `null`, or does it always return a list? - Nullability of the list items: when a list is present, may it include `null`? Here's how those combinations play out:   | nullable field | non-null field nullable items | [Integer, null: true], null: true
# => [Int] | [Integer, null: true], null: false
# => [Int]! non-null items | [Integer], null: true
# => [Int!] | [Integer], null: false
# => [Int!]! (The first line is GraphQL-Ruby code. The second line, beginning with `# =>`, is the corresponding GraphQL SDL code.) Let's look at some examples. #### Non-null lists with non-null items Here's an example field: ```ruby field :scores, [Integer], null: false # In GraphQL, # scores: [Int!]! ``` In this example, `scores` may not return `null`. It must _always_ return a list. Additionally, the list may _never_ contain `null` -- it may only contain `Int`s. (It may be empty, but it cannot have `null` in it.) Here are values the field may return: Valid | Invalid ------|------ `[]` | `null` `[1, 2, ...]`| `[null]` | | `[1, null, 2, ...]` ### Non-null lists with nullable items Here's an example field: ```ruby field :scores, [Integer, null: true], null: false # In GraphQL, # scores: [Int]! ``` In this example, `scores` may not return `null`. It must _always_ return a list. However, the list _may_ contain `null`s and/or `Int`s. Here are values the field may return: Valid | Invalid ------|------ `[]` | `null` `[1, 2, ...]`| `[null]` | `[1, null, 2, ...]` | ### Nullable lists with nullable items Here's an example field: ```ruby field :scores, [Integer, null: true], null: true # In GraphQL, # scores: [Int] ``` In this example, `scores` return `null` _or_ a list. Additionally, the list _may_ contain `null`s and/or `Int`s. Here are values the field may return: Valid | Invalid ------|------ `null` | `[]` | `[1, 2, ...]`| `[null]` | `[1, null, 2, ...]` | ### Nullable lists with non-null items Here's an example field: ```ruby field :scores, [Integer], null: true # In GraphQL, # scores: [Int!] ``` In this example, `scores` return `null` _or_ a list. However, if a list is present, it may _not_ contain `null` -- only `Int`s. Here are values the field may return: Valid | Invalid ------|------ `null` | `[null]` `[]` | `[1, null, 2, ...]` `[1, 2, ...]` | graphql-ruby-1.11.10/guides/type_definitions/non_nulls.md000066400000000000000000000037521414121453000234570ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Non-Null Types desc: Values which must be present index: 7 --- GraphQL's concept of _non-null_ is expressed in the [Schema Definition Language](https://graphql.org/learn/schema/#type-language) (SDL) with `!`, for example: ```graphql type User { # This field _always_ returns a String, never returns `null` handle: String! # `since:` _must_ be passed a `DateTime` value, it can never be omitted or passed `null` followers(since: DateTime!): [User!]! } ``` In Ruby, this concept is expressed with `null:` for fields and `required:` for arguments. ## Non-null return types When `!` is used for field return types (like `handle: String!` above), it means that the field will _never_ (and may never) return `nil`. To make a field non-null in Ruby, use `null: false` in the field definition: ```ruby # equivalent to `handle: String!` above field :handle, String, null: false ``` This means that the field will _never_ be `nil` (and if it is, it will be removed from the response, as described below). ### Non-null error propagation If a non-null field ever returns `nil`, then the entire selection will be removed from the response and replaced with `nil`. If this removal would result in _another_ invalid `nil`, then it cascades upward, until it reaches the root `"data"` key. This is to support clients in strongly-typed languages. Any non-null field will _never_ return `null`, and client developers can depend on that. ## Non-null argument types When `!` is used for arguments (like `followers(since: DateTime!)` above), it means that the argument is _required_ for the query to execute. Any query which doesn't have a value for that argument will be rejected immediately. To make an argument non-null in Ruby, use `required: true`, for example: ```ruby # equivalent to `since: DateTime!` above argument :since, Types::DateTime, required: true ``` This means that any query _without_ a value for `since:` will be rejected. graphql-ruby-1.11.10/guides/type_definitions/objects.md000066400000000000000000000070771414121453000231050ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Objects desc: Objects expose data and link to other objects index: 0 --- GraphQL object types are the bread and butter of GraphQL APIs. Each object has _fields_ which expose data and may be queried by name. For example, we can query a `User` like this: ```ruby user { handle email } ``` And get back values like this: ```ruby { "user" => { "handle" => "rmosolgo", "email" => nil, } } ``` Generally speaking, GraphQL object types correspond to models in your application, like `User`, `Product`, or `Comment`. Sometimes, object types are described using the [GraphQL Schema Definition Language](https://graphql.org/learn/schema/#type-language) (SDL): ```ruby type User { email: String handle: String! friends: [User!]! } ``` This means that `User` objects have three fields: - `email`, which may return a `String` _or_ `nil`. - `handle`, which returns a `String` but _never_ `nil` (`!` means the field never returns `nil`) - `friends`, which returns a list of other `User`s (`[...]` means the field returns a list of values; `User!` means the list contains `User` objects, and never contains `nil`.) The same object can be defined using Ruby: ```ruby class Types::User < GraphQL::Schema::Object field :email, String, null: true field :handle, String, null: false field :friends, [User], null: false end ``` The rest of this guide will describe how to define GraphQL object types in Ruby. To learn more about GraphQL object types in general, see the [GraphQL docs](https://graphql.org/learn/schema/#object-types-and-fields). ## Object classes Classes extending {{ "GraphQL::Schema::Object" | api_doc }} describe [Object types](https://graphql.org/learn/schema/#object-types-and-fields) and customize their behavior. Object fields can be created with the `field(...)` class method, [described in detail below](#fields) Field and argument names should be underscored as a convention. They will be converted to camelCase in the underlying GraphQL type and be camelCase in the schema itself. ```ruby # first, somewhere, a base class: class Types::BaseObject < GraphQL::Schema::Object end # then... class Types::TodoList < Types::BaseObject description "A list of items which may be completed" field :name, String, "The unique name of this list", null: false field :is_completed, String, "Completed status depending on all tasks being done.", null: false # Related Object: field :owner, Types::User, "The creator of this list", null: false # List field: field :viewers, [Types::User], "Users who can see this list", null: false # Connection: field :items, Types::TodoItem.connection_type, "Tasks on this list", null: false do argument :status, TodoStatus, "Restrict items to this status", required: false end end ``` ## Fields Object fields expose data about that object or connect the object to other objects. You can add fields to your object types with the `field(...)` class method. See the {% internal_link "Fields guide", "/fields/introduction" %} for details about object fields. ## Implementing interfaces If an object implements any interfaces, they can be added with `implements`, for example: ```ruby # This object implements some interfaces: implements GraphQL::Relay::Node.interface implements Types::UserAssignableType ``` When an object `implements` interfaces, it: - inherits the GraphQL field definitions from that object - includes that module into the object definition Read more about interfaces in the {% internal_link "Interfaces guide", "/type_definitions/interfaces" %} graphql-ruby-1.11.10/guides/type_definitions/scalars.md000066400000000000000000000063571414121453000231040ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Scalars desc: Scalars are "simple" data types like integers and strings index: 1 --- Scalars are "leaf" values in GraphQL. There are several built-in scalars, and you can define custom scalars, too. ({% internal_link "Enums", "/type_definitions/enums" %} are also leaf values.) The built-in scalars are: - `String`, like a JSON or Ruby string - `Int`, like a JSON or Ruby integer - `Float`, like a JSON or Ruby floating point decimal - `Boolean`, like a JSON or Ruby boolean (`true` or `false`) - `ID`, which a specialized `String` for representing unique object identifiers - `ISO8601DateTime`, an ISO 8601-encoded datetime - `ISO8601Date`, an ISO 8601-encoded date - `JSON`, ⚠ This returns arbitrary JSON (Ruby hashes, arrays, strings, integers, floats, booleans and nils). Take care: by using this type, you completely lose all GraphQL type safety. Consider building object types for your data instead. - `BigInt`, a numeric value which may exceed the size of a 32-bit integer Fields can return built-in scalars by referencing them by name: ```ruby # String field: field :name, String, # Integer field: field :top_score, Int, null: false # or: field :top_score, Integer, null: false # Float field field :avg_points_per_game, Float, null: false # Boolean field field :is_top_ranked, Boolean, null: false # ID field field :id, ID, null: false # ISO8601DateTime field field :created_at, GraphQL::Types::ISO8601DateTime, null: false # ISO8601Date field field :birthday, GraphQL::Types::ISO8601Date, null: false # JSON field ⚠ field :parameters, GraphQL::Types::JSON, null: false # BigInt field field :sales, GraphQL::Types::BigInt, null: false ``` Custom scalars (see below) can also be used by name: ```ruby # `homepage: Url` field :homepage, Types::Url, null: true ``` In the [Schema Definition Language](https://graphql.org/learn/schema/#type-language) (SDL), scalars are simply named: ```ruby scalar DateTime ``` ## Custom Scalars You can implement your own scalars by extending {{ "GraphQL::Schema::Scalar" | api_doc }}. For example: ```ruby # app/graphql/types/base_scalar.rb # Make a base class: class Types::BaseScalar < GraphQL::Schema::Scalar end # app/graphql/types/url.rb class Types::Url < Types::BaseScalar description "A valid URL, transported as a string" def self.coerce_input(input_value, context) # Parse the incoming object into a `URI` url = URI.parse(input_value) if url.is_a?(URI::HTTP) || url.is_a?(URI::HTTPS) # It's valid, return the URI object url else raise GraphQL::CoercionError, "#{input_value.inspect} is not a valid URL" end end def self.coerce_result(ruby_value, context) # It's transported as a string, so stringify it ruby_value.to_s end end ``` Your class must define two class methods: - `self.coerce_input` takes a GraphQL input and converts it into a Ruby value - `self.coerce_result` takes the return value of a field and prepares it for the GraphQL response JSON When incoming data is incorrect, the method may raise {{ "GraphQL::CoercionError" | api_doc }}, which will be returned to the client in the `"errors"` key. Scalar classes are never initialized; only their `.coerce_*` methods are called at runtime. graphql-ruby-1.11.10/guides/type_definitions/unions.md000066400000000000000000000043021414121453000227530ustar00rootroot00000000000000--- layout: guide doc_stub: false search: true section: Type Definitions title: Unions desc: Unions are sets of types which may appear in the same place (but don't share fields). index: 5 --- A union type is a set of object types which may appear in the same spot. Here's a union, expressed in [GraphQL Schema Definition Language](https://graphql.org/learn/schema/#type-language) (SDL): ```ruby union MediaItem = AudioClip | VideoClip | Image | TextSnippet ``` This might be used on a search field, for example: ```ruby searchMedia(term: "puppies") { ... on AudioClip { duration } ... on VideoClip { previewURL resolution } ... on Image { thumbnailURL } ... on TextSnippet { teaserText } } ``` Here, the `searchMedia` field returns `[MediaItem!]`, a list where each member is part of the `MediaItem` union. So, for each member, we want to select different fields depending on which kind of object that member is. {% internal_link "Interfaces", "/type_definitions/interfaces" %} are a similar concept, but in an interface, all types must share some common fields. Unions are a good choice when the object types don't have any significant fields in common. Since union members share _no_ fields, selections are _always_ made with typed fragments (`... on SomeType`, as seen above). ## Defining Union Types Unions extend `GraphQL::Schema::Union`. First, make a base class: ```ruby class Types::BaseUnion < GraphQL::Schema::Union end ``` Then, extend that one for each union in your schema: ```ruby class Types::CommentSubject < Types::BaseUnion description "Objects which may be commented on" possible_types Types::Post, Types::Image # Optional: if this method is defined, it will override `Schema.resolve_type` def self.resolve_type(object, context) if object.is_a?(BlogPost) Types::Post else Types::Image end end end ``` The `possible_types(*types)` method accepts one or more types which belong to this union. Union classes are never instantiated; At runtime, only their `.resolve_type` methods may be called (if defined). For information about `.resolve_type`, see the {% internal_link "Interfaces guide", "/type_definitions/interfaces#resolve-type" %}. graphql-ruby-1.11.10/javascript_client/000077500000000000000000000000001414121453000177675ustar00rootroot00000000000000graphql-ruby-1.11.10/javascript_client/.npmignore000066400000000000000000000001171414121453000217650ustar00rootroot00000000000000node_modules/ OperationStoreClient.js yarn.lock src/ **/__tests__/* coverage/* graphql-ruby-1.11.10/javascript_client/CHANGELOG.md000066400000000000000000000066771414121453000216200ustar00rootroot00000000000000# graphql-ruby-client ## 1.8.1 - Sync: When `--url` is omitted, generate an outfile without syncing with a server ## 1.8.0 - Ably: Support server-side `cipher_base:` config in the client ## 1.7.12 - Ably: Add `rewind:` config so messages aren't lost between subscribe and registration of listener. #3210 - Ably: Fix race condition where error was raised before the channel was available. #3210 ## 1.7.11 (15 June 2020) - Ably: Improve channel state handling in case the initial subscription result contains errors #2993 ## 1.7.10 (13 June 2020) - Ably: Improve error handling and channel cleanup #2991 ## 1.7.9 (15 May 2020) - Ably: _completely_ unsubscribe when subscriptions are done #2944 - Ably: propagate errors from subscriptions #2944 ## 1.7.8 (1 May 2020) - `sync`: Add support for Apollo-Android's `OperationOutput.json` #2914 ## 1.7.7 (15 Apr 2020) - Ably handler: dispatch initial response #2866 - Ably handler: catch any error in initial HTTP call #2877 ## 1.7.6 (3 Apr 2020) - Fix ActionCableLink sending unsubcribe to ActionCable #2842 ## 1.7.5 (4 Mar 2020) - Add missing dependency declarations ## 1.7.4 (18 Feb 2020) - Move all exports to top level - Fix sync body handling: wait for all chunks, improve verbose output ## 1.7.3 (17 Feb 2020) - Fix CLI for TypeScript ## 1.7.2 (17 Feb 2020) - Convert outfile generators to TypeScript and include them in published package ## 1.7.1 (17 Feb 2020) - Fix `bin` configuration in package.json ## 1.7.0 (17 Feb 2020) - Rewrite in TypeScript ## 1.6.8 (18 Sept 2019) - Properly send `Content-Type: application/json` when posting persisted operations ## 1.6.7 (18 Sept 2019) - Add post data to `--verbose` output of `sync` ## 1.6.6 (6 Aug 2019) - Add `--relay-persisted-output` for working with Relay Compiler's new `--persist-output` option #2415 ## 1.6.5 (17 July 2019) - Update dependencies #2335 ## 1.6.4 (11 May 2019) - Add `--verbose` option to `sync` #2075 - Support Relay 2.0.0 #2121 - ActionCableLink: support subscriber when there are errors but no data #2176 ## 1.6.3 (11 Jan 2019) - Fix `.unsubscribe()` for PusherLink #2042 ## 1.6.2 (14 Dec 2018) - Support identified Ably client #2003 ## 1.6.1 (30 Nov 2018) - Support `ably:` option for Relay subscriptions ## 1.6.0 (19 Nov 2018) - Fix unused requires #1943 - Add `generateClient` function to generate code _without_ the HTTP call #1941 ## 1.5.0 (27 October 2018) - Fix `export` usage in PusherLink, use `require` and `module.exports` instead #1889 - Add `AblyLink` #1925 ## 1.4.1 (19 Sept 2018) - Add `connectionOptions` to ActionCableLink #1857 ## 1.4.0 (12 Apr 2018) - Add `PusherLink` for Apollo 2 Subscriptions on Pusher - Add `OperationStoreLink` for Apollo 2 persisted queries ## 1.3.0 (30 Nov 2017) - Support HTTPS, basic auth, query string and port in `sync` #1053 - Add Apollo 2 support for ActionCable subscriptions #1120 - Add `--outfile-type=json` for stored operation manifest #1142 ## 1.2.0 (15 Nov 2017) - Support Apollo batching middleware #1092 ## 1.1.3 (11 Oct 2017) - Fix Apollo + ActionCable unsubscribe function #1019 ## 1.1.2 (9 Oct 2017) - Add channel IDs to ActionCable subscriptions #1004 ## 1.1.1 (21 Sept 2017) - Add `--add-typename` option to `sync` #967 ## 1.1.0 (18 Sept 2017) - Add subscription clients for Apollo and Relay Modern ## 1.0.2 (22 Aug 2017) - Remove debug output ## 1.0.1 (21 Aug 2017) - Rename from `graphql-pro-js` to `graphql-ruby-client` ## 1.0.0 (31 Jul 2017) - Add `sync` task graphql-ruby-1.11.10/javascript_client/LICENSE.md000066400000000000000000000007341414121453000213770ustar00rootroot00000000000000Copyright (c) Minimum Viable Software `graphql-ruby-client` is an Open Source project licensed under the terms of the LGPLv3 license. Please see https://www.gnu.org/licenses/lgpl-3.0.html for license text. `GraphQL::Pro` customers are granted a commercial-friendly license allowing private forks and modifications of `graphql-ruby-client`. Please see https://graphql.pro/ for more detail. You can find the commercial license terms at https://graphql.pro/COMM-LICENSE.html. graphql-ruby-1.11.10/javascript_client/jest.config.js000066400000000000000000000002511414121453000225340ustar00rootroot00000000000000module.exports = { roots: [ "/src" ], verbose: true, testMatch: [ "**/__tests__/**/*.ts", ], transform: { "^.+\\.ts$": "ts-jest" }, } graphql-ruby-1.11.10/javascript_client/package.json000066400000000000000000000017721414121453000222640ustar00rootroot00000000000000{ "name": "graphql-ruby-client", "version": "1.8.1", "description": "JavaScript client for graphql-ruby", "main": "dist/index.js", "types": "dist/index.d.ts", "repository": "https://github.com/rmosolgo/graphql-ruby", "author": "Robert Mosolgo", "license": "LGPL-3.0", "bin": "./dist/cli.js", "devDependencies": { "@types/glob": "^7.1.1", "@types/jest": "^25.1.2", "@types/minimist": "^1.2.0", "@types/node": "^13.7.1", "ably": "^1.2.4", "jest": "^25.0.0", "nock": "^11.0.0", "ts-jest": "^25.2.0", "prettier": "^1.19.1" }, "scripts": { "test": "tsc && jest", "prepublishOnly": "tsc" }, "dependencies": { "@types/ably": "^1.0.0", "@types/actioncable": "^5.2.3", "@types/pusher-js": "^4.2.2", "apollo-link": "^1.2.12", "glob": "^7.1.4", "graphql": "^14.3.1", "minimist": "^1.2.0", "typescript": "^3.7.5" }, "prettier": { "semi": false, "trailingComma": "none" }, "engines": { "node": ">=10" } } graphql-ruby-1.11.10/javascript_client/readme.md000066400000000000000000000011211414121453000215410ustar00rootroot00000000000000Find the `graphql-ruby-client` docs on the [GraphQL-Ruby website](https://graphql-ruby.org/javascript_client/overview). ## License `graphql-ruby-client` is available under the LGPLv3 license; [graphql-pro](https://graphql.pro) customers are granted a special commercial license. ## Development - With GraphQL-Ruby: - Install the dependencies with `rake js:install` - Run the tests with `rake js:test` - Stand-alone: - Install dependencies `yarn install` - Run the tests `yarn run test` - Run the TypeScript compiler: `yarn tsc -w` - Install for local development with `npm link .` graphql-ruby-1.11.10/javascript_client/src/000077500000000000000000000000001414121453000205565ustar00rootroot00000000000000graphql-ruby-1.11.10/javascript_client/src/__generated__/000077500000000000000000000000001414121453000233105ustar00rootroot00000000000000graphql-ruby-1.11.10/javascript_client/src/__generated__/Card_card.graphql.js000066400000000000000000000014471414121453000271530ustar00rootroot00000000000000/** * @flow */ /* eslint-disable */ 'use strict'; /*:: import type {ConcreteFragment} from 'relay-runtime'; export type Card_card = {| +id: string; +name: string; +imageUrl: string; |}; */ const fragment /*: ConcreteFragment*/ = { "argumentDefinitions": [], "kind": "Fragment", "metadata": null, "name": "Card_card", "selections": [ { "kind": "ScalarField", "alias": null, "args": null, "name": "id", "storageKey": null }, { "kind": "ScalarField", "alias": null, "args": null, "name": "name", "storageKey": null }, { "kind": "ScalarField", "alias": "imageUrl", "args": null, "name": "image_url", "storageKey": null } ], "type": "Card" }; module.exports = fragment; graphql-ruby-1.11.10/javascript_client/src/__generated__/GetStuff.graphql.js000066400000000000000000000113141414121453000270320ustar00rootroot00000000000000/** * This file was generated by: * relay-compiler * * @providesModule AppFeedQuery.graphql * @generated SignedSource<> * @relayHash 353e010cb78d082b29cb63ee7e9027b3 * @flow * @nogrep */ 'use strict'; /*:: import type {ConcreteBatch} from 'relay-runtime'; */ /* eslint-disable comma-dangle, quotes */ /* query AppFeedQuery { feed(type: NEW, limit: 5) { ...Feed } } fragment Feed on Entry { repository { owner { login } name } ...FeedEntry } fragment FeedEntry on Entry { repository { owner { login } name stargazers_count } postedBy { login } } */ const batch /*: ConcreteBatch*/ = { "fragment": { "argumentDefinitions": [], "kind": "Fragment", "metadata": null, "name": "AppFeedQuery", "selections": [ { "kind": "LinkedField", "alias": null, "args": [ { "kind": "Literal", "name": "limit", "value": 5, "type": "Int" }, { "kind": "Literal", "name": "type", "value": "NEW", "type": "FeedType!" } ], "concreteType": "Entry", "name": "feed", "plural": true, "selections": [ { "kind": "FragmentSpread", "name": "Feed", "args": null } ], "storageKey": "feed{\"limit\":5,\"type\":\"NEW\"}" } ], "type": "Query" }, "id": null, "kind": "Batch", "metadata": {}, "name": "AppFeedQuery", "query": { "argumentDefinitions": [], "kind": "Root", "name": "AppFeedQuery", "operation": "query", "selections": [ { "kind": "LinkedField", "alias": null, "args": [ { "kind": "Literal", "name": "limit", "value": 5, "type": "Int" }, { "kind": "Literal", "name": "type", "value": "NEW", "type": "FeedType!" } ], "concreteType": "Entry", "name": "feed", "plural": true, "selections": [ { "kind": "InlineFragment", "type": "Entry", "selections": [ { "kind": "LinkedField", "alias": null, "args": null, "concreteType": "Repository", "name": "repository", "plural": false, "selections": [ { "kind": "LinkedField", "alias": null, "args": null, "concreteType": "User", "name": "owner", "plural": false, "selections": [ { "kind": "ScalarField", "alias": null, "args": null, "name": "login", "storageKey": null } ], "storageKey": null }, { "kind": "ScalarField", "alias": null, "args": null, "name": "name", "storageKey": null }, { "kind": "ScalarField", "alias": null, "args": null, "name": "stargazers_count", "storageKey": null } ], "storageKey": null }, { "kind": "LinkedField", "alias": null, "args": null, "concreteType": "User", "name": "postedBy", "plural": false, "selections": [ { "kind": "ScalarField", "alias": null, "args": null, "name": "login", "storageKey": null } ], "storageKey": null } ] } ], "storageKey": "feed{\"limit\":5,\"type\":\"NEW\"}" } ] }, "text": "query AppFeedQuery {\n feed(type: NEW, limit: 5) {\n ...Feed\n }\n}\n\nfragment Feed on Entry {\n repository {\n owner {\n login\n }\n name\n }\n ...FeedEntry\n}\n\nfragment FeedEntry on Entry {\n repository {\n owner {\n login\n }\n name\n stargazers_count\n }\n postedBy {\n login\n }\n}\n" }; module.exports = batch; graphql-ruby-1.11.10/javascript_client/src/__tests__/000077500000000000000000000000001414121453000225145ustar00rootroot00000000000000graphql-ruby-1.11.10/javascript_client/src/__tests__/__snapshots__/000077500000000000000000000000001414121453000253325ustar00rootroot00000000000000graphql-ruby-1.11.10/javascript_client/src/__tests__/__snapshots__/syncTest.ts.snap000066400000000000000000000346501414121453000304660ustar00rootroot00000000000000// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`sync operations Input files Merges fragments and operations across files 1`] = ` Array [ Object { "alias": "5f0da489cf508a7c65ff5fa144e50545", "body": "fragment Frag1 on Query { moreStuff } query GetStuff { ...Frag1 } ", "name": "GetStuff", }, Object { "alias": "c944b08d15eb94cf93dd124b7d664b62", "body": "fragment Frag1 on Query { moreStuff } fragment Frag2 on Query { ...Frag3 } fragment Frag3 on Query { evenMoreStuff } query GetStuff2 { stuff ...Frag1 ...Frag2 } ", "name": "GetStuff2", }, Object { "alias": "fc215a466a3ea309fe493e8f7cb206f2", "body": "fragment Frag2 on Query { ...Frag3 } fragment Frag3 on Query { evenMoreStuff } fragment Frag4 on Query { evenMoreStuff { stuffInside } } query GetStuff3 { stuff { withStuffInside } ...Frag2 ...Frag4 } ", "name": "GetStuff3", }, Object { "alias": "919af8f80d8848a9b9b6176e457f8e45", "body": "query GetStuffIsolated { ...FragIsolated things { existHere } } fragment FragIsolated on Query { evenMoreStuff { stuffInside } } ", "name": "GetStuffIsolated", }, Object { "alias": "d5dfe825034aeda06e2639935d8b107d", "body": "query GetStuffIsolated2 { things { existHere } } ", "name": "GetStuffIsolated2", }, ] `; exports[`sync operations Input files Uses mode: file to process each file separately 1`] = ` Array [ Object { "alias": "26c44bfe42872860da112b6177355bfa", "body": "fragment Frag1 on Query { moreStuff } ", "name": "", }, Object { "alias": "7de9e7bf1d6ea1f527de07a25983086c", "body": "fragment Frag2 on Query { ...Frag3 } ", "name": "", }, Object { "alias": "8bc9b9922a7fbb66f4ac6c58d5d5c357", "body": "fragment Frag3 on Query { evenMoreStuff } ", "name": "", }, Object { "alias": "bfc521a5ade5ff6f17bdf9352c86c850", "body": "fragment Frag4 on Query { evenMoreStuff { stuffInside } } ", "name": "", }, Object { "alias": "0a6add7303775e2487f2c2235ecb1c80", "body": "query GetStuff { ...Frag1 } ", "name": "GetStuff", }, Object { "alias": "2f26b770ded2a04279bc4bf824ca54ac", "body": "query GetStuff2 { stuff ...Frag1 ...Frag2 } ", "name": "GetStuff2", }, Object { "alias": "1b8da77aabef67ee54df7e5acfd75893", "body": "query GetStuff3 { stuff { withStuffInside } ...Frag2 ...Frag4 } ", "name": "GetStuff3", }, Object { "alias": "919af8f80d8848a9b9b6176e457f8e45", "body": "query GetStuffIsolated { ...FragIsolated things { existHere } } fragment FragIsolated on Query { evenMoreStuff { stuffInside } } ", "name": "GetStuffIsolated", }, Object { "alias": "d5dfe825034aeda06e2639935d8b107d", "body": "query GetStuffIsolated2 { things { existHere } } ", "name": "GetStuffIsolated2", }, ] `; exports[`sync operations Logging Can be quieted with quiet: true 1`] = `Array []`; exports[`sync operations Logging Logs progress 1`] = ` Array [ Array [ "Syncing 5 operations to bogus...", ], Array [ "Generating client module in src/OperationStoreClient.js...", ], Array [ "✓ Done!", ], ] `; exports[`sync operations Printing the result prints failure and sends the message to the promise 1`] = ` Array [ Array [ "Syncing 5 operations to http://example.com/stored_operations/sync...", ], Array [ " 0 added", ], Array [ " 0 not modified", ], Array [ " 1 failed", ], ] `; exports[`sync operations Printing the result prints failure and sends the message to the promise 2`] = ` Array [ Array [ "Sync failed, errors:", ], Array [ " GetStuff:", ], Array [ " ✘ something", ], ] `; exports[`sync operations Printing the result prints success 1`] = ` Array [ Array [ "Syncing 5 operations to http://example.com/stored_operations/sync...", ], ] `; exports[`sync operations Printing the result prints success 2`] = ` Array [ Array [ " 1 added", ], Array [ " 2 not modified", ], Array [ " 0 failed", ], Array [ "Generating client module in src/OperationStoreClient.js...", ], Array [ "✓ Done!", ], ] `; exports[`sync operations Printing the result prints success 3`] = `Array []`; exports[`sync operations Relay support Uses Apollo Android OperationOutput JSON files 1`] = ` Array [ Object { "alias": "aba626ea9bdf465954e89e5590eb2c1a", "body": "mutation RemoveTodoMutation( $input: RemoveTodoInput! ) { removeTodo(input: $input) { deletedTodoId user { completedCount totalCount id } } } ", }, Object { "alias": "67c2bc8aa3185a209d6651b4feb63c04", "body": "query appQuery( $userId: String ) { user(id: $userId) { ...TodoApp_user id } } fragment TodoApp_user on User { id userId totalCount ...TodoListFooter_user ...TodoList_user } fragment TodoListFooter_user on User { id userId completedCount todos(first: 2147483647) { edges { node { id complete __typename } cursor } pageInfo { endCursor hasNextPage } } totalCount } fragment TodoList_user on User { todos(first: 2147483647) { edges { node { id complete ...Todo_todo __typename } cursor } pageInfo { endCursor hasNextPage } } id userId totalCount completedCount ...Todo_user } fragment Todo_todo on Todo { complete id text } fragment Todo_user on User { id userId totalCount completedCount } ", }, Object { "alias": "db9904c31d91416f21d45fe3d153884c", "body": "mutation MarkAllTodosMutation( $input: MarkAllTodosInput! ) { markAllTodos(input: $input) { changedTodos { id complete } user { id completedCount } } } ", }, Object { "alias": "2eb8c9941fdb3117fdbc08d15fab62d0", "body": "mutation AddTodoMutation( $input: AddTodoInput! ) { addTodo(input: $input) { todoEdge { __typename cursor node { complete id text } } user { id totalCount } } } ", }, Object { "alias": "d970fd7dbf118794415dec7324d463e3", "body": "mutation RenameTodoMutation( $input: RenameTodoInput! ) { renameTodo(input: $input) { todo { id text } } } ", }, Object { "alias": "a49217db31a8be3f4107763b957d5fca", "body": "mutation RemoveCompletedTodosMutation( $input: RemoveCompletedTodosInput! ) { removeCompletedTodos(input: $input) { deletedTodoIds user { completedCount totalCount id } } } ", }, Object { "alias": "d7dda774dcfa32fe0d9661e01cac9a4a", "body": "mutation ChangeTodoStatusMutation( $input: ChangeTodoStatusInput! ) { changeTodoStatus(input: $input) { todo { id complete } user { id completedCount } } } ", }, ] `; exports[`sync operations Relay support Uses Relay generated .js files 1`] = ` Array [ Object { "alias": "353e010cb78d082b29cb63ee7e9027b3", "body": "query AppFeedQuery { feed(type: NEW, limit: 5) { ...Feed } } fragment Feed on Entry { repository { owner { login } name } ...FeedEntry } fragment FeedEntry on Entry { repository { owner { login } name stargazers_count } postedBy { login } } ", "name": "AppFeedQuery", }, ] `; exports[`sync operations Relay support Uses relay --persist-output JSON files 1`] = ` Array [ Object { "alias": "aba626ea9bdf465954e89e5590eb2c1a", "body": "mutation RemoveTodoMutation( $input: RemoveTodoInput! ) { removeTodo(input: $input) { deletedTodoId user { completedCount totalCount id } } } ", }, Object { "alias": "67c2bc8aa3185a209d6651b4feb63c04", "body": "query appQuery( $userId: String ) { user(id: $userId) { ...TodoApp_user id } } fragment TodoApp_user on User { id userId totalCount ...TodoListFooter_user ...TodoList_user } fragment TodoListFooter_user on User { id userId completedCount todos(first: 2147483647) { edges { node { id complete __typename } cursor } pageInfo { endCursor hasNextPage } } totalCount } fragment TodoList_user on User { todos(first: 2147483647) { edges { node { id complete ...Todo_todo __typename } cursor } pageInfo { endCursor hasNextPage } } id userId totalCount completedCount ...Todo_user } fragment Todo_todo on Todo { complete id text } fragment Todo_user on User { id userId totalCount completedCount } ", }, Object { "alias": "db9904c31d91416f21d45fe3d153884c", "body": "mutation MarkAllTodosMutation( $input: MarkAllTodosInput! ) { markAllTodos(input: $input) { changedTodos { id complete } user { id completedCount } } } ", }, Object { "alias": "2eb8c9941fdb3117fdbc08d15fab62d0", "body": "mutation AddTodoMutation( $input: AddTodoInput! ) { addTodo(input: $input) { todoEdge { __typename cursor node { complete id text } } user { id totalCount } } } ", }, Object { "alias": "d970fd7dbf118794415dec7324d463e3", "body": "mutation RenameTodoMutation( $input: RenameTodoInput! ) { renameTodo(input: $input) { todo { id text } } } ", }, Object { "alias": "a49217db31a8be3f4107763b957d5fca", "body": "mutation RemoveCompletedTodosMutation( $input: RemoveCompletedTodosInput! ) { removeCompletedTodos(input: $input) { deletedTodoIds user { completedCount totalCount id } } } ", }, Object { "alias": "d7dda774dcfa32fe0d9661e01cac9a4a", "body": "mutation ChangeTodoStatusMutation( $input: ChangeTodoStatusInput! ) { changeTodoStatus(input: $input) { todo { id complete } user { id completedCount } } } ", }, ] `; exports[`sync operations custom file processing options Adds .graphql to the glob if needed 1`] = ` Array [ Object { "alias": "f7f65309043352183e905e1396e51078", "body": "query GetStuff { stuff } ", "name": "GetStuff", }, ] `; exports[`sync operations custom file processing options Adds .graphql to the glob if needed 2`] = ` Array [ Object { "alias": "f7f65309043352183e905e1396e51078", "body": "query GetStuff { stuff } ", "name": "GetStuff", }, ] `; exports[`sync operations custom file processing options Uses a custom hash function if provided 1`] = ` Array [ Object { "alias": "GETSTUFF", "body": "query GetStuff { stuff } ", "name": "GetStuff", }, ] `; exports[`sync operations generating artifacts without syncing works without a URL 1`] = ` " /** * Generated by graphql-ruby-client * */ /** * Map local operation names to persisted keys on the server * @return {Object} * @private */ var _aliases = { \\"GetStuff\\": \\"f7f65309043352183e905e1396e51078\\" } /** * The client who synced these operations with the server * @return {String} * @private */ var _client = \\"test-1\\" var OperationStoreClient = { /** * Build a string for \`params[:operationId]\` * @param {String} operationName * @return {String} stored operation ID */ getOperationId: function(operationName) { return _client + \\"/\\" + OperationStoreClient.getPersistedQueryAlias(operationName) }, /** * Fetch a persisted alias from a local operation name * @param {String} operationName * @return {String} persisted alias */ getPersistedQueryAlias: function(operationName) { var persistedAlias = _aliases[operationName] if (!persistedAlias) { throw new Error(\\"Failed to find persisted alias for operation name: \\" + operationName) } else { return persistedAlias } }, /** * Satisfy the Apollo Link API. * This link checks for an operation name, and if it's present, * sets the HTTP context to _not_ include the query, * and instead, include \`extensions.operationId\`. * (This is inspired by apollo-link-persisted-queries.) */ apolloLink: function(operation, forward) { if (operation.operationName) { const operationId = OperationStoreClient.getOperationId(operation.operationName) operation.setContext({ http: { includeQuery: false, includeExtensions: true, } }) operation.extensions.operationId = operationId } return forward(operation) }, /** * Satisfy the Apollo middleware API. * Replace the query with an operationId */ apolloMiddleware: { applyBatchMiddleware: function(options, next) { options.requests.forEach(function(req) { // Fetch the persisted alias for this operation req.operationId = OperationStoreClient.getOperationId(req.operationName) // Remove the now-unused query string delete req.query return req }) // Continue the request next() }, applyMiddleware: function(options, next) { var req = options.request // Fetch the persisted alias for this operation req.operationId = OperationStoreClient.getOperationId(req.operationName) // Remove the now-unused query string delete req.query // Continue the request next() } } } module.exports = OperationStoreClient " `; exports[`sync operations verbose Adds debug output 1`] = ` Array [ Array [ "[Sync] glob: ", "./src/__tests__/documents**/*.graphql*", ], Array [ "[Sync] 1 files:", ], Array [ "[Sync] - ./src/__tests__/documents/doc1.graphql", ], Array [ "Syncing 1 operations to bogus...", ], Array [ "Verbose!", ], Array [ "Generating client module in src/OperationStoreClient.js...", ], Array [ "✓ Done!", ], ] `; graphql-ruby-1.11.10/javascript_client/src/__tests__/cliTest.ts000066400000000000000000000005561414121453000245010ustar00rootroot00000000000000var childProcess = require("child_process") describe("CLI", () => { it("exits 1 on error", () => { expect(() => { childProcess.execSync("node ./dist/cli.js sync", {stdio: "pipe"}) }).toThrow("Client name must be provided for sync") }) it("exits 0 on OK", () => { childProcess.execSync("node ./dist/cli.js sync -h", {stdio: "pipe"}) }) }) graphql-ruby-1.11.10/javascript_client/src/__tests__/documents/000077500000000000000000000000001414121453000245155ustar00rootroot00000000000000graphql-ruby-1.11.10/javascript_client/src/__tests__/documents/doc1.graphql000066400000000000000000000000331414121453000267170ustar00rootroot00000000000000query GetStuff { stuff } graphql-ruby-1.11.10/javascript_client/src/__tests__/example-apollo-android-operation-output.json000066400000000000000000000055271414121453000333110ustar00rootroot00000000000000{ "aba626ea9bdf465954e89e5590eb2c1a": { "name": "RemoveTodoMutation", "source": "mutation RemoveTodoMutation(\n $input: RemoveTodoInput!\n) {\n removeTodo(input: $input) {\n deletedTodoId\n user {\n completedCount\n totalCount\n id\n }\n }\n}\n" }, "67c2bc8aa3185a209d6651b4feb63c04": { "name": "appQuery", "source": "query appQuery(\n $userId: String\n) {\n user(id: $userId) {\n ...TodoApp_user\n id\n }\n}\n\nfragment TodoApp_user on User {\n id\n userId\n totalCount\n ...TodoListFooter_user\n ...TodoList_user\n}\n\nfragment TodoListFooter_user on User {\n id\n userId\n completedCount\n todos(first: 2147483647) {\n edges {\n node {\n id\n complete\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n totalCount\n}\n\nfragment TodoList_user on User {\n todos(first: 2147483647) {\n edges {\n node {\n id\n complete\n ...Todo_todo\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n userId\n totalCount\n completedCount\n ...Todo_user\n}\n\nfragment Todo_todo on Todo {\n complete\n id\n text\n}\n\nfragment Todo_user on User {\n id\n userId\n totalCount\n completedCount\n}\n" }, "db9904c31d91416f21d45fe3d153884c": { "name": "MarkAllTodosMutation", "source": "mutation MarkAllTodosMutation(\n $input: MarkAllTodosInput!\n) {\n markAllTodos(input: $input) {\n changedTodos {\n id\n complete\n }\n user {\n id\n completedCount\n }\n }\n}\n" }, "2eb8c9941fdb3117fdbc08d15fab62d0": { "name": "AddTodoMutation", "source": "mutation AddTodoMutation(\n $input: AddTodoInput!\n) {\n addTodo(input: $input) {\n todoEdge {\n __typename\n cursor\n node {\n complete\n id\n text\n }\n }\n user {\n id\n totalCount\n }\n }\n}\n" }, "d970fd7dbf118794415dec7324d463e3": { "name": "RenameTodoMutation", "source": "mutation RenameTodoMutation(\n $input: RenameTodoInput!\n) {\n renameTodo(input: $input) {\n todo {\n id\n text\n }\n }\n}\n" }, "a49217db31a8be3f4107763b957d5fca": { "name": "RemoveCompletedTodosMutation", "source": "mutation RemoveCompletedTodosMutation(\n $input: RemoveCompletedTodosInput!\n) {\n removeCompletedTodos(input: $input) {\n deletedTodoIds\n user {\n completedCount\n totalCount\n id\n }\n }\n}\n" }, "d7dda774dcfa32fe0d9661e01cac9a4a": { "name": "ChangeTodoStatusMutation", "source": "mutation ChangeTodoStatusMutation(\n $input: ChangeTodoStatusInput!\n) {\n changeTodoStatus(input: $input) {\n todo {\n id\n complete\n }\n user {\n id\n completedCount\n }\n }\n}\n" } } graphql-ruby-1.11.10/javascript_client/src/__tests__/example-relay-persisted-queries.json000066400000000000000000000047301414121453000316330ustar00rootroot00000000000000{ "aba626ea9bdf465954e89e5590eb2c1a": "mutation RemoveTodoMutation(\n $input: RemoveTodoInput!\n) {\n removeTodo(input: $input) {\n deletedTodoId\n user {\n completedCount\n totalCount\n id\n }\n }\n}\n", "67c2bc8aa3185a209d6651b4feb63c04": "query appQuery(\n $userId: String\n) {\n user(id: $userId) {\n ...TodoApp_user\n id\n }\n}\n\nfragment TodoApp_user on User {\n id\n userId\n totalCount\n ...TodoListFooter_user\n ...TodoList_user\n}\n\nfragment TodoListFooter_user on User {\n id\n userId\n completedCount\n todos(first: 2147483647) {\n edges {\n node {\n id\n complete\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n totalCount\n}\n\nfragment TodoList_user on User {\n todos(first: 2147483647) {\n edges {\n node {\n id\n complete\n ...Todo_todo\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n userId\n totalCount\n completedCount\n ...Todo_user\n}\n\nfragment Todo_todo on Todo {\n complete\n id\n text\n}\n\nfragment Todo_user on User {\n id\n userId\n totalCount\n completedCount\n}\n", "db9904c31d91416f21d45fe3d153884c": "mutation MarkAllTodosMutation(\n $input: MarkAllTodosInput!\n) {\n markAllTodos(input: $input) {\n changedTodos {\n id\n complete\n }\n user {\n id\n completedCount\n }\n }\n}\n", "2eb8c9941fdb3117fdbc08d15fab62d0": "mutation AddTodoMutation(\n $input: AddTodoInput!\n) {\n addTodo(input: $input) {\n todoEdge {\n __typename\n cursor\n node {\n complete\n id\n text\n }\n }\n user {\n id\n totalCount\n }\n }\n}\n", "d970fd7dbf118794415dec7324d463e3": "mutation RenameTodoMutation(\n $input: RenameTodoInput!\n) {\n renameTodo(input: $input) {\n todo {\n id\n text\n }\n }\n}\n", "a49217db31a8be3f4107763b957d5fca": "mutation RemoveCompletedTodosMutation(\n $input: RemoveCompletedTodosInput!\n) {\n removeCompletedTodos(input: $input) {\n deletedTodoIds\n user {\n completedCount\n totalCount\n id\n }\n }\n}\n", "d7dda774dcfa32fe0d9661e01cac9a4a": "mutation ChangeTodoStatusMutation(\n $input: ChangeTodoStatusInput!\n) {\n changeTodoStatus(input: $input) {\n todo {\n id\n complete\n }\n user {\n id\n completedCount\n }\n }\n}\n" } graphql-ruby-1.11.10/javascript_client/src/__tests__/indexTest.ts000066400000000000000000000015031414121453000250320ustar00rootroot00000000000000import {sync} from "../index" import childProcess from "child_process" describe("root module", () => { it("exports the sync function", () => { expect(sync).toBeInstanceOf(Function) }) it("exports things at root level", () => { // Make sure that the compiled JavaScript // has all the expected exports. var testScript = "var client = require('./dist/index'); console.log(JSON.stringify({ keys: Object.keys(client).sort() }))" var output = childProcess.execSync("node -e \"" + testScript + "\"") var outputData = JSON.parse(output.toString()) var expectedKeys = [ "AblyLink", "ActionCableLink", "PusherLink", "addGraphQLSubscriptions", "createRelaySubscriptionHandler", "generateClient", "sync" ] expect(outputData.keys).toEqual(expectedKeys) }) }) graphql-ruby-1.11.10/javascript_client/src/__tests__/project/000077500000000000000000000000001414121453000241625ustar00rootroot00000000000000graphql-ruby-1.11.10/javascript_client/src/__tests__/project/frag_1.graphql000066400000000000000000000000501414121453000266740ustar00rootroot00000000000000fragment Frag1 on Query { moreStuff } graphql-ruby-1.11.10/javascript_client/src/__tests__/project/frag_2.graphql000066400000000000000000000000471414121453000267030ustar00rootroot00000000000000fragment Frag2 on Query { ...Frag3 } graphql-ruby-1.11.10/javascript_client/src/__tests__/project/frag_3.graphql000066400000000000000000000000541414121453000267020ustar00rootroot00000000000000fragment Frag3 on Query { evenMoreStuff } graphql-ruby-1.11.10/javascript_client/src/__tests__/project/frag_4.graphql000066400000000000000000000001021414121453000266750ustar00rootroot00000000000000fragment Frag4 on Query { evenMoreStuff { stuffInside } } graphql-ruby-1.11.10/javascript_client/src/__tests__/project/op_1.graphql000066400000000000000000000000361414121453000263770ustar00rootroot00000000000000query GetStuff { ...Frag1 } graphql-ruby-1.11.10/javascript_client/src/__tests__/project/op_2.graphql000066400000000000000000000000621414121453000263770ustar00rootroot00000000000000query GetStuff2 { stuff ...Frag1 ...Frag2 } graphql-ruby-1.11.10/javascript_client/src/__tests__/project/op_3.graphql000066400000000000000000000001141414121453000263760ustar00rootroot00000000000000query GetStuff3 { stuff { withStuffInside } ...Frag2 ...Frag4 } graphql-ruby-1.11.10/javascript_client/src/__tests__/project/op_isolated_1.graphql000066400000000000000000000002241414121453000302620ustar00rootroot00000000000000query GetStuffIsolated { ...FragIsolated things { existHere } } fragment FragIsolated on Query { evenMoreStuff { stuffInside } } graphql-ruby-1.11.10/javascript_client/src/__tests__/project/op_isolated_2.graphql000066400000000000000000000000721414121453000302640ustar00rootroot00000000000000query GetStuffIsolated2 { things { existHere } } graphql-ruby-1.11.10/javascript_client/src/__tests__/syncTest.ts000066400000000000000000000266711414121453000247140ustar00rootroot00000000000000import sync from "../sync" var fs = require("fs") var nock = require("nock") interface MockOperation { alias: string, } interface MockPayload { operations: MockOperation[], generatedCode: string, } interface MockedObject { mock: { calls: object } } describe("sync operations", () => { beforeEach(() => { global.console.error = jest.fn() global.console.log = jest.fn() }) afterEach(() => { jest.clearAllMocks(); }) describe("generating artifacts without syncing", () => { it("works without a URL", () => { var options = { client: "test-1", path: "./src/__tests__/documents", } return sync(options).then(function() { var generatedCode = fs.readFileSync("./src/OperationStoreClient.js", "utf8") expect(generatedCode).toMatch('"GetStuff": "f7f65309043352183e905e1396e51078"') expect(generatedCode).toMatchSnapshot() }) }) }) describe("custom HTTP options", () => { it("uses the provided `send` option & provided URL", () => { var url: string var options = { client: "test-1", path: "./src/__tests__/documents", url: "bogus", quiet: true, send: (_sendPayload: object, options: { url: string }) => { url = options.url }, } return sync(options).then(function() { expect(url).toEqual("bogus") }) }) }) describe("verbose", () => { it("Adds debug output", () => { var spy = (console.log as unknown) as MockedObject var options = { client: "test-1", path: "./src/__tests__/documents", url: "bogus", verbose: true, send: (_sendPayload: string, opts: { verbose: boolean }) => { if (opts.verbose) { console.log("Verbose!") } }, } return sync(options).then(function() { expect(spy.mock.calls).toMatchSnapshot() }) }) }) describe("custom file processing options", () => { it("Adds .graphql to the glob if needed", () => { var payload: MockPayload var options = { client: "test-1", path: "./src/__tests__/documents", url: "bogus", quiet: true, send: (sendPayload: MockPayload, _opts: object) => { payload = sendPayload }, } return sync(options).then(function() { expect(payload.operations).toMatchSnapshot() var optionsWithExt = {...options, glob: "./**/*.graphql"} return sync(optionsWithExt).then(function() { // Get the same result, even when the glob already has a file extension expect(payload.operations).toMatchSnapshot() }) }) }) it("Uses a custom hash function if provided", () => { var payload: MockPayload var options = { client: "test-1", path: "./src/__tests__/documents", url: "bogus", quiet: true, hash: (graphQLBody: string) => { // This is a bad hack to get the operation name var opName = graphQLBody.match(/query ([A-Za-z]+) \{/) return opName ? opName[1].toUpperCase() : null }, send: (sendPayload: MockPayload, _opts: object) => { payload = sendPayload }, } return sync(options).then(function() { expect(payload.operations).toMatchSnapshot() }) }) }) describe("Relay support", () => { it("Uses Relay generated .js files", () => { var payload: MockPayload var options = { client: "test-1", quiet: true, path: "./src/__generated__", url: "bogus", send: (sendPayload: MockPayload, _opts: object) => { payload = sendPayload }, } return sync(options).then(function () { expect(payload.operations).toMatchSnapshot() }) }) it("Uses relay --persist-output JSON files", () => { var payload: MockPayload var options = { client: "test-1", quiet: true, relayPersistedOutput: "./src/__tests__/example-relay-persisted-queries.json", url: "bogus", send: (sendPayload: MockPayload, _opts: object) => { payload = sendPayload }, } return sync(options).then(function () { return expect(payload.operations).toMatchSnapshot() }) }) it("Uses Apollo Android OperationOutput JSON files", () => { var payload: MockPayload var options = { client: "test-1", quiet: true, apolloAndroidOperationOutput: "./src/__tests__/example-apollo-android-operation-output.json", url: "bogus", send: (sendPayload: MockPayload, _opts: object) => { payload = sendPayload }, } return sync(options).then(function () { expect(payload.operations[0].alias).toEqual("aba626ea9bdf465954e89e5590eb2c1a") return expect(payload.operations).toMatchSnapshot() }) }) }) describe("Input files", () => { it("Merges fragments and operations across files", () => { var payload: MockPayload var options = { client: "test-1", quiet: true, path: "./src/__tests__/project/", url: "bogus", // mode: "project" is the default send: (sendPayload: MockPayload, _opts: object) => { payload = sendPayload }, } return sync(options).then(function () { expect(payload.operations).toMatchSnapshot() }) }) it("Uses mode: file to process each file separately", () => { var payload: MockPayload var options = { client: "test-1", quiet: true, path: "./src/__tests__/project", url: "bogus", mode: "file", send: (sendPayload: MockPayload, _opts: object) => { payload = sendPayload }, } return sync(options).then(function() { expect(payload.operations).toMatchSnapshot() }) }) }) describe("Promise result", () => { it("Yields the payload and generated code", () => { var options = { client: "test-1", path: "./src/__tests__/project", url: "bogus", quiet: true, send: () => { }, } return sync(options).then(function(ppayload: unknown) { var payload = ppayload as MockPayload expect(payload.operations.length).toEqual(5) var generatedCode = fs.readFileSync("./src/OperationStoreClient.js", "utf8") expect(payload.generatedCode).toEqual(generatedCode) fs.unlinkSync("./src/OperationStoreClient.js") }) }) }) describe("Sync output", () => { it("Generates a usable artifact for middleware", () => { var options = { client: "test-1", path: "./src/__tests__/project", url: "bogus", quiet: true, send: () => { }, } return sync(options).then(function() { var generatedCode = fs.readFileSync("./src/OperationStoreClient.js", "utf8") expect(generatedCode).toMatch('"GetStuff": "5f0da489cf508a7c65ff5fa144e50545"') expect(generatedCode).toMatch('module.exports = OperationStoreClient') expect(generatedCode).toMatch('var _client = "test-1"') fs.unlinkSync("./src/OperationStoreClient.js") }) }) it("Takes an outfile option", () => { var options = { client: "test-2", path: "./src/__tests__/project", url: "bogus", quiet: true, outfile: "__crazy_outfile.js", send: () => { }, } return sync(options).then(function() { var generatedCode = fs.readFileSync("./__crazy_outfile.js", "utf8") expect(generatedCode).toMatch('"GetStuff": "5f0da489cf508a7c65ff5fa144e50545"') expect(generatedCode).toMatch('module.exports = OperationStoreClient') expect(generatedCode).toMatch('var _client = "test-2"') fs.unlinkSync("./__crazy_outfile.js") }) }) it("Skips outfile generation when using --persist-output artifact", () => { var options = { client: "test-2", relayPersistedOutput: "./src/__tests__/example-relay-persisted-queries.json", url: "bogus", quiet: true, send: () => { }, } return sync(options).then(function() { // This is the default outfile: var wasWritten = fs.existsSync("./src/OperationStoreClient.js") expect(wasWritten).toBe(false) }) }) it("Skips outfile generation when using --apollo-android-operation-output artifact", () => { var options = { client: "test-2", apolloAndroidOperationOutput: "./src/__tests__/example-apollo-android-operation-output.json", url: "bogus", quiet: true, send: () => { }, } return sync(options).then(function() { // This is the default outfile: var wasWritten = fs.existsSync("./src/OperationStoreClient.js") expect(wasWritten).toBe(false) }) }) }) describe("Logging", () => { it("Logs progress", () => { var spy = (console.log as unknown) as MockedObject var options = { client: "test-1", path: "./src/__tests__/project", url: "bogus", send: () => { }, } return sync(options).then(function() { expect(spy.mock.calls).toMatchSnapshot() }) }) it("Can be quieted with quiet: true", () => { var spy = (console.log as unknown) as MockedObject var options = { client: "test-1", path: "./src/__tests__/project", url: "bogus", quiet: true, send: () => { }, } return sync(options).then(function() { expect(spy.mock.calls).toMatchSnapshot() }) }) }) describe("Printing the result", () => { function buildMockRespondingWith(status: number, data: object) { return nock("http://example.com").post("/stored_operations/sync").reply(status, data) } it("prints failure and sends the message to the promise", () => { var spyConsoleLog = (console.log as unknown) as MockedObject var spyConsoleError = (console.error as unknown) as MockedObject buildMockRespondingWith(422, { errors: { "5f0da489cf508a7c65ff5fa144e50545": ["something"] }, failed: ["5f0da489cf508a7c65ff5fa144e50545"], added: ["defg"], not_modified: [], }) var options = { client: "test-1", path: "./src/__tests__/project", url: "http://example.com/stored_operations/sync", quiet: false, } var syncPromise = sync(options) return syncPromise.catch((errmsg) => { expect(errmsg).toEqual("Sync failed: GetStuff: something") expect(spyConsoleLog.mock.calls).toMatchSnapshot() expect(spyConsoleError.mock.calls).toMatchSnapshot() jest.clearAllMocks(); }) }) it("prints success", () => { var spyConsoleLog = (console.log as unknown) as MockedObject var spyConsoleError = (console.error as unknown) as MockedObject buildMockRespondingWith(422, { errors: {}, failed: [], added: ["defg"], not_modified: ["xyz", "123"], }) var options = { client: "test-1", path: "./src/__tests__/project", url: "http://example.com/stored_operations/sync", quiet: false, } var syncPromise = sync(options) expect(spyConsoleLog.mock.calls).toMatchSnapshot() jest.clearAllMocks(); return syncPromise.then(() => { expect(spyConsoleLog.mock.calls).toMatchSnapshot() expect(spyConsoleError.mock.calls).toMatchSnapshot() jest.clearAllMocks(); }) }) }) }) graphql-ruby-1.11.10/javascript_client/src/cli.ts000077500000000000000000000056141414121453000217060ustar00rootroot00000000000000#!/usr/bin/env node import parseArgs from "minimist" import sync from "./sync/index" var argv = parseArgs(process.argv.slice(2)) if (argv.help || argv.h) { console.log(`usage: graphql-ruby-client sync Read .graphql files and push the contained operations to a GraphQL::Pro::OperationStore required arguments: --url= URL where data should be POSTed --client= Identifier for this client application optional arguments: --path= Path to .graphql files (default is "./**/*.graphql") --outfile= Target file for generated code --outfile-type= Target type for generated code (default is "js") --key= HMAC authentication key --relay-persisted-output= Path to a .json file from "relay-compiler ... --persist-output" (Outfile generation is skipped by default.) --apollo-android-operation-output= Path to a .json file from Apollo-Android's "generateOperationOutput" feature. (Outfile generation is skipped by default.) --mode= Treat files like a certain kind of project: relay: treat files like relay-compiler output project: treat files like a cohesive project (fragments are shared, names must be unique) file: treat each file like a stand-alone operation By default, this flag is set to: - "relay" if "__generated__" in the path - otherwise, "project" --add-typename Automatically adds the "__typename" field to your queries --quiet Suppress status logging --verbose Print debug output --help Print this message `) } else { var commandName = argv._[0] if (commandName !== "sync") { console.log("Only `graphql-ruby-client sync` is supported") } else { var result = sync({ path: argv.path, relayPersistedOutput: argv["relay-persisted-output"], apolloAndroidOperationOutput: argv["apollo-android-operation-output"], url: argv.url, client: argv.client, outfile: argv.outfile, outfileType: argv["outfile-type"], secret: argv.secret, mode: argv.mode, addTypename: argv["add-typename"], quiet: argv.hasOwnProperty("quiet"), verbose: argv.hasOwnProperty("verbose"), }) result.then(function() { process.exit(0) }).catch(function() { // The error is logged by the function process.exit(1) }) } } graphql-ruby-1.11.10/javascript_client/src/index.ts000066400000000000000000000010341414121453000222330ustar00rootroot00000000000000import sync from "./sync" import { generateClient } from "./sync/generateClient" import ActionCableLink from "./subscriptions/ActionCableLink" import PusherLink from "./subscriptions/PusherLink" import AblyLink from "./subscriptions/AblyLink" import addGraphQLSubscriptions from "./subscriptions/addGraphQLSubscriptions" import createHandler from "./subscriptions/createHandler" export { sync, generateClient, ActionCableLink, PusherLink, AblyLink, addGraphQLSubscriptions, createHandler as createRelaySubscriptionHandler, } graphql-ruby-1.11.10/javascript_client/src/subscriptions/000077500000000000000000000000001414121453000234655ustar00rootroot00000000000000graphql-ruby-1.11.10/javascript_client/src/subscriptions/AblyLink.ts000066400000000000000000000100541414121453000255420ustar00rootroot00000000000000// An Apollo Link for using graphql-pro's Ably subscriptions // // @example Adding subscriptions to a HttpLink // // Load Ably and create a client // var Ably = require('ably') // // Be sure to create an API key with "Subscribe" and "Presence" permissions only, // // and use that limited API key here: // var ablyClient = new Ably.Realtime({ key: "yourapp.key:secret" }) // // // Build a combined link, initialize the client: // const ablyLink = new AblyLink({ably: ablyClient}) // const link = ApolloLink.from([authLink, ablyLink, httpLink]) // const client = new ApolloClient(link: link, ...) // // @example Building a subscription, then subscribing to it // subscription = client.subscribe({ // variables: { room: roomName}, // query: gql` // subscription MessageAdded($room: String!) { // messageWasAdded(room: $room) { // room { // messages { // id // body // author { // screenname // } // } // } // } // } // ` // }) // // subscription.subscribe({ next: ({data, errors}) => { // // Do something with `data` and/or `errors` // }}) // import { ApolloLink, Observable, FetchResult, NextLink, Operation } from "apollo-link" import { Realtime } from "ably" type RequestResult = Observable, Record>> class AblyLink extends ApolloLink { ably: Realtime constructor(options: { ably: Realtime }) { super() // Retain a handle to the Ably client this.ably = options.ably } request(operation: Operation, forward: NextLink): RequestResult { return new Observable((observer) => { // Check the result of the operation forward(operation).subscribe({ next: (data) => { // If the operation has the subscription header, it's a subscription const subscriptionChannelConfig = this._getSubscriptionChannel(operation) if (subscriptionChannelConfig) { // This will keep pushing to `.next` this._createSubscription(subscriptionChannelConfig, observer) } else { // This isn't a subscription, // So pass the data along and close the observer. observer.next(data) observer.complete() } }}) }) } _getSubscriptionChannel(operation: Operation) { const response = operation.getContext().response // Check to see if the response has the header const subscriptionChannel = response.headers.get("X-Subscription-ID") // The server returns this header when encryption is enabled. const cipherKey = response.headers.get("X-Subscription-Key") return { channel: subscriptionChannel, key: cipherKey } } _createSubscription(subscriptionChannelConfig: { channel: string, key: string }, observer: { next: Function, complete: Function}) { const subscriptionChannel = subscriptionChannelConfig["channel"] const subscriptionKey = subscriptionChannelConfig["key"] const ablyOptions = subscriptionKey ? { cipher: { key: subscriptionKey } } : {} const ablyChannel = this.ably.channels.get(subscriptionChannel, ablyOptions) const ablyClientId = this.ably.auth.clientId // Register presence, so that we can detect empty channels and clean them up server-side if (ablyClientId) { ablyChannel.presence.enter() } else { ablyChannel.presence.enterClient("graphql-subscriber", "subscribed") } // Subscribe for more update ablyChannel.subscribe("update", function(message) { var payload = message.data if (!payload.more) { // This is the end, the server says to unsubscribe if (ablyClientId) { ablyChannel.presence.leave() } else { ablyChannel.presence.leaveClient("graphql-subscriber") } ablyChannel.unsubscribe() observer.complete() } const result = payload.result if (result) { // Send the new response to listeners observer.next(result) } }) } } export default AblyLink graphql-ruby-1.11.10/javascript_client/src/subscriptions/ActionCableLink.ts000066400000000000000000000041311414121453000270160ustar00rootroot00000000000000import { ApolloLink, Observable, FetchResult, Operation, NextLink } from "apollo-link" import { Cable } from "actioncable" import { print } from "graphql" type RequestResult = Observable, Record>> class ActionCableLink extends ApolloLink { cable: Cable channelName: string actionName: string connectionParams: object constructor(options: { cable: Cable, channelName?: string, actionName?: string, connectionParams?: object }) { super() this.cable = options.cable this.channelName = options.channelName || "GraphqlChannel" this.actionName = options.actionName || "execute" this.connectionParams = options.connectionParams || {} } // Interestingly, this link does _not_ call through to `next` because // instead, it sends the request to ActionCable. request(operation: Operation, _next: NextLink): RequestResult { return new Observable((observer) => { var channelId = Math.round(Date.now() + Math.random() * 100000).toString(16) var actionName = this.actionName var subscription = this.cable.subscriptions.create(Object.assign({},{ channel: this.channelName, channelId: channelId }, this.connectionParams), { connected: function() { this.perform( actionName, { query: operation.query ? print(operation.query) : null, variables: operation.variables, // This is added for persisted operation support: operationId: (operation as {operationId?: string}).operationId, operationName: operation.operationName } ) }, received: function(payload) { if (payload.result.data || payload.result.errors) { observer.next(payload.result) } if (!payload.more) { observer.complete() } } }) // Make the ActionCable subscription behave like an Apollo subscription return Object.assign(subscription, {closed: false}) }) } } export default ActionCableLink graphql-ruby-1.11.10/javascript_client/src/subscriptions/ActionCableSubscriber.ts000066400000000000000000000051521414121453000302300ustar00rootroot00000000000000import printer from "graphql/language/printer" import registry from "./registry" import { Cable } from "actioncable" interface ApolloNetworkInterface { applyMiddlewares: Function query: (req: object) => Promise _opts: any } class ActionCableSubscriber { _cable: Cable _networkInterface: ApolloNetworkInterface constructor(cable: Cable, networkInterface: ApolloNetworkInterface) { this._cable = cable this._networkInterface = networkInterface } /** * Send `request` over ActionCable (`registry._cable`), * calling `handler` with any incoming data. * Return the subscription so that the registry can unsubscribe it later. * @param {Object} registry * @param {Object} request * @param {Function} handler * @return {ID} An ID for unsubscribing */ subscribe(request: any, handler: any) { var networkInterface = this._networkInterface // unique-ish var channelId = Math.round(Date.now() + Math.random() * 100000).toString(16) var channel = this._cable.subscriptions.create({ channel: "GraphqlChannel", channelId: channelId, }, { // After connecting, send the data over ActionCable connected: function() { var _this = this // applyMiddlewares code is inspired by networkInterface internals var opts = Object.assign({}, networkInterface._opts) networkInterface .applyMiddlewares({request: request, options: opts}) .then(function() { var queryString = request.query ? printer.print(request.query) : null var operationName = request.operationName var operationId = request.operationId var variables = JSON.stringify(request.variables) var channelParams = Object.assign({}, request, { query: queryString, variables: variables, operationId: operationId, operationName: operationName, }) // This goes to the #execute method of the channel _this.perform("execute", channelParams) }) }, // Payload from ActionCable should have at least two keys: // - more: true if this channel should stay open // - result: the GraphQL response for this result received: function(payload) { if (!payload.more) { registry.unsubscribe(id) } var result = payload.result if (result) { handler(result.errors, result.data) } }, }) var id = registry.add(channel) return id } unsubscribe(id: number) { registry.unsubscribe(id) } } export default ActionCableSubscriber graphql-ruby-1.11.10/javascript_client/src/subscriptions/PusherLink.ts000066400000000000000000000114441414121453000261250ustar00rootroot00000000000000// An Apollo Link for using graphql-pro's Pusher subscriptions // // @example Adding subscriptions to a HttpLink // // Load Pusher and create a client // import Pusher from "pusher-js" // var pusherClient = new Pusher("your-app-key", { cluster: "us2" }) // // // Build a combined link, initialize the client: // const pusherLink = new PusherLink({pusher: pusherClient}) // const link = ApolloLink.from([authLink, pusherLink, httpLink]) // const client = new ApolloClient(link: link, ...) // // @example Building a subscription, then subscribing to it // subscription = client.subscribe({ // variables: { room: roomName}, // query: gql` // subscription MessageAdded($room: String!) { // messageWasAdded(room: $room) { // room { // messages { // id // body // author { // screenname // } // } // } // } // } // ` // }) // // subscription.subscribe({ next: ({data, errors}) => { // // Do something with `data` and/or `errors` // }}) // import { ApolloLink, Observable, Operation, NextLink, FetchResult } from "apollo-link" import { Pusher } from "pusher-js" type RequestResult = Observable, Record>> class PusherLink extends ApolloLink { pusher: Pusher constructor(options: { pusher: Pusher }) { super() // Retain a handle to the Pusher client this.pusher = options.pusher } request(operation: Operation, forward: NextLink): RequestResult { const subscribeObservable = new Observable((_observer) => { }) as RequestResult var pusher = this.pusher // Capture the super method const prevSubscribe = subscribeObservable.subscribe.bind(subscribeObservable) // Override subscribe to return an `unsubscribe` object, see // https://github.com/apollographql/subscriptions-transport-ws/blob/master/src/client.ts#L182-L212 subscribeObservable.subscribe = (observerOrNext: any, onError: (error: any) => void, onComplete: () => void) => { // Call super prevSubscribe(observerOrNext, onError, onComplete) const observer = getObserver(observerOrNext, onError, onComplete) var subscriptionChannel: string // Check the result of the operation const resultObservable = forward(operation) // When the operation is done, try to get the subscription ID from the server resultObservable.subscribe({ next: (data) => { // If the operation has the subscription header, it's a subscription const response = operation.getContext().response // Check to see if the response has the header subscriptionChannel = response.headers.get("X-Subscription-ID") if (subscriptionChannel) { // Set up the pusher subscription for updates from the server const pusherChannel = this.pusher.subscribe(subscriptionChannel) // Subscribe for more update pusherChannel.bind("update", function(payload) { if (!payload.more) { // This is the end, the server says to unsubscribe pusher.unsubscribe(subscriptionChannel) observer.complete() } const result = payload.result if (result) { // Send the new response to listeners observer.next(result) } }) } else { // This isn't a subscription, // So pass the data along and close the observer. observer.next(data) observer.complete() } }}) // Return an object that will unsubscribe _if_ the query was a subscription. return { closed: false, unsubscribe: () => { subscriptionChannel && this.pusher.unsubscribe(subscriptionChannel) } } } return subscribeObservable } } // Turn `subscribe` arguments into an observer-like thing, see getObserver // https://github.com/apollographql/subscriptions-transport-ws/blob/master/src/client.ts#L329-L343 function getObserver(observerOrNext: Function | {next: Function, error: Function, complete: Function}, onError: Function, onComplete: Function) { if (typeof observerOrNext === 'function') { // Duck-type an observer return { next: (v: object) => observerOrNext(v), error: (e: object) => onError && onError(e), complete: () => onComplete && onComplete(), } } else { // Make an object that calls to the given object, with safety checks return { next: (v: object) => observerOrNext.next && observerOrNext.next(v), error: (e: object) => observerOrNext.error && observerOrNext.error(e), complete: () => observerOrNext.complete && observerOrNext.complete(), } } } export default PusherLink export { getObserver } graphql-ruby-1.11.10/javascript_client/src/subscriptions/PusherSubscriber.ts000066400000000000000000000050351414121453000273320ustar00rootroot00000000000000import registry from "./registry" import { Pusher } from "pusher-js" interface ApolloNetworkInterface { use: Function useAfter: Function query: (req: object) => Promise } /** * Make a new subscriber for `addGraphQLSubscriptions` * * @param {Pusher} pusher */ class PusherSubscriber { _pusher: Pusher _networkInterface: ApolloNetworkInterface constructor(pusher: Pusher, networkInterface: ApolloNetworkInterface) { this._pusher = pusher this._networkInterface = networkInterface // This is a bit tricky: // only the _request_ is passed to the `subscribe` function, s // so we have to attach the subscription id to the `request`. // However, the request is _not_ available in the afterware function. // So: // - Add the request to `options` so it's available in afterware // - In the afterware, update the request to hold the header value // - Finally, in `subscribe`, read the subscription ID off of `request` networkInterface.use([{ applyMiddleware: function({request, options}: any, next: Function) { options.request = request next() } }]) networkInterface.useAfter([{ applyAfterware: function({response, options}: any, next: Function) { options.request.__subscriptionId = response.headers.get("X-Subscription-ID") next() } }]) } // Implement the Apollo subscribe API subscribe(request: {__subscriptionId: string}, handler: any) { var pusher = this._pusher var networkInterface = this._networkInterface var subscription = { _channelName: "", // set after the successful POST unsubscribe: function() { if (this._channelName) { pusher.unsubscribe(this._channelName) } } } var id = registry.add(subscription) // Send the subscription as a query // Get the channel ID from the response headers networkInterface.query(request).then(function(_executionResult: any){ var subscriptionChannel = request.__subscriptionId subscription._channelName = subscriptionChannel var pusherChannel = pusher.subscribe(subscriptionChannel) // When you get an update form Pusher, send it to Apollo pusherChannel.bind("update", function(payload) { if (!payload.more) { registry.unsubscribe(id) } var result = payload.result if (result) { handler(result.errors, result.data) } }) }) return id } unsubscribe(id: number) { registry.unsubscribe(id) } } export default PusherSubscriber graphql-ruby-1.11.10/javascript_client/src/subscriptions/__tests__/000077500000000000000000000000001414121453000254235ustar00rootroot00000000000000graphql-ruby-1.11.10/javascript_client/src/subscriptions/__tests__/ActionCableLinkTest.ts000066400000000000000000000065141414121453000316230ustar00rootroot00000000000000import ActionCableLink from "../ActionCableLink" import { parse } from "graphql" import { Cable } from "actioncable" import { Operation } from "apollo-link" describe("ActionCableLink", () => { var log: any[] var cable: any var options: any var link: any var query: any var operation: Operation beforeEach(() => { log = [] cable = { subscriptions: { create: function(_channelName: string, options: {connected: Function, received: Function}) { var subscription = Object.assign( Object.create({ perform: function(actionName: string, options: object) { log.push(["perform", { actionName: actionName, options: options }]) }, unsubscribe: function() { log.push(["unsubscribe"]) } }), options ) subscription.connected = subscription.connected.bind(subscription) subscription.__proto__.unsubscribe = subscription.__proto__.unsubscribe.bind(subscription) subscription.connected() return subscription } } } options = { cable: (cable as unknown) as Cable } link = new ActionCableLink(options) query = parse("subscription { foo { bar } }") operation = ({ query: query, variables: { a: 1 }, operationId: "operationId", operationName: "operationName" } as unknown) as Operation }) it("delegates to the cable", () => { var observable = link.request(operation, null as any) // unpack the underlying subscription var subscription: any = (observable.subscribe(function(result: any) { log.push(["received", result]) }) as any)._cleanup subscription.received({ result: { data: null }, more: true }) subscription.received({ result: { data: "data 1" }, more: true }) subscription.received({ result: { data: "data 2" }, more: false }) expect(log).toEqual([ [ "perform", { actionName: "execute", options: { query: "subscription {\n foo {\n bar\n }\n}\n", variables: { a: 1 }, operationId: "operationId", operationName: "operationName" } } ], ["received", { data: "data 1" }], ["received", { data: "data 2" }], ["unsubscribe"] ]) }) it("delegates a manual unsubscribe to the cable", () => { var observable = link.request(operation, null as any) // unpack the underlying subscription var subscription: any = (observable.subscribe(function(result: any) { log.push(["received", result]) }) as any)._cleanup subscription.received({ result: { data: null }, more: true }) subscription.received({ result: { data: "data 1" }, more: true }) subscription.unsubscribe() expect(log).toEqual([ [ "perform", { actionName: "execute", options: { query: "subscription {\n foo {\n bar\n }\n}\n", variables: { a: 1 }, operationId: "operationId", operationName: "operationName" } } ], ["received", { data: "data 1" }], ["unsubscribe"] ]) }) }) graphql-ruby-1.11.10/javascript_client/src/subscriptions/__tests__/addGraphQLSubscriptionsTest.ts000066400000000000000000000014741414121453000334000ustar00rootroot00000000000000import addGraphQLSubscriptions from "../addGraphQLSubscriptions" describe("addGraphQLSubscriptions", () => { it("delegates to the subscriber", () => { var state: {[key: string]: string} = {} var subscriber = { subscribe: function(req: string, handler: string) { state[req] = handler return req + "/" + handler }, unsubscribe(id: string) { var key = id.split("/")[0] delete state[key] } } var dummyNetworkInterface = addGraphQLSubscriptions({}, {subscriber: subscriber}) var id = dummyNetworkInterface.subscribe("abc", "def") expect(id).toEqual("abc/def") expect(Object.keys(state).length).toEqual(1) expect(state["abc"]).toEqual("def") dummyNetworkInterface.unsubscribe(id) expect(Object.keys(state).length).toEqual(0) }) }) graphql-ruby-1.11.10/javascript_client/src/subscriptions/__tests__/createAblyHandlerTest.ts000066400000000000000000000300361414121453000322060ustar00rootroot00000000000000import { createAblyHandler } from "../createAblyHandler" import { Realtime, Types } from "ably" const dummyOperation = { text: "", name: "" } const channelTemplate = { presence: { enter() {}, enterClient() {}, leave(callback?: (err?: Types.ErrorInfo) => void) { if (callback) callback() } }, subscribe: () => {}, unsubscribe: () => {}, on: () => {}, detach: (callback?: (err?: Types.ErrorInfo) => void) => { if (callback) callback() } } const createDummyConsumer = ( channel: any = channelTemplate, release = (_channelName: string) => {} ): Realtime => (({ auth: { clientId: "foo" }, channels: { get: () => channel, release } } as unknown) as Realtime) const nextTick = () => new Promise(resolve => setTimeout(resolve, 0)) describe("createAblyHandler", () => { it("returns a function producing a disposable subscription", async () => { const subscriptionId = "dummy-subscription" var wasUnsubscribed = false var wasDetached = false var releasedChannelName const producer = createAblyHandler({ fetchOperation: () => new Promise(resolve => resolve({ headers: new Map([["X-Subscription-ID", subscriptionId]]), body: { data: { foo: "bar" } } }) ), ably: createDummyConsumer( { ...channelTemplate, unsubscribe: () => { wasUnsubscribed = true }, detach: (callback?: (err?: Types.ErrorInfo) => void) => { if (callback) callback() wasDetached = true }, name: subscriptionId }, (channelName: string) => { releasedChannelName = channelName } ) }) const subscription = producer( dummyOperation, {}, {}, { onError: () => {}, onNext: () => {}, onCompleted: () => {} } ) await nextTick() await subscription.dispose() expect(wasUnsubscribed).toEqual(true) expect(wasDetached).toEqual(true) expect(releasedChannelName).toEqual(subscriptionId) }) it("dispatches the immediate response in case of success", async () => { let errorInvokedWith = undefined let nextInvokedWith = undefined const producer = createAblyHandler({ fetchOperation: () => new Promise(resolve => resolve({ headers: new Map([["X-Subscription-ID", "foo"]]), body: { data: { foo: "bar" } } }) ), ably: createDummyConsumer() }) producer( dummyOperation, {}, {}, { onError: (errors: any) => { errorInvokedWith = errors }, onNext: (response: any) => { nextInvokedWith = response }, onCompleted: () => {} } ) await nextTick() expect(errorInvokedWith).toBeUndefined() expect(nextInvokedWith).toEqual({ data: { foo: "bar" } }) }) it("dispatches the immediate response in case of error", async () => { let errorInvokedWith = undefined let nextInvokedWith = undefined const dummyErrors = [{ message: "baz" }] const producer = createAblyHandler({ fetchOperation: () => new Promise(resolve => resolve({ headers: new Map([["X-Subscription-ID", "foo"]]), body: { errors: dummyErrors } }) ), ably: createDummyConsumer() }) producer( dummyOperation, {}, {}, { onError: (errors: any) => { errorInvokedWith = errors }, onNext: () => {}, onCompleted: () => {} } ) await nextTick() expect(errorInvokedWith).toEqual(dummyErrors) expect(nextInvokedWith).toBeUndefined() }) it("doesn't dispatch anything for an empty response", async () => { let errorInvokedWith = undefined let nextInvokedWith = undefined const producer = createAblyHandler({ fetchOperation: () => new Promise(resolve => resolve({ headers: new Map([["X-Subscription-ID", "foo"]]), body: {} }) ), ably: createDummyConsumer() }) producer( dummyOperation, {}, {}, { onError: (errors: any) => { errorInvokedWith = errors }, onNext: (response: any) => { nextInvokedWith = response }, onCompleted: () => {} } ) await nextTick() expect(errorInvokedWith).toBeUndefined() expect(nextInvokedWith).toBeUndefined() }) it("dispatches caught errors", async () => { let errorInvokedWith = undefined let nextInvokedWith = undefined const error = new Error("blam") const producer = createAblyHandler({ fetchOperation: () => new Promise((_resolve, reject) => reject(error)), ably: createDummyConsumer() }) producer( dummyOperation, {}, {}, { onError: (errors: any) => { errorInvokedWith = errors }, onNext: (response: any) => { nextInvokedWith = response }, onCompleted: () => {} } ) await nextTick() expect(errorInvokedWith).toBe(error) expect(nextInvokedWith).toBeUndefined() }) it("detaches the channel when the subscription is disposed during initial response", async () => { let detached = false const ably = createDummyConsumer({ ...channelTemplate, detach() { detached = true } }) const producer = createAblyHandler({ fetchOperation: () => new Promise(resolve => resolve({ headers: new Map([["X-Subscription-ID", "foo"]]), body: { errors: {} } }) ), ably }) const { dispose } = producer( dummyOperation, {}, {}, { onError: async () => { dispose() }, onNext: async () => {}, onCompleted: () => {} } ) await nextTick() expect(detached).toBe(true) }) describe("integration with Ably", () => { const key = process.env.ABLY_KEY const testWithAblyKey = key ? test : test.skip test("onError is called when using invalid key", async () => { const ably = new Realtime({ key: "integration-test:invalid", log: { level: 0 } }) await new Promise(resolve => { const fetchOperation = async () => ({ headers: new Map([["X-Subscription-ID", "foo"]]) }) const ablyHandler = createAblyHandler({ ably, fetchOperation }) const operation = {} const variables = {} const cacheConfig = {} const onError = (error: any) => { expect(error.message).toMatch(/Invalid key in request/) resolve() } const onNext = () => console.log("onNext") const onCompleted = () => console.log("onCompleted") const observer = { onError, onNext, onCompleted } ablyHandler(operation, variables, cacheConfig, observer) }) ably.close() }) // For executing this test you need to provide a valid Ably API key in // environment variable ABLY_KEY testWithAblyKey( "onError is called for too many subscriptions", async () => { const ably = new Realtime({ key, log: { level: 0 } }) await new Promise(resolve => { let subscriptionCounter = 0 const fetchOperation = async () => { subscriptionCounter += 1 return { headers: new Map([ ["X-Subscription-ID", `foo-${subscriptionCounter}`] ]) } } const ablyHandler = createAblyHandler({ ably, fetchOperation }) const operation = {} const variables = {} const cacheConfig = {} const onError = (error: any) => { expect(error.message).toMatch(/Maximum number of channels/) resolve() } const onNext = () => console.log("onNext") const onCompleted = () => console.log("onCompleted") const observer = { onError, onNext, onCompleted } for (let i = 0; i < 201; ++i) { ablyHandler(operation, variables, cacheConfig, observer) } }) ably.close() } ) // For executing this test you need to provide a valid Ably API key in // environment variable ABLY_KEY // // This test might take longer than the default jest timeout of 5s. // Consider setting a higher timeout when running in CI. testWithAblyKey("can make more than 200 subscriptions", async () => { let caughtError = null const ably = new Realtime({ key, log: { level: 0 } }) let subscriptionCounter = 0 const fetchOperation = async () => { subscriptionCounter += 1 return { headers: new Map([ ["X-Subscription-ID", `foo-${subscriptionCounter}`] ]) } } const ablyHandler = createAblyHandler({ ably, fetchOperation }) const operation = {} const variables = {} const cacheConfig = {} const onError = (error: Error) => { caughtError = error } const onNext = () => {} const onCompleted = () => {} const observer = { onError, onNext, onCompleted } const disposals = [] for (let i = 0; i < 200; ++i) { const { dispose } = ablyHandler( operation, variables, cacheConfig, observer ) await new Promise(resolve => setTimeout(resolve, 0)) disposals.push(dispose()) } await Promise.all(disposals) // 201st subscription - should work now that previous 200 subscriptions have been diposed const { dispose } = ablyHandler( operation, variables, cacheConfig, observer ) await new Promise(resolve => setTimeout(resolve, 0)) await dispose() ably.close() if (caughtError) throw caughtError }) // For executing this test you need to provide a valid Ably API key in // environment variable ABLY_KEY testWithAblyKey( "receives message sent before subscribe takes effect", async () => { let caughtError = null const ably = new Realtime({ key, log: { level: 0 } }) ably.connect() const subscriptionId = Math.random().toString(36) const fetchOperation = async () => ({ headers: new Map([["X-Subscription-ID", subscriptionId]]), body: { data: "immediateResult" } }) const ablyHandler = createAblyHandler({ ably, fetchOperation }) const operation = {} const variables = {} const cacheConfig = {} const onError = (error: Error) => { caughtError = error } const messages: any[] = [] const onNext = (message: any) => { messages.push(message.data) } const onCompleted = () => {} const observer = { onError, onNext, onCompleted } // Publish before subscribe await new Promise((resolve, reject) => { const ablyPublisher = new Realtime({ key, log: { level: 0 } }) const publishChannel = ablyPublisher.channels.get(subscriptionId) publishChannel.publish( "update", { result: { data: "asyncResult" } }, err => { ablyPublisher.close() if (err) { reject(err) } else { resolve() } } ) }) const { dispose } = ablyHandler( operation, variables, cacheConfig, observer ) for (let i = 0; i < 20 && messages.length < 2; ++i) { await new Promise(resolve => setTimeout(resolve, 100)) } await dispose() ably.close() if (caughtError) throw caughtError expect(messages).toEqual(["immediateResult", "asyncResult"]) } ) }) }) graphql-ruby-1.11.10/javascript_client/src/subscriptions/__tests__/createActionCableHandlerTest.ts000066400000000000000000000013471414121453000334660ustar00rootroot00000000000000import { createActionCableHandler } from "../createActionCableHandler" import { Cable } from "actioncable" describe("createActionCableHandler", () => { it("returns a function producing a disposable subscription", () => { var wasDisposed = false var subscription = { unsubscribe: () => (wasDisposed = true) } var dummyActionCableConsumer = { subscriptions: { create: () => subscription }, } var options = { cable: (dummyActionCableConsumer as unknown) as Cable } var producer = createActionCableHandler(options) producer({text: "", name: ""}, {}, {}, { onError: () => {}, onNext: () => {}, onCompleted: () => {} }).dispose() expect(wasDisposed).toEqual(true) }) }) graphql-ruby-1.11.10/javascript_client/src/subscriptions/__tests__/registryTest.ts000066400000000000000000000021471414121453000305070ustar00rootroot00000000000000import registry from "../registry" describe("subscription registry", () => { it("adds and unsubscribes", () => { // A subscription is something that responds to `.unsubscribe` var wasUnsubscribed1 = false var subscription1 = { unsubscribe: function() { wasUnsubscribed1 = true } } var wasUnsubscribed2 = false var subscription2 = { unsubscribe: function() { wasUnsubscribed2 = true } } // Adding a subscription returns an ID for unsubscribing var id1 = registry.add(subscription1) var id2 = registry.add(subscription2) expect(typeof id1).toEqual("number") expect(typeof id2).toEqual("number") // Unsubscribing calls the `.unsubscribe `function registry.unsubscribe(id1) expect(wasUnsubscribed1).toEqual(true) expect(wasUnsubscribed2).toEqual(false) registry.unsubscribe(id2) expect(wasUnsubscribed1).toEqual(true) expect(wasUnsubscribed2).toEqual(true) }) it("raises on unknown ids", () => { expect(() => { registry.unsubscribe(999) }).toThrow("No subscription found for id: 999") }) }) graphql-ruby-1.11.10/javascript_client/src/subscriptions/addGraphQLSubscriptions.ts000066400000000000000000000054741414121453000306060ustar00rootroot00000000000000import ActionCableSubscriber from "./ActionCableSubscriber" import PusherSubscriber from "./PusherSubscriber" import { Pusher } from "pusher-js" import { Cable } from "actioncable" interface Subscriber { subscribe: Function unsubscribe: Function } /** * Modify an Apollo network interface to * subscribe an unsubscribe using `cable:`. * Based on `addGraphQLSubscriptions` from `subscriptions-transport-ws`. * * This function assigns `.subscribe` and `.unsubscribe` functions * to the provided networkInterface. * @example Adding ActionCable subscriptions to a HTTP network interface * // Load ActionCable and create a consumer * var ActionCable = require('actioncable') * var cable = ActionCable.createConsumer() * window.cable = cable * * // Load ApolloClient and create a network interface * var apollo = require('apollo-client') * var RailsNetworkInterface = apollo.createNetworkInterface({ * uri: '/graphql', * opts: { * credentials: 'include', * }, * headers: { * 'X-CSRF-Token': $("meta[name=csrf-token]").attr("content"), * } * }); * * // Add subscriptions to the network interface * var addGraphQLSubscriptions = require("graphql-ruby-client/subscriptions/addGraphQLSubscriptions") * addGraphQLSubscriptions(RailsNetworkInterface, {cable: cable}) * * // Optionally, add persisted query support: * var OperationStoreClient = require("./OperationStoreClient") * RailsNetworkInterface.use([OperationStoreClient.apolloMiddleware]) * * @example Subscriptions with Pusher & graphql-pro * var pusher = new Pusher(appId, options) * addGraphQLSubscriptions(RailsNetworkInterface, {pusher: pusher}) * * @param {Object} networkInterface - an HTTP NetworkInterface * @param {ActionCable.Consumer} options.cable - A cable for subscribing with * @param {Pusher} options.pusher - A pusher client for subscribing with */ function addGraphQLSubscriptions(networkInterface: any, options: { pusher?: Pusher, cable?: Cable, subscriber?: Subscriber}) { if (!options) { options = {} } var subscriber: Subscriber if (options.subscriber) { // Right now this is just for testing subscriber = options.subscriber } else if (options.cable) { subscriber = new ActionCableSubscriber(options.cable, networkInterface) } else if (options.pusher) { subscriber = new PusherSubscriber(options.pusher, networkInterface) } else { throw new Error("Must provide cable: or pusher: option") } var networkInterfaceWithSubscriptions = Object.assign(networkInterface, { subscribe: function(request: any, handler: any) { var id = subscriber.subscribe(request, handler) return id }, unsubscribe(id: number) { subscriber.unsubscribe(id) }, }) return networkInterfaceWithSubscriptions } export default addGraphQLSubscriptions graphql-ruby-1.11.10/javascript_client/src/subscriptions/createAblyHandler.ts000066400000000000000000000136331414121453000274140ustar00rootroot00000000000000import { Realtime, Types } from "ably" // TODO: // - end-to-end test // - extract update code, inject it as a function? interface AblyHandlerOptions { ably: Realtime fetchOperation: Function } interface ApolloObserver { onError: Function onNext: Function onCompleted: Function } const anonymousClientId = "graphql-subscriber" // Current max. number of rewound messages in the initial response to // subscribe. See // https://github.com/ably/docs/blob/baa0a4666079abba3a3e19e82eb99ca8b8a735d0/content/realtime/channels/channel-parameters/rewind.textile#additional-information // Note that using a higher value emits a warning. const maxNumRewindMessages = 100 class AblyError { constructor(reason: Types.ErrorInfo) { const error = Error(reason.message) const attributes: (keyof Types.ErrorInfo)[] = ["code", "statusCode"] attributes.forEach(attr => { Object.defineProperty(error, attr, { get() { return reason[attr] } }) }) Error.captureStackTrace(error, AblyError) return error } } function createAblyHandler(options: AblyHandlerOptions) { const { ably, fetchOperation } = options const isAnonymousClient = () => !ably.auth.clientId || ably.auth.clientId === "*" return ( operation: object, variables: object, cacheConfig: object, observer: ApolloObserver ) => { let channel: Types.RealtimeChannelCallbacks | null = null const dispatchResult = (result: { errors: any; data: any }) => { if (result) { if (result.errors) { // What kind of error stuff belongs here? observer.onError(result.errors) } else if (result.data) { observer.onNext({ data: result.data }) } } } const updateHandler = (message: Types.Message) => { // TODO Extract this code // When we get a response, send the update to `observer` const payload = message.data dispatchResult(payload.result) if (!payload.more) { // Subscription is finished observer.onCompleted() } } ;(async () => { try { // POST the subscription like a normal query const response = await fetchOperation(operation, variables, cacheConfig) const channelName = response.headers.get("X-Subscription-ID") if (!channelName) { throw new Error("Missing X-Subscription-ID header") } const channelKey = response.headers.get("X-Subscription-Key") channel = ably.channels.get(channelName, { params: { rewind: String(maxNumRewindMessages) }, cipher: channelKey ? { key: channelKey } : undefined, modes: ["SUBSCRIBE", "PRESENCE"] }) channel.on("failed", function(stateChange: Types.ChannelStateChange) { observer.onError( stateChange.reason ? new AblyError(stateChange.reason) : new Error("Ably channel changed to failed state") ) }) channel.on("suspended", function( stateChange: Types.ChannelStateChange ) { // Note: suspension can be a temporary condition and isn't necessarily // an error, however we handle the case where the channel gets // suspended before it is attached because that's the only way to // propagate error 90010 (see https://help.ably.io/error/90010) if ( stateChange.previous === "attaching" && stateChange.current === "suspended" ) { observer.onError( stateChange.reason ? new AblyError(stateChange.reason) : new Error("Ably channel suspended before being attached") ) } }) // Register presence, so that we can detect empty channels and clean them up server-side const enterCallback = (errorInfo: Types.ErrorInfo | undefined) => { if (errorInfo && channel) { observer.onError(new AblyError(errorInfo)) } } if (isAnonymousClient()) { channel.presence.enterClient( anonymousClientId, "subscribed", enterCallback ) } else { channel.presence.enter("subscribed", enterCallback) } // When you get an update from ably, give it to Relay channel.subscribe("update", updateHandler) // Dispatch the result _after_ setting up the channel, // because Relay might immediately dispose of the subscription. // (In that case, we want to make sure the channel is cleaned up properly.) dispatchResult(response.body) } catch (error) { observer.onError(error) } })() return { dispose: async () => { try { if (channel) { const disposedChannel = channel channel = null disposedChannel.unsubscribe() // Ensure channel is no longer attaching, as otherwise detach does // nothing if (disposedChannel.state === "attaching") { await new Promise((resolve, _reject) => { const onStateChange = ( stateChange: Types.ChannelStateChange ) => { if (stateChange.current !== "attaching") { disposedChannel.off(onStateChange) resolve() } } disposedChannel.on(onStateChange) }) } await new Promise((resolve, reject) => { disposedChannel.detach((err) => { if (err) { reject(new AblyError(err)) } else { resolve() } }) }) ably.channels.release(disposedChannel.name) } } catch (error) { observer.onError(error) } } } } } export { createAblyHandler, AblyHandlerOptions } graphql-ruby-1.11.10/javascript_client/src/subscriptions/createActionCableHandler.ts000066400000000000000000000046511414121453000306710ustar00rootroot00000000000000import { Cable } from "actioncable" /** * Create a Relay Modern-compatible subscription handler. * * @param {ActionCable.Consumer} cable - An ActionCable consumer from `.createConsumer` * @param {OperationStoreClient} operations - A generated OperationStoreClient for graphql-pro's OperationStore * @return {Function} */ interface ActionCableHandlerOptions { cable: Cable operations?: { getOperationId: Function} } function createActionCableHandler(options: ActionCableHandlerOptions) { return function (operation: { text: string, name: string}, variables: object, _cacheConfig: object, observer: {onError: Function, onNext: Function, onCompleted: Function}) { // unique-ish var channelId = Math.round(Date.now() + Math.random() * 100000).toString(16) var cable = options.cable var operations = options.operations // Register the subscription by subscribing to the channel const subscription = cable.subscriptions.create({ channel: "GraphqlChannel", channelId: channelId, }, { connected: function() { var channelParams: object // Once connected, send the GraphQL data over the channel // Use the stored operation alias if possible if (operations) { channelParams = { variables: variables, operationName: operation.name, operationId: operations.getOperationId(operation.name) } } else { channelParams = { variables: variables, operationName: operation.name, query: operation.text } } this.perform("execute", channelParams) }, // This result is sent back from ActionCable. received: function(payload: { result: { errors: any[], data: object }, more: boolean}) { // When we get a response, send the update to `observer` const result = payload.result if (result && result.errors) { // What kind of error stuff belongs here? observer.onError(result.errors) } else if (result) { observer.onNext({data: result.data}) } if (!payload.more) { // Subscription is finished observer.onCompleted() } } }) // Return an object for Relay to unsubscribe with return { dispose: function() { subscription.unsubscribe() } } } } export { createActionCableHandler, ActionCableHandlerOptions } graphql-ruby-1.11.10/javascript_client/src/subscriptions/createHandler.ts000066400000000000000000000027471414121453000266100ustar00rootroot00000000000000import { createActionCableHandler, ActionCableHandlerOptions } from "./createActionCableHandler" import { createPusherHandler, PusherHandlerOptions } from "./createPusherHandler" import { createAblyHandler, AblyHandlerOptions } from "./createAblyHandler" /** * Transport-agnostic wrapper for Relay Modern subscription handlers. * @example Add ActionCable subscriptions * var subscriptionHandler = createHandler({ * cable: cable, * operations: OperationStoreClient, * }) * var network = Network.create(fetchQuery, subscriptionHandler) * @param {ActionCable.Consumer} options.cable - A consumer from `.createConsumer` * @param {Pusher} options.pusher - A Pusher client * @param {Ably.Realtime} options.ably - An Ably client * @param {OperationStoreClient} options.operations - A generated `OperationStoreClient` for graphql-pro's OperationStore * @return {Function} A handler for a Relay Modern network */ function createHandler(options: ActionCableHandlerOptions | PusherHandlerOptions | AblyHandlerOptions) { if (!options) { return null } var handler if ((options as ActionCableHandlerOptions).cable) { handler = createActionCableHandler(options as ActionCableHandlerOptions) } else if ((options as PusherHandlerOptions).pusher) { handler = createPusherHandler(options as PusherHandlerOptions) } else if ((options as AblyHandlerOptions).ably) { handler = createAblyHandler(options as AblyHandlerOptions) } return handler } export default createHandler graphql-ruby-1.11.10/javascript_client/src/subscriptions/createPusherHandler.ts000066400000000000000000000030051414121453000277630ustar00rootroot00000000000000import { Pusher } from "pusher-js" // TODO: // - end-to-end test // - extract update code, inject it as a function? interface PusherHandlerOptions { pusher: Pusher, fetchOperation: Function } function createPusherHandler(options: PusherHandlerOptions) { var pusher = options.pusher var fetchOperation = options.fetchOperation return function (operation: object, variables: object, cacheConfig: object, observer: { onNext: Function, onError: Function, onCompleted: Function}) { var channelName: string // POST the subscription like a normal query fetchOperation(operation, variables, cacheConfig).then(function(response: { headers: { get: Function } }) { channelName = response.headers.get("X-Subscription-ID") var channel = pusher.subscribe(channelName) // When you get an update from pusher, give it to Relay channel.bind("update", function(payload) { // TODO Extract this code // When we get a response, send the update to `observer` const result = payload.result if (result && result.errors) { // What kind of error stuff belongs here? observer.onError(result.errors) } else if (result) { observer.onNext({data: result.data}) } if (!payload.more) { // Subscription is finished observer.onCompleted() } }) }) return { dispose: function() { pusher.unsubscribe(channelName) } } } } export { createPusherHandler, PusherHandlerOptions } graphql-ruby-1.11.10/javascript_client/src/subscriptions/registry.ts000066400000000000000000000017031414121453000257060ustar00rootroot00000000000000interface ApolloSubscription { unsubscribe: Function } // State management for subscriptions. // Used to add subscriptions to an Apollo network intrface. class ApolloSubscriptionRegistry { // Apollo expects unique ids to reference each subscription, // here's a simple incrementing ID generator which starts at 1 // (so it's always truthy) _id: number // for unsubscribing when Apollo asks us to _subscriptions: {[key: number]: ApolloSubscription} constructor() { this._id = 1 this._subscriptions = {} } add(subscription: ApolloSubscription): number { var id = this._id++ this._subscriptions[id] = subscription return id } unsubscribe(id: number): void { var subscription = this._subscriptions[id] if (!subscription) { throw new Error("No subscription found for id: " + id) } subscription.unsubscribe() delete this._subscriptions[id] } } export default new ApolloSubscriptionRegistry graphql-ruby-1.11.10/javascript_client/src/sync/000077500000000000000000000000001414121453000215325ustar00rootroot00000000000000graphql-ruby-1.11.10/javascript_client/src/sync/__tests__/000077500000000000000000000000001414121453000234705ustar00rootroot00000000000000graphql-ruby-1.11.10/javascript_client/src/sync/__tests__/__snapshots__/000077500000000000000000000000001414121453000263065ustar00rootroot00000000000000graphql-ruby-1.11.10/javascript_client/src/sync/__tests__/__snapshots__/generateClientTest.ts.snap000066400000000000000000000057661414121453000334250ustar00rootroot00000000000000// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`returns generated code 1`] = ` " /** * Generated by graphql-ruby-client * */ /** * Map local operation names to persisted keys on the server * @return {Object} * @private */ var _aliases = { \\"GetStuff\\": \\"f7f65309043352183e905e1396e51078\\" } /** * The client who synced these operations with the server * @return {String} * @private */ var _client = \\"test-client\\" var OperationStoreClient = { /** * Build a string for \`params[:operationId]\` * @param {String} operationName * @return {String} stored operation ID */ getOperationId: function(operationName) { return _client + \\"/\\" + OperationStoreClient.getPersistedQueryAlias(operationName) }, /** * Fetch a persisted alias from a local operation name * @param {String} operationName * @return {String} persisted alias */ getPersistedQueryAlias: function(operationName) { var persistedAlias = _aliases[operationName] if (!persistedAlias) { throw new Error(\\"Failed to find persisted alias for operation name: \\" + operationName) } else { return persistedAlias } }, /** * Satisfy the Apollo Link API. * This link checks for an operation name, and if it's present, * sets the HTTP context to _not_ include the query, * and instead, include \`extensions.operationId\`. * (This is inspired by apollo-link-persisted-queries.) */ apolloLink: function(operation, forward) { if (operation.operationName) { const operationId = OperationStoreClient.getOperationId(operation.operationName) operation.setContext({ http: { includeQuery: false, includeExtensions: true, } }) operation.extensions.operationId = operationId } return forward(operation) }, /** * Satisfy the Apollo middleware API. * Replace the query with an operationId */ apolloMiddleware: { applyBatchMiddleware: function(options, next) { options.requests.forEach(function(req) { // Fetch the persisted alias for this operation req.operationId = OperationStoreClient.getOperationId(req.operationName) // Remove the now-unused query string delete req.query return req }) // Continue the request next() }, applyMiddleware: function(options, next) { var req = options.request // Fetch the persisted alias for this operation req.operationId = OperationStoreClient.getOperationId(req.operationName) // Remove the now-unused query string delete req.query // Continue the request next() } } } module.exports = OperationStoreClient " `; generateJsonClientTest.ts.snap000066400000000000000000000004631414121453000341650ustar00rootroot00000000000000graphql-ruby-1.11.10/javascript_client/src/sync/__tests__/__snapshots__// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`generates a valid json object string that maps names to operations 1`] = ` "{ \\"a\\": \\"b\\", \\"c-d\\": \\"e-f\\" }" `; exports[`generates a valid json object string that maps names to operations 2`] = ` Object { "a": "b", "c-d": "e-f", } `; prepareIsolatedFilesTest.ts.snap000066400000000000000000000017331414121453000345110ustar00rootroot00000000000000graphql-ruby-1.11.10/javascript_client/src/sync/__tests__/__snapshots__// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`builds out single operations 1`] = ` Array [ Object { "alias": "", "body": "query GetStuffIsolated { ...FragIsolated things { existHere } } fragment FragIsolated on Query { evenMoreStuff { stuffInside } } ", "name": "GetStuffIsolated", }, Object { "alias": "", "body": "query GetStuffIsolated2 { things { existHere } } ", "name": "GetStuffIsolated2", }, ] `; exports[`with --add-typename builds out single operations with __typename fields 1`] = ` Array [ Object { "alias": "", "body": "query GetStuffIsolated { ...FragIsolated things { existHere __typename } } fragment FragIsolated on Query { evenMoreStuff { stuffInside __typename } } ", "name": "GetStuffIsolated", }, Object { "alias": "", "body": "query GetStuffIsolated2 { things { existHere __typename } } ", "name": "GetStuffIsolated2", }, ] `; graphql-ruby-1.11.10/javascript_client/src/sync/__tests__/__snapshots__/prepareProjectTest.ts.snap000066400000000000000000000017021414121453000334430ustar00rootroot00000000000000// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`merging a project builds out separate operations 1`] = ` Array [ Object { "alias": "", "body": "query GetStuff2 { stuff ...Frag1 ...Frag2 } fragment Frag1 on Query { moreStuff } fragment Frag2 on Query { ...Frag3 } fragment Frag3 on Query { evenMoreStuff } ", "name": "GetStuff2", }, Object { "alias": "", "body": "query GetStuff { ...Frag1 } fragment Frag1 on Query { moreStuff } ", "name": "GetStuff", }, ] `; exports[`merging a project with --add-typename builds out operation with __typename fields 1`] = ` Array [ Object { "alias": "", "body": "query GetStuff3 { stuff { withStuffInside __typename } ...Frag2 ...Frag4 } fragment Frag2 on Query { ...Frag3 } fragment Frag3 on Query { evenMoreStuff } fragment Frag4 on Query { evenMoreStuff { stuffInside __typename } } ", "name": "GetStuff3", }, ] `; graphql-ruby-1.11.10/javascript_client/src/sync/__tests__/addTypenameToSelectionSetTest.ts000066400000000000000000000006671414121453000317710ustar00rootroot00000000000000import {addTypenameToSelectionSet} from "../addTypenameToSelectionSet" import { parse, print } from "graphql" describe("adding typename", () => { it("adds to fields and inline fragments", () => { var doc = parse("{ a { b ... { c } } }") var newDoc = addTypenameToSelectionSet(doc) var newString = print(newDoc).replace(/\s+/g, " ").trim() expect(newString).toEqual("{ a { b ... { c __typename } __typename } }") }) }) graphql-ruby-1.11.10/javascript_client/src/sync/__tests__/generateClientTest.ts000066400000000000000000000003611414121453000276310ustar00rootroot00000000000000import { generateClient } from "../generateClient" it("returns generated code", function() { var code = generateClient({ path: "./src/__tests__/documents/*.graphql", client: "test-client", }) expect(code).toMatchSnapshot() }) graphql-ruby-1.11.10/javascript_client/src/sync/__tests__/generateJsClientTest.ts000066400000000000000000000044021414121453000301260ustar00rootroot00000000000000import { generateClientCode, JS_TYPE, OperationStoreClient } from "../generateClient" import fs from "fs" function withExampleClient(mapName: string, callback: (client: OperationStoreClient) => void) { // Generate some code and write it to a file var exampleOperations = [ {name: "a", alias: "b", body: ""}, {name: "c-d", alias: "e-f", body: ""} ] var jsCode = generateClientCode("example-client", exampleOperations, JS_TYPE) var filename = "./src/sync/__tests__/" + mapName + ".js" fs.writeFileSync(filename, jsCode) // Load the module and use it var exampleModule = require("./" + mapName) callback(exampleModule) // Clean up the generated file fs.unlinkSync(filename) } it("generates a valid JavaScript module that maps names to operations", () => { withExampleClient("map1", (exampleClient) => { // It does the mapping expect(exampleClient.getPersistedQueryAlias("a")).toEqual("b") expect(exampleClient.getPersistedQueryAlias("c-d")).toEqual("e-f") // It returns a param expect(exampleClient.getOperationId("a")).toEqual("example-client/b") }) }) it("generates an Apollo middleware", () => { withExampleClient("map2", (exampleClient) => { var nextWasCalled = false var next = () => { nextWasCalled = true } var req = { operationName: "a", query: "x", operationId: "", } exampleClient.apolloMiddleware.applyMiddleware({request: req}, next) expect(nextWasCalled).toEqual(true) expect(req.query).toBeUndefined() expect(req.operationId).toEqual("example-client/b") }) }) it("generates an Apollo Link", () => { var fakeOperation = { operationName: "a", context: { http: {} }, setContext: function(c: { http: object }) { this.context = c }, extensions: { operationId: "", }, } var forwardedOperation: object var fakeForward = function(operation: object) { forwardedOperation = operation } withExampleClient("map3", (exampleClient) => { exampleClient.apolloLink(fakeOperation, fakeForward) expect(fakeOperation.extensions.operationId).toEqual("example-client/b") expect(fakeOperation.context.http).toEqual({includeQuery: false, includeExtensions: true}) expect(forwardedOperation).toEqual(fakeOperation) }) }) graphql-ruby-1.11.10/javascript_client/src/sync/__tests__/generateJsonClientTest.ts000066400000000000000000000012641414121453000304660ustar00rootroot00000000000000import { generateClientCode, JSON_TYPE } from "../generateClient" function withExampleClient(callback: (client: string) => void) { // Generate some code and write it to a file var exampleOperations = [ {name: "a", alias: "b", body: ""}, {name: "c-d", alias: "e-f", body: ""} ] var json = generateClientCode("example-client", exampleOperations, JSON_TYPE) // Run callback with generated client callback(json) } it("generates a valid json object string that maps names to operations", () => { withExampleClient((json) => { expect(json).toMatchSnapshot() // String version expect(JSON.parse(json)).toMatchSnapshot() // Object version (i.e., valid JSON) }) }) graphql-ruby-1.11.10/javascript_client/src/sync/__tests__/prepareIsolatedFilesTest.ts000066400000000000000000000012241414121453000310050ustar00rootroot00000000000000import prepareIsolatedFiles from "../prepareIsolatedFiles" it("builds out single operations", () => { var filenames = [ "./src/__tests__/project/op_isolated_1.graphql", "./src/__tests__/project/op_isolated_2.graphql", ] var ops = prepareIsolatedFiles(filenames, false) expect(ops).toMatchSnapshot() }) describe("with --add-typename", () => { it("builds out single operations with __typename fields", () => { var filenames = [ "./src/__tests__/project/op_isolated_1.graphql", "./src/__tests__/project/op_isolated_2.graphql", ] var ops = prepareIsolatedFiles(filenames, true) expect(ops).toMatchSnapshot() }) }) graphql-ruby-1.11.10/javascript_client/src/sync/__tests__/prepareProjectTest.ts000066400000000000000000000025101414121453000276630ustar00rootroot00000000000000import prepareProject from "../prepareProject" describe("merging a project", () => { it("builds out separate operations", () => { var filenames = [ "./src/__tests__/project/op_2.graphql", "./src/__tests__/project/op_1.graphql", "./src/__tests__/project/frag_1.graphql", "./src/__tests__/project/frag_2.graphql", "./src/__tests__/project/frag_3.graphql", ] var ops = prepareProject(filenames, false) expect(ops).toMatchSnapshot() }) describe("with --add-typename", () => { it("builds out operation with __typename fields", () => { var filenames = [ "./src/__tests__/project/op_3.graphql", "./src/__tests__/project/frag_2.graphql", "./src/__tests__/project/frag_3.graphql", "./src/__tests__/project/frag_4.graphql", ] var ops = prepareProject(filenames, true) expect(ops).toMatchSnapshot() }) }) it("blows up on duplicate names", () => { var filenames = [ "./src/__tests__/documents/doc1.graphql", "./src/__tests__/project/op_2.graphql", "./src/__tests__/project/op_1.graphql", "./src/__tests__/project/frag_1.graphql", ] expect(() => { prepareProject(filenames, false) }).toThrow("Found duplicate definition name: GetStuff, fragment & operation names must be unique to sync") }) }) graphql-ruby-1.11.10/javascript_client/src/sync/__tests__/sendPayloadTest.ts000066400000000000000000000046261414121453000271530ustar00rootroot00000000000000jest.dontMock('nock'); import nock from "nock" import sendPayload from "../sendPayload" describe("Posting GraphQL to OperationStore Endpoint", () => { it("Posts to the specified URL", () => { var mock = nock("http://example.com") .post("/stored_operations/sync") .reply(200, { "ok" : "ok" }) return sendPayload("payload", { url: "http://example.com/stored_operations/sync" }).then(function() { expect(mock.isDone()).toEqual(true) }) }) it("Uses HTTPS when provided", () => { var mock = nock("https://example2.com") .post("/stored_operations/sync") .reply(200, { "ok" : "ok" }) return sendPayload("payload", { url: "https://example2.com/stored_operations/sync" }).then(function() { expect(mock.isDone()).toEqual(true) }) }) it("Uses auth, port, and query", () => { var mock = nock("https://example2.com:229") .post("/stored_operations/sync?q=1") .basicAuth({ user: "username", pass: "pass" }) .reply(200, { "ok" : "ok" }) return sendPayload("payload", { url: "https://username:pass@example2.com:229/stored_operations/sync?q=1" }).then(function() { expect(mock.isDone()).toEqual(true) }) }) it("Returns the response JSON to the promise", () => { nock("http://example.com") .post("/stored_operations/sync") .reply(200, { result: "ok" }) return sendPayload("payload", { url: "http://example.com/stored_operations/sync" }).then(function(response) { expect(response).toEqual('{"result":"ok"}') }) }) it("Adds an hmac-sha256 header if key is present", () => { var payload = { "payload": [1,2,3] } var key = "2f26b770ded2a04279bc4bf824ca54ac" // ruby -ropenssl -e 'puts OpenSSL::HMAC.hexdigest("SHA256", "2f26b770ded2a04279bc4bf824ca54ac", "{\"payload\":[1,2,3]}")' // f6eab31abc2fa446dbfd2e9c10a778aaffd4d0c1d62dd9513d6f7ea60557987c var signature = "f6eab31abc2fa446dbfd2e9c10a778aaffd4d0c1d62dd9513d6f7ea60557987c" var mock = nock("http://example.com", { reqheaders: { 'authorization': 'GraphQL::Pro Abc ' + signature }}) .post("/stored_operations/sync") .reply(200, { result: "ok" }) var opts = {secret: key, client: "Abc", url: "http://example.com/stored_operations/sync"} return sendPayload(payload, opts).then(function(response) { expect(response).toEqual('{"result":"ok"}') expect(mock.isDone()).toEqual(true) }) }) }) graphql-ruby-1.11.10/javascript_client/src/sync/addTypenameToSelectionSet.ts000066400000000000000000000022461414121453000271660ustar00rootroot00000000000000import { visit, ASTNode, FieldNode, InlineFragmentNode } from "graphql" const TYPENAME_FIELD: FieldNode = { kind: "Field", name: { kind: "Name", value: "__typename", }, selectionSet: { kind: "SelectionSet", selections: [] } } function addTypenameIfAbsent(node: FieldNode | InlineFragmentNode): undefined | FieldNode | InlineFragmentNode { if (node.selectionSet) { const alreadyHasThisField = node.selectionSet.selections.some(function(selection) { return ( selection.kind === "Field" && selection.name.value === "__typename" ) }) if (!alreadyHasThisField) { return { ...node, selectionSet: { ...node.selectionSet, selections: [...node.selectionSet.selections, TYPENAME_FIELD] } } } else { return undefined } } else { return undefined } } function addTypenameToSelectionSet(node: ASTNode) { var visitor = { Field: { leave: addTypenameIfAbsent, }, InlineFragment: { leave: addTypenameIfAbsent, } } var newNode = visit(node, visitor) return newNode } export { addTypenameToSelectionSet, addTypenameIfAbsent } graphql-ruby-1.11.10/javascript_client/src/sync/generateClient.ts000066400000000000000000000116151414121453000250370ustar00rootroot00000000000000import glob from "glob" import prepareRelay from "./prepareRelay" import prepareIsolatedFiles from './prepareIsolatedFiles' import prepareProject from "./prepareProject" import md5 from "./md5" import generateJs from "./outfileGenerators/js" import generateJson from "./outfileGenerators/json" var JS_TYPE = "js"; var JSON_TYPE = "json"; var generators = { [JS_TYPE]: generateJs, [JSON_TYPE]: generateJson, }; interface GenerateClientCodeOptions { path?: string // A glob to recursively search for `.graphql` files (Default is `./`) mode?: string // If `"file"`, treat each file separately. If `"project"`, concatenate all files and extract each operation. If `"relay"`, treat it as relay-compiler output addTypename?: boolean // Indicates if the "__typename" field are automatically added to your queries clientType?: string // The type of the generated code (i.e., json, js) client: string // the Client ID that these operations belong to hash?: Function // A custom hash function for query strings with the signature `options.hash(string) => digest` (Default is `md5(string) => digest`) verbose?: boolean // If true, print debug output } interface OperationStoreClient { getOperationId: (operationName: string) => string getPersistedQueryAlias: (operationName: string) => string apolloMiddleware: { applyMiddleware: (req: any, next: any) => any } apolloLink: (operation: any, forward: any) => any } /** * Generate a JavaScript client module based on local `.graphql` files. * * See {gatherOperations} and {generateClientCode} for options. * @return {String} The generated JavaScript code */ function generateClient(options: GenerateClientCodeOptions): string { var payload = gatherOperations(options) var generatedCode = generateClientCode(options.client, payload.operations, options.clientType) return generatedCode } interface ClientOperation { alias: string, name?: string, body: string, } /** * Parse files in the specified path and generate an alias for each operation. */ function gatherOperations(options: GenerateClientCodeOptions) { var graphqlGlob = options.path || "./" // Check for file ext already, add it if missing var containsFileExt = graphqlGlob.indexOf(".graphql") > -1 || graphqlGlob.indexOf(".gql") > -1 if (!containsFileExt) { graphqlGlob = graphqlGlob + "**/*.graphql*" } var hashFunc = options.hash || md5 var filesMode = options.mode || (graphqlGlob.indexOf("__generated__") > -1 ? "relay" : "project") var addTypename = !!options.addTypename var verbose = !!options.verbose var operations: ClientOperation[] = [] var filenames: string[] = glob.sync(graphqlGlob, {}) if (verbose) { console.log("[Sync] glob: ", graphqlGlob) console.log("[Sync] " + filenames.length + " files:") console.log(filenames.map(function(f) { return "[Sync] - " + f }).join("\n")) } if (filesMode == "relay") { operations = prepareRelay(filenames) } else { if (filesMode === "file") { operations = prepareIsolatedFiles(filenames, addTypename) } else if (filesMode === "project") { operations = prepareProject(filenames, addTypename) } else { throw new Error("Unexpected mode: " + filesMode) } // Update the operations with the hash of the body operations.forEach(function(op) { op.alias = hashFunc(op.body) }) } return { operations: operations } } /** * Given a map of { name => alias } pairs, generate outfile based on type. * @param {String} clientName - the client ID that this map belongs to * @param {Object} nameToAlias - `name => alias` pairs * @param {String} type - the outfile's type * @return {String} generated outfile code */ function generateClientCode(clientName: string, operations: ClientOperation[], type?: string): string { if (!clientName) { throw new Error("Client name is required to generate a persisted alias lookup map"); } var nameToAlias: {[key: string] : string | null} = {} operations.forEach(function(op) { // This can be blank from relay-perisisted-output, // but typescript doesn't know that we don't use this function in that case // (Er, I should make _two_ interfaces, but I haven't yet.) if (op.name) { nameToAlias[op.name] = op.alias } }) // Build up the map var keyValuePairs = "{" keyValuePairs += Object.keys(nameToAlias).map(function(operationName) { var persistedAlias = nameToAlias[operationName] return "\n \"" + operationName + "\": \"" + persistedAlias + "\"" }).join(",") keyValuePairs += "\n}" var outfileType = type || JS_TYPE var generateOutfile = generators[outfileType]; if (!generateOutfile) { throw new Error("Unknown generator type " + outfileType + " encountered for generating the outFile"); } return generateOutfile(outfileType, clientName, keyValuePairs); } export { generateClient, generateClientCode, gatherOperations, JS_TYPE, JSON_TYPE, ClientOperation, OperationStoreClient, } graphql-ruby-1.11.10/javascript_client/src/sync/index.ts000066400000000000000000000210061414121453000232100ustar00rootroot00000000000000import sendPayload from "./sendPayload" import { generateClientCode, gatherOperations, ClientOperation } from "./generateClient" import Logger from "./logger" import fs from "fs" interface SyncOptions { path?: string, relayPersistedOutput?: string, apolloAndroidOperationOutput?: string, secret?: string url?: string, mode?: string, outfile?: string, outfileType?: string, client: string, send?: Function, hash?: Function, verbose?: boolean, quiet?: boolean, addTypename?: boolean, } /** * Find `.graphql` files in `path`, * then prepare them & send them to the configured endpoint. * * @param {Object} options * @param {String} options.path - A glob to recursively search for `.graphql` files (Default is `./`) * @param {String} options.relayPersistedOutput - A path to a `.json` file from `relay-compiler`'s `--persist-output` option * @param {String} options.secret - HMAC-SHA256 key which must match the server secret (default is no encryption) * @param {String} options.url - Target URL for sending prepared queries. If omitted, then an outfile is generated without sending operations to the server. * @param {String} options.mode - If `"file"`, treat each file separately. If `"project"`, concatenate all files and extract each operation. If `"relay"`, treat it as relay-compiler output * @param {Boolean} options.addTypename - Indicates if the "__typename" field are automatically added to your queries * @param {String} options.outfile - Where the generated code should be written * @param {String} options.outfileType - The type of the generated code (i.e., json, js) * @param {String} options.client - the Client ID that these operations belong to * @param {Function} options.send - A function for sending the payload to the server, with the signature `options.send(payload)`. (Default is an HTTP `POST` request) * @param {Function} options.hash - A custom hash function for query strings with the signature `options.hash(string) => digest` (Default is `md5(string) => digest`) * @param {Boolean} options.verbose - If true, log debug output * @return {Promise} Rejects with an Error or String if something goes wrong. Resolves with the operation payload if successful. */ function sync(options: SyncOptions) { var logger = new Logger(!!options.quiet) var verbose = !!options.verbose var url = options.url if (!url) { logger.log("No URL; Generating artifacts without syncing them") } var clientName = options.client if (!clientName) { throw new Error("Client name must be provided for sync") } var encryptionKey = options.secret if (encryptionKey) { logger.log("Authenticating with HMAC") } var graphqlGlob = options.path var hashFunc = options.hash var sendFunc = options.send || sendPayload var gatherMode = options.mode var clientType = options.outfileType if (options.relayPersistedOutput) { // relay-compiler has already generated an artifact for us var payload: { operations: ClientOperation[] } = { operations: [] } var relayOutputText = fs.readFileSync(options.relayPersistedOutput, "utf8") var relayOutput = JSON.parse(relayOutputText) var operationBody for (var hash in relayOutput) { operationBody = relayOutput[hash] payload.operations.push({ body: operationBody, alias: hash, }) } } else if (options.apolloAndroidOperationOutput) { // Apollo Android has already generated an artifact (https://www.apollographql.com/docs/android/advanced/persisted-queries/#operationoutputjson) var payload: { operations: ClientOperation[] } = { operations: [] } var apolloAndroidOutputText = fs.readFileSync(options.apolloAndroidOperationOutput, "utf8") var apolloAndroidOutput = JSON.parse(apolloAndroidOutputText) var operationData // Structure is { operationId => { "name" => "...", "source" => "query { ... } " } } for (var operationId in apolloAndroidOutput) { operationData = apolloAndroidOutput[operationId] payload.operations.push({ body: operationData.source, alias: operationId, }) } } else { var payload = gatherOperations({ path: graphqlGlob, hash: hashFunc, mode: gatherMode, addTypename: !!options.addTypename, clientType: clientType, client: clientName, verbose: verbose, }) } var outfile: string | null if (options.outfile) { outfile = options.outfile } else if (options.relayPersistedOutput || options.apolloAndroidOperationOutput) { // These artifacts have embedded IDs in its generated files, // no need to generate an outfile. outfile = null } else if (fs.existsSync("src")) { outfile = "src/OperationStoreClient.js" } else { outfile = "OperationStoreClient.js" } var syncPromise = new Promise(function(resolve, reject) { if (payload.operations.length === 0) { logger.log("No operations found in " + options.path + ", not syncing anything") resolve(null) return } else if(!url) { // This is a local-only run to generate an artifact resolve(payload) return } else { logger.log("Syncing " + payload.operations.length + " operations to " + logger.bright(url) + "...") var sendOpts = { url: url, client: clientName, secret: encryptionKey, verbose: verbose, } var sendPromise = Promise.resolve(sendFunc(payload, sendOpts)) return sendPromise.then(function(response) { var responseData if (response) { try { responseData = JSON.parse(response) var aliasToNameMap: {[key: string] : string | undefined} = {} payload.operations.forEach(function(op) { aliasToNameMap[op.alias] = op.name }) var failed = responseData.failed.length // These might get overriden for status output var notModified = responseData.not_modified.length var added = responseData.added.length if (failed) { // Override these to reflect reality notModified = 0 added = 0 } var addedColor = added ? "green" : "dim" logger.log(" " + logger.colorize(addedColor, added + " added")) var notModifiedColor = notModified ? "reset" : "dim" logger.log(" " + logger.colorize(notModifiedColor, notModified + " not modified")) var failedColor = failed ? "red" : "dim" logger.log(" " + logger.colorize(failedColor, failed + " failed")) if (failed) { logger.error("Sync failed, errors:") var failedOperationAlias: string var failedOperationName: string var errors var allErrors: string[] = [] for (failedOperationAlias in responseData.errors) { failedOperationName = aliasToNameMap[failedOperationAlias] || failedOperationAlias logger.error(" " + failedOperationName + ":") errors = responseData.errors[failedOperationAlias] errors.forEach(function(errMessage: string) { allErrors.push(failedOperationName + ": " + errMessage) logger.error(" " + logger.red("✘") + " " + errMessage) }) } reject("Sync failed: " + allErrors.join(", ")) return } } catch (err) { logger.log("Failed to print sync result:", err) reject(err) return } } resolve(payload) return }).catch(function(err) { logger.error(logger.red("Sync failed:")) logger.error(err) reject(err) return }) } }) return syncPromise.then(function(_payload) { // The payload is yielded when sync was successful, but typescript had // trouble using it from ^^ here. So instead, just use its presence as a signal to continue. // Don't generate a new file when we're using relay-comipler's --persist-output if (_payload && outfile) { var generatedCode = generateClientCode(clientName, payload.operations, clientType) var finishedPayload = { operations: payload.operations, generatedCode, } logger.log("Generating client module in " + logger.colorize("bright", outfile) + "...") fs.writeFileSync(outfile, generatedCode, "utf8") logger.log(logger.green("✓ Done!")) return finishedPayload } else { logger.log(logger.green("✓ Done!")) return payload } }) } export default sync graphql-ruby-1.11.10/javascript_client/src/sync/logger.ts000066400000000000000000000017441414121453000233670ustar00rootroot00000000000000class Logger { isQuiet: boolean constructor(isQuiet: boolean) { this.isQuiet = isQuiet } log(...args: string[]) { this.isQuiet ? null : console.log(...args) } error(...args: string[]) { this.isQuiet ? null : console.error(...args) } colorize(color: string, text: string) { var prefix = colors[color] if (!prefix) { throw new Error("No color named: " + color) } return prefix + text + colors.reset } // Shortcuts to `.colorize`, add more as-needed. red(text: string) { return this.colorize("red", text) } green(text: string) { return this.colorize("green", text) } bright(text: string) { return this.colorize("bright", text) } } const colors: {[key: string]: string} = { yellow: "\x1b[33m", red: "\x1b[31m", green: "\x1b[32m", blue: "\x1b[34m", magenta: "\x1b[35m", cyan: "\x1b[36m", reset: "\x1b[0m", bright: "\x1b[1m", dim: "\x1b[2m", } export default Logger graphql-ruby-1.11.10/javascript_client/src/sync/md5.ts000066400000000000000000000003411414121453000225650ustar00rootroot00000000000000import crypto from 'crypto' // Return the hex-encoded md5 hash of `inputString` function md5(inputString: string): string { return crypto.createHash("md5") .update(inputString) .digest("hex") } export default md5 graphql-ruby-1.11.10/javascript_client/src/sync/outfileGenerators/000077500000000000000000000000001414121453000252335ustar00rootroot00000000000000graphql-ruby-1.11.10/javascript_client/src/sync/outfileGenerators/js.ts000066400000000000000000000057521414121453000262300ustar00rootroot00000000000000function generateOutfile(_type: string, clientName: string, keyValuePairs: string) { return ` /** * Generated by graphql-ruby-client * */ /** * Map local operation names to persisted keys on the server * @return {Object} * @private */ var _aliases = ${keyValuePairs} /** * The client who synced these operations with the server * @return {String} * @private */ var _client = "${clientName}" var OperationStoreClient = { /** * Build a string for \`params[:operationId]\` * @param {String} operationName * @return {String} stored operation ID */ getOperationId: function(operationName) { return _client + "/" + OperationStoreClient.getPersistedQueryAlias(operationName) }, /** * Fetch a persisted alias from a local operation name * @param {String} operationName * @return {String} persisted alias */ getPersistedQueryAlias: function(operationName) { var persistedAlias = _aliases[operationName] if (!persistedAlias) { throw new Error("Failed to find persisted alias for operation name: " + operationName) } else { return persistedAlias } }, /** * Satisfy the Apollo Link API. * This link checks for an operation name, and if it's present, * sets the HTTP context to _not_ include the query, * and instead, include \`extensions.operationId\`. * (This is inspired by apollo-link-persisted-queries.) */ apolloLink: function(operation, forward) { if (operation.operationName) { const operationId = OperationStoreClient.getOperationId(operation.operationName) operation.setContext({ http: { includeQuery: false, includeExtensions: true, } }) operation.extensions.operationId = operationId } return forward(operation) }, /** * Satisfy the Apollo middleware API. * Replace the query with an operationId */ apolloMiddleware: { applyBatchMiddleware: function(options, next) { options.requests.forEach(function(req) { // Fetch the persisted alias for this operation req.operationId = OperationStoreClient.getOperationId(req.operationName) // Remove the now-unused query string delete req.query return req }) // Continue the request next() }, applyMiddleware: function(options, next) { var req = options.request // Fetch the persisted alias for this operation req.operationId = OperationStoreClient.getOperationId(req.operationName) // Remove the now-unused query string delete req.query // Continue the request next() } } } module.exports = OperationStoreClient ` } export default generateOutfile; graphql-ruby-1.11.10/javascript_client/src/sync/outfileGenerators/json.ts000066400000000000000000000002251414121453000265530ustar00rootroot00000000000000function generateOutfile(_type: string, _clientName: string, keyValuePairs: string) { return `${keyValuePairs}` } export default generateOutfile; graphql-ruby-1.11.10/javascript_client/src/sync/prepareIsolatedFiles.ts000066400000000000000000000026211414121453000262110ustar00rootroot00000000000000import fs from "fs" import {parse, visit, print, OperationDefinitionNode} from "graphql" import {addTypenameIfAbsent} from "./addTypenameToSelectionSet" /** * Read a bunch of GraphQL files and treat them as islands. * Don't join any fragments from other files. * Don't make assertions about name uniqueness. * */ function prepareIsolatedFiles(filenames: string[], addTypename: boolean) { return filenames.map(function(filename) { var fileOperationBody = fs.readFileSync(filename, "utf8") var fileOperationName = "" var ast = parse(fileOperationBody) var visitor = { OperationDefinition: { enter: function(node: OperationDefinitionNode) { if (fileOperationName.length > 0) { throw new Error("Found multiple operations in " + filename + ": " + fileOperationName + ", " + node.name + ". Files must contain only one operation") } else if (node.name && node.name.value) { fileOperationName = node.name.value } }, }, InlineFragment: { leave: addTypename ? addTypenameIfAbsent : () => {} }, Field: { leave: addTypename ? addTypenameIfAbsent : () => {} } } ast = visit(ast, visitor) return { // populate alias later, when hashFunc is available alias: "", name: fileOperationName, body: print(ast), } }) } export default prepareIsolatedFiles graphql-ruby-1.11.10/javascript_client/src/sync/prepareProject.ts000066400000000000000000000072061414121453000250740ustar00rootroot00000000000000import { addTypenameIfAbsent } from "./addTypenameToSelectionSet"; import fs from "fs" import {parse, visit, print, OperationDefinitionNode, FragmentDefinitionNode, FragmentSpreadNode, DocumentNode} from "graphql" /** * Take a whole bunch of GraphQL in one big string * and validate it, especially: * * - operation names are unique * - fragment names are unique * * Then, split each operation into a free-standing document, * so it has all the fragments it needs. */ function prepareProject(filenames: string[], addTypename: boolean) { if(!filenames.length) { return []; } var allGraphQL = "" filenames.forEach(function(filename) { allGraphQL += fs.readFileSync(filename) }) var ast = parse(allGraphQL) // This will contain { name: [name, name] } pairs var definitionDependencyNames: {[key: string] : string[] } = {} var allOperationNames: string[] = [] var currentDependencyNames = null // When entering a fragment or operation, // start recording its dependencies var enterDefinition = function(node: FragmentDefinitionNode | OperationDefinitionNode) { // Technically, it could be an anonymous definition if (node.name) { var definitionName = node.name.value if (definitionDependencyNames[definitionName]) { throw new Error("Found duplicate definition name: " + definitionName + ", fragment & operation names must be unique to sync") } else { currentDependencyNames = definitionDependencyNames[definitionName] = [] } } } var visitor = { OperationDefinition: { enter: function(node: OperationDefinitionNode) { enterDefinition(node) node.name && allOperationNames.push(node.name.value) }, }, FragmentDefinition: { enter: enterDefinition, }, // When entering a fragment spread, register it as a // dependency of its context FragmentSpread: { enter: function(node: FragmentSpreadNode) { currentDependencyNames.push(node.name.value) } }, Field: { leave: addTypename ? addTypenameIfAbsent : () => {} }, InlineFragment: { leave: addTypename ? addTypenameIfAbsent : () => {} } } // Find the dependencies, build the accumulator ast = visit(ast, visitor) // For each operation, build a separate document of that operation and its deps // then print the new document to a string var operations = allOperationNames.map(function(operationName) { var visitedDepNames: string[] = [] var depNamesToVisit = [operationName] var depName while (depNamesToVisit.length > 0) { depName = depNamesToVisit.shift() if (depName) { visitedDepNames.push(depName) definitionDependencyNames[depName].forEach(function(nextDepName) { if (visitedDepNames.indexOf(nextDepName) === -1) { depNamesToVisit.push(nextDepName) } }) } } var newAST = extractDefinitions(ast, visitedDepNames) return { name: operationName, body: print(newAST), alias: "", // will be filled in later, when hashFunc is available } }) return operations } // Return a new AST which contains only `definitionNames` function extractDefinitions(ast: DocumentNode, definitionNames: string[]) { var removeDefinitionNode = function(node: FragmentDefinitionNode | OperationDefinitionNode) { if (node.name && definitionNames.indexOf(node.name.value) === -1) { return null } else { return undefined } } var visitor = { OperationDefinition: removeDefinitionNode, FragmentDefinition: removeDefinitionNode, } var newAST = visit(ast, visitor) return newAST } export default prepareProject graphql-ruby-1.11.10/javascript_client/src/sync/prepareRelay.ts000066400000000000000000000035261414121453000245430ustar00rootroot00000000000000import path from "path" import fs from "fs" interface RelayCompilerOperation { params?: RelayCompilerOperation text: string name: string } /** * Read relay-compiler output * and extract info for persisting them & writing a map: * * - alias: get the relayHash from the header * - name: get the name from the JavaScript object * - body: get the text from the JavaScript object * * @param {Array} filenames - Filenames to read * @return {Array} List of operations to persist & write to a map */ function prepareRelay(filenames: string[]) { var currentDirectory = process.cwd() var operations = filenames.map(function(filename) { // Search the file for the relayHash var textContent = fs.readFileSync(filename, "utf8") var operationAlias = textContent.match(/@relayHash ([a-z0-9]+)/) // Only operations get `relayHash`, so // skip over generated fragments if (operationAlias) { // Require the file to get values from the JavaScript code var absoluteFilename = path.resolve(currentDirectory, filename) var operation: RelayCompilerOperation = require(absoluteFilename) var operationBody, operationName // Support Relay version ^2.0.0 if (operation.params) { operationBody = operation.params.text operationName = operation.params.name } else { // Support Relay versions < 2.0.0 operationBody = operation.text operationName = operation.name } return { alias: operationAlias[1], name: operationName, body: operationBody, } } else { return { alias: "", name: "not-found", body: "not-found", } } }) // Remove the nulls var operationsWithoutNulls = operations.filter(function(o) { return o.alias.length }) return operationsWithoutNulls } export default prepareRelay graphql-ruby-1.11.10/javascript_client/src/sync/sendPayload.ts000066400000000000000000000056501414121453000243530ustar00rootroot00000000000000import http from "http" import https from "https" import url from "url" import crypto from 'crypto' interface SendPayloadOptions { url: string, secret?: string, client?: string, verbose?: boolean } /** * Use HTTP POST to send this payload to the endpoint. * * Override this function with `options.send` to use custom auth. * * @private * @param {Object} payload - JS object to be posted as form data * @param {String} options.url - Target URL * @param {String} options.secret - (optional) used for HMAC header if provided * @param {String} options.client - (optional) used for HMAC header if provided * @param {Boolean} options.verbose - (optional) if true, print extra info for debugging * @return {Promise} */ function sendPayload(payload: any, options: SendPayloadOptions) { var syncUrl = options.url var key = options.secret var clientName = options.client var verbose = options.verbose // Prepare JS object as form data var postData = JSON.stringify(payload) // Get parts of URL for request options var parsedURL = url.parse(syncUrl) // Prep options for HTTP request var defaultHeaders: {[key: string]: string} = { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData).toString() } var httpOptions = { protocol: parsedURL.protocol, hostname: parsedURL.hostname, port: parsedURL.port, path: parsedURL.path, auth: parsedURL.auth, method: 'POST', headers: defaultHeaders, }; // If an auth key was provided, add a HMAC header var authDigest = null if (key) { authDigest = crypto.createHmac('sha256', key) .update(postData) .digest('hex') var header = "GraphQL::Pro " + clientName + " " + authDigest if (verbose) { console.log("[Sync] Header: ", header) console.log("[Sync] Data:", postData) } httpOptions.headers["Authorization"] = header } var httpClient = parsedURL.protocol === "https:" ? https : http var promise = new Promise(function(resolve, reject) { // Make the request, // hook up response handler const req = httpClient.request(httpOptions, (res) => { res.setEncoding('utf8'); // Gather the response from the server var body = "" res.on('data', (chunk) => { body += chunk }); res.on("end", () => { if (verbose) { console.log("[Sync] Response Headers: ", res.headers) console.log("[Sync] Response Body: ", body) } var status = res.statusCode // 422 gets special treatment because // the body has error messages if (status && status > 299 && status != 422) { reject(" Server responded with " + res.statusCode) } else { resolve(body) } }) }); req.on('error', (e) => { reject(e) }); // Send the data, fire the request req.write(postData); req.end(); }) return promise } export default sendPayload graphql-ruby-1.11.10/javascript_client/tsconfig.json000066400000000000000000000133561414121453000225060ustar00rootroot00000000000000{ "compilerOptions": { /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ // "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ "outDir": "./dist", /* Redirect output structure to the directory. */ "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ // "removeComments": true, /* Do not emit comments to output. */ // "noEmit": true, /* Do not emit outputs. */ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ "noUnusedLocals": true, /* Report errors on unused locals. */ "noUnusedParameters": true, /* Report errors on unused parameters. */ "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ /* Source Map Options */ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } } graphql-ruby-1.11.10/lib/000077500000000000000000000000001414121453000150315ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/generators/000077500000000000000000000000001414121453000172025ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/generators/graphql/000077500000000000000000000000001414121453000206405ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/generators/graphql/core.rb000066400000000000000000000036441414121453000221240ustar00rootroot00000000000000# frozen_string_literal: true require 'rails/generators/base' module Graphql module Generators module Core def self.included(base) base.send( :class_option, :directory, type: :string, default: "app/graphql", desc: "Directory where generated files should be saved" ) end def insert_root_type(type, name) log :add_root_type, type sentinel = /< GraphQL::Schema\s*\n/m in_root do inject_into_file schema_file_path, " #{type}(Types::#{name})\n", after: sentinel, verbose: false, force: false end end def create_mutation_root_type create_dir("#{options[:directory]}/mutations") template("base_mutation.erb", "#{options[:directory]}/mutations/base_mutation.rb", { skip: true }) template("mutation_type.erb", "#{options[:directory]}/types/mutation_type.rb", { skip: true }) insert_root_type('mutation', 'MutationType') end def schema_file_path "#{options[:directory]}/#{schema_name.underscore}.rb" end def create_dir(dir) empty_directory(dir) if !options[:skip_keeps] create_file("#{dir}/.keep") end end def module_namespacing_when_supported if defined?(module_namespacing) module_namespacing { yield } else yield end end private def schema_name @schema_name ||= begin if options[:schema] options[:schema] else "#{parent_name}Schema" end end end def parent_name require File.expand_path("config/application", destination_root) if Rails.application.class.respond_to?(:module_parent_name) Rails.application.class.module_parent_name else Rails.application.class.parent_name end end end end end graphql-ruby-1.11.10/lib/generators/graphql/enum_generator.rb000066400000000000000000000017011414121453000241760ustar00rootroot00000000000000# frozen_string_literal: true require 'generators/graphql/type_generator' module Graphql module Generators # Generate an enum type by name, with the given values. # To add a `value:` option, add another value after a `:`. # # ``` # rails g graphql:enum ProgrammingLanguage RUBY PYTHON PERL PERL6:"PERL" # ``` class EnumGenerator < TypeGeneratorBase desc "Create a GraphQL::EnumType with the given name and values" source_root File.expand_path('../templates', __FILE__) argument :values, type: :array, default: [], banner: "value{:ruby_value} value{:ruby_value} ...", desc: "Values for this enum (if present, ruby_value will be inserted verbatim)" def create_type_file template "enum.erb", "#{options[:directory]}/types/#{type_file_name}.rb" end private def prepared_values values.map { |v| v.split(":", 2) } end end end end graphql-ruby-1.11.10/lib/generators/graphql/install_generator.rb000066400000000000000000000123711414121453000247050ustar00rootroot00000000000000# frozen_string_literal: true require 'rails/generators/base' require_relative 'core' module Graphql module Generators # Add GraphQL to a Rails app with `rails g graphql:install`. # # Setup a folder structure for GraphQL: # # ``` # - app/ # - graphql/ # - resolvers/ # - types/ # - base_argument.rb # - base_field.rb # - base_enum.rb # - base_input_object.rb # - base_interface.rb # - base_object.rb # - base_scalar.rb # - base_union.rb # - query_type.rb # - loaders/ # - mutations/ # - base_mutation.rb # - {app_name}_schema.rb # ``` # # (Add `.gitkeep`s by default, support `--skip-keeps`) # # Add a controller for serving GraphQL queries: # # ``` # app/controllers/graphql_controller.rb # ``` # # Add a route for that controller: # # ```ruby # # config/routes.rb # post "/graphql", to: "graphql#execute" # ``` # # Accept a `--relay` option which adds # The root `node(id: ID!)` field. # # Accept a `--batch` option which adds `GraphQL::Batch` setup. # # Use `--no-graphiql` to skip `graphiql-rails` installation. # # TODO: also add base classes class InstallGenerator < Rails::Generators::Base include Core desc "Install GraphQL folder structure and boilerplate code" source_root File.expand_path('../templates', __FILE__) class_option :schema, type: :string, default: nil, desc: "Name for the schema constant (default: {app_name}Schema)" class_option :skip_keeps, type: :boolean, default: false, desc: "Skip .keep files for source control" class_option :skip_graphiql, type: :boolean, default: false, desc: "Skip graphiql-rails installation" class_option :skip_mutation_root_type, type: :boolean, default: false, desc: "Skip creation of the mutation root type" class_option :relay, type: :boolean, default: false, desc: "Include GraphQL::Relay installation" class_option :batch, type: :boolean, default: false, desc: "Include GraphQL::Batch installation" class_option :playground, type: :boolean, default: false, desc: "Use GraphQL Playground over Graphiql as IDE" # These two options are taken from Rails' own generators' class_option :api, type: :boolean, desc: "Preconfigure smaller stack for API only apps" def create_folder_structure create_dir("#{options[:directory]}/types") template("schema.erb", schema_file_path) ["base_object", "base_argument", "base_field", "base_enum", "base_input_object", "base_interface", "base_scalar", "base_union"].each do |base_type| template("#{base_type}.erb", "#{options[:directory]}/types/#{base_type}.rb") end # Note: You can't have a schema without the query type, otherwise introspection breaks template("query_type.erb", "#{options[:directory]}/types/query_type.rb") insert_root_type('query', 'QueryType') create_mutation_root_type unless options.skip_mutation_root_type? template("graphql_controller.erb", "app/controllers/graphql_controller.rb") route('post "/graphql", to: "graphql#execute"') if options[:batch] gem("graphql-batch") create_dir("#{options[:directory]}/loaders") end if options.api? say("Skipped graphiql, as this rails project is API only") say(" You may wish to use GraphiQL.app for development: https://github.com/skevy/graphiql-app") elsif !options[:skip_graphiql] gem("graphiql-rails", group: :development) # This is a little cheat just to get cleaner shell output: log :route, 'graphiql-rails' shell.mute do # Rails 5.2 has better support for `route`? if Rails::VERSION::STRING > "5.2" route <<-RUBY if Rails.env.development? mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql" end RUBY else route <<-RUBY if Rails.env.development? mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql" end RUBY end end end if options[:playground] gem("graphql_playground-rails", group: :development) log :route, 'graphql_playground-rails' shell.mute do if Rails::VERSION::STRING > "5.2" route <<-RUBY if Rails.env.development? mount GraphqlPlayground::Rails::Engine, at: "/playground", graphql_path: "/graphql" end RUBY else route <<-RUBY if Rails.env.development? mount GraphqlPlayground::Rails::Engine, at: "/playground", graphql_path: "/graphql" end RUBY end end end if gemfile_modified? say "Gemfile has been modified, make sure you `bundle install`" end end private def gemfile_modified? @gemfile_modified end def gem(*args) @gemfile_modified = true super(*args) end end end end graphql-ruby-1.11.10/lib/generators/graphql/interface_generator.rb000066400000000000000000000014341414121453000251750ustar00rootroot00000000000000# frozen_string_literal: true require 'generators/graphql/type_generator' module Graphql module Generators # Generate an interface type by name, # with the specified fields. # # ``` # rails g graphql:interface NamedEntityType name:String! # ``` class InterfaceGenerator < TypeGeneratorBase desc "Create a GraphQL::InterfaceType with the given name and fields" source_root File.expand_path('../templates', __FILE__) argument :fields, type: :array, default: [], banner: "name:type name:type ...", desc: "Fields for this interface (type may be expressed as Ruby or GraphQL)" def create_type_file template "interface.erb", "#{options[:directory]}/types/#{type_file_name}.rb" end end end end graphql-ruby-1.11.10/lib/generators/graphql/loader_generator.rb000066400000000000000000000010621414121453000245000ustar00rootroot00000000000000# frozen_string_literal: true require "rails/generators/named_base" require_relative "core" module Graphql module Generators # @example Generate a `GraphQL::Batch` loader by name. # rails g graphql:loader RecordLoader class LoaderGenerator < Rails::Generators::NamedBase include Core desc "Create a GraphQL::Batch::Loader by name" source_root File.expand_path('../templates', __FILE__) def create_loader_file template "loader.erb", "#{options[:directory]}/loaders/#{file_path}.rb" end end end end graphql-ruby-1.11.10/lib/generators/graphql/mutation_generator.rb000066400000000000000000000033141414121453000250740ustar00rootroot00000000000000# frozen_string_literal: true require 'rails/generators/named_base' require_relative 'core' module Graphql module Generators # TODO: What other options should be supported? # # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name # rails g graphql:mutation CreatePostMutation class MutationGenerator < Rails::Generators::Base include Core desc "Create a Relay Classic mutation by name" source_root File.expand_path('../templates', __FILE__) argument :name, type: :string def initialize(args, *options) #:nodoc: # Unfreeze name in case it's given as a frozen string args[0] = args[0].dup if args[0].is_a?(String) && args[0].frozen? super assign_names!(name) end attr_reader :file_name, :mutation_name, :field_name def create_mutation_file unless @behavior == :revoke create_mutation_root_type else log :gsub, "#{options[:directory]}/types/mutation_type.rb" end template "mutation.erb", "#{options[:directory]}/mutations/#{file_name}.rb" sentinel = /class .*MutationType\s*<\s*[^\s]+?\n/m in_root do gsub_file "#{options[:directory]}/types/mutation_type.rb", / \# TODO\: Add Mutations as fields\s*\n/m, "" inject_into_file "#{options[:directory]}/types/mutation_type.rb", " field :#{field_name}, mutation: Mutations::#{mutation_name}\n", after: sentinel, verbose: false, force: false end end private def assign_names!(name) @field_name = name.camelize.underscore @mutation_name = name.camelize(:upper) @file_name = name.camelize.underscore end end end end graphql-ruby-1.11.10/lib/generators/graphql/object_generator.rb000066400000000000000000000041531414121453000245040ustar00rootroot00000000000000# frozen_string_literal: true require 'generators/graphql/type_generator' module Graphql module Generators # Generate an object type by name, # with the specified fields. # # ``` # rails g graphql:object PostType name:String! # ``` # # Add the Node interface with `--node`. class ObjectGenerator < TypeGeneratorBase desc "Create a GraphQL::ObjectType with the given name and fields" source_root File.expand_path('../templates', __FILE__) argument :custom_fields, type: :array, default: [], banner: "name:type name:type ...", desc: "Fields for this object (type may be expressed as Ruby or GraphQL)" class_option :node, type: :boolean, default: false, desc: "Include the Relay Node interface" def create_type_file template "object.erb", "#{options[:directory]}/types/#{type_file_name}.rb" end def fields columns = [] columns += klass.columns.map { |c| generate_column_string(c) } if class_exists? columns + custom_fields end def self.normalize_type_expression(type_expression, mode:, null: true) case type_expression when "Text" ["String", null] when "Decimal" ["Float", null] when "DateTime", "Datetime" ["GraphQL::Types::ISO8601DateTime", null] when "Date" ["GraphQL::Types::ISO8601Date", null] else super end end private def generate_column_string(column) name = column.name required = column.null ? "" : "!" type = column_type_string(column) "#{name}:#{required}#{type}" end def column_type_string(column) column.name == "id" ? "ID" : column.type.to_s.camelize end def class_exists? klass.is_a?(Class) && klass.ancestors.include?(ActiveRecord::Base) rescue NameError return false end def klass @klass ||= Module.const_get(type_name.camelize) end end end end graphql-ruby-1.11.10/lib/generators/graphql/scalar_generator.rb000066400000000000000000000010121414121453000244720ustar00rootroot00000000000000# frozen_string_literal: true require 'generators/graphql/type_generator' module Graphql module Generators # Generate a scalar type by given name. # # ``` # rails g graphql:scalar Date # ``` class ScalarGenerator < TypeGeneratorBase desc "Create a GraphQL::ScalarType with the given name" source_root File.expand_path('../templates', __FILE__) def create_type_file template "scalar.erb", "#{options[:directory]}/types/#{type_file_name}.rb" end end end end graphql-ruby-1.11.10/lib/generators/graphql/templates/000077500000000000000000000000001414121453000226365ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/generators/graphql/templates/base_argument.erb000066400000000000000000000001771414121453000261510ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> module Types class BaseArgument < GraphQL::Schema::Argument end end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/templates/base_enum.erb000066400000000000000000000001671414121453000252720ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> module Types class BaseEnum < GraphQL::Schema::Enum end end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/templates/base_field.erb000066400000000000000000000002401414121453000254010ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> module Types class BaseField < GraphQL::Schema::Field argument_class Types::BaseArgument end end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/templates/base_input_object.erb000066400000000000000000000002541414121453000270100ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> module Types class BaseInputObject < GraphQL::Schema::InputObject argument_class Types::BaseArgument end end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/templates/base_interface.erb000066400000000000000000000002561414121453000262650ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> module Types module BaseInterface include GraphQL::Schema::Interface field_class Types::BaseField end end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/templates/base_mutation.erb000066400000000000000000000004501414121453000261610ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> module Mutations class BaseMutation < GraphQL::Schema::RelayClassicMutation argument_class Types::BaseArgument field_class Types::BaseField input_object_class Types::BaseInputObject object_class Types::BaseObject end end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/templates/base_object.erb000066400000000000000000000002341414121453000255670ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> module Types class BaseObject < GraphQL::Schema::Object field_class Types::BaseField end end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/templates/base_scalar.erb000066400000000000000000000001731414121453000255700ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> module Types class BaseScalar < GraphQL::Schema::Scalar end end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/templates/base_union.erb000066400000000000000000000001711414121453000254510ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> module Types class BaseUnion < GraphQL::Schema::Union end end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/templates/enum.erb000066400000000000000000000003751414121453000243010ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> module Types class <%= type_ruby_name.split('::')[-1] %> < Types::BaseEnum <% prepared_values.each do |v| %> value "<%= v[0] %>"<%= v.length > 1 ? ", value: #{v[1]}" : "" %> <% end %> end end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/templates/graphql_controller.erb000066400000000000000000000030731414121453000272340ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> class GraphqlController < ApplicationController # If accessing from outside this domain, nullify the session # This allows for outside API access while preventing CSRF attacks, # but you'll have to authenticate your user separately # protect_from_forgery with: :null_session def execute variables = prepare_variables(params[:variables]) query = params[:query] operation_name = params[:operationName] context = { # Query context goes here, for example: # current_user: current_user, } result = <%= schema_name %>.execute(query, variables: variables, context: context, operation_name: operation_name) render json: result rescue => e raise e unless Rails.env.development? handle_error_in_development e end private # Handle variables in form data, JSON body, or a blank value def prepare_variables(variables_param) case variables_param when String if variables_param.present? JSON.parse(variables_param) || {} else {} end when Hash variables_param when ActionController::Parameters variables_param.to_unsafe_hash # GraphQL-Ruby will validate name and type of incoming variables. when nil {} else raise ArgumentError, "Unexpected parameter: #{variables_param}" end end def handle_error_in_development(e) logger.error e.message logger.error e.backtrace.join("\n") render json: { errors: [{ message: e.message, backtrace: e.backtrace }], data: {} }, status: 500 end end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/templates/interface.erb000066400000000000000000000003371414121453000252730ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> module Types module <%= type_ruby_name.split('::')[-1] %> include Types::BaseInterface <% normalized_fields.each do |f| %> <%= f.to_ruby %> <% end %> end end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/templates/loader.erb000066400000000000000000000007551414121453000246050ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> module Loaders class <%= class_name %> < GraphQL::Batch::Loader # Define `initialize` to store grouping arguments, eg # # Loaders::<%= class_name %>.for(group).load(value) # # def initialize() # end # `keys` contains each key from `.load(key)`. # Find the corresponding values, then # call `fulfill(key, value)` or `fulfill(key, nil)` # for each key. def perform(keys) end end end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/templates/mutation.erb000066400000000000000000000005661414121453000251770ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> module Mutations class <%= mutation_name %> < BaseMutation # TODO: define return fields # field :post, Types::PostType, null: false # TODO: define arguments # argument :name, String, required: true # TODO: define resolve method # def resolve(name:) # { post: ... } # end end end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/templates/mutation_type.erb000066400000000000000000000004451414121453000262340ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> module Types class MutationType < Types::BaseObject # TODO: remove me field :test_field, String, null: false, description: "An example field added by the generator" def test_field "Hello World" end end end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/templates/object.erb000066400000000000000000000004351414121453000246000ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> module Types class <%= type_ruby_name.split('::')[-1] %> < Types::BaseObject <% if options.node %> implements GraphQL::Relay::Node.interface <% end %><% normalized_fields.each do |f| %> <%= f.to_ruby %> <% end %> end end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/templates/query_type.erb000066400000000000000000000007271414121453000255440ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> module Types class QueryType < Types::BaseObject # Add root-level fields here. # They will be entry points for queries on your schema. # TODO: remove me field :test_field, String, null: false, description: "An example field added by the generator" def test_field "Hello World!" end <% if options[:relay] %> field :node, field: GraphQL::Relay::Node.field <% end %> end end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/templates/scalar.erb000066400000000000000000000007061414121453000246000ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> module Types class <%= type_ruby_name.split('::')[-1] %> < Types::BaseScalar def self.coerce_input(input_value, context) # Override this to prepare a client-provided GraphQL value for your Ruby code input_value end def self.coerce_result(ruby_value, context) # Override this to serialize a Ruby value for the GraphQL response ruby_value.to_s end end end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/templates/schema.erb000066400000000000000000000024521414121453000245730ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> class <%= schema_name %> < GraphQL::Schema query(Types::QueryType) # Opt in to the new runtime (default in future graphql-ruby versions) use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST # Add built-in connections for pagination use GraphQL::Pagination::Connections <% if options[:relay] %> # Relay Object Identification: # Return a string UUID for `object` def self.id_from_object(object, type_definition, query_ctx) # Here's a simple implementation which: # - joins the type name & object.id # - encodes it with base64: # GraphQL::Schema::UniqueWithinType.encode(type_definition.name, object.id) end # Given a string UUID, find the object def self.object_from_id(id, query_ctx) # For example, to decode the UUIDs generated above: # type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id) # # Then, based on `type_name` and `id` # find an object in your application # ... end # Object Resolution def self.resolve_type(type, obj, ctx) # TODO: Implement this function # to return the correct type for `obj` raise(GraphQL::RequiredImplementationMissingError) end <% end %><% if options[:batch] %> # GraphQL::Batch setup: use GraphQL::Batch <% end %>end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/templates/union.erb000066400000000000000000000003631414121453000244620ustar00rootroot00000000000000<% module_namespacing_when_supported do -%> module Types class <%= type_ruby_name.split('::')[-1] %> < Types::BaseUnion <% if possible_types.any? %> possible_types <%= normalized_possible_types.join(", ") %> <% end %> end end <% end -%> graphql-ruby-1.11.10/lib/generators/graphql/type_generator.rb000066400000000000000000000070141414121453000242160ustar00rootroot00000000000000# frozen_string_literal: true require 'rails/generators/base' require 'graphql' require 'active_support' require 'active_support/core_ext/string/inflections' require_relative 'core' module Graphql module Generators class TypeGeneratorBase < Rails::Generators::Base include Core argument :type_name, type: :string, required: true, banner: "TypeName", desc: "Name of this object type (expressed as Ruby or GraphQL)" # Take a type expression in any combination of GraphQL or Ruby styles # and return it in a specified output style # TODO: nullability / list with `mode: :graphql` doesn't work # @param type_expresson [String] # @param mode [Symbol] # @param null [Boolean] # @return [(String, Boolean)] The type expression, followed by `null:` value def self.normalize_type_expression(type_expression, mode:, null: true) if type_expression.start_with?("!") normalize_type_expression(type_expression[1..-1], mode: mode, null: false) elsif type_expression.end_with?("!") normalize_type_expression(type_expression[0..-2], mode: mode, null: false) elsif type_expression.start_with?("[") && type_expression.end_with?("]") name, is_null = normalize_type_expression(type_expression[1..-2], mode: mode, null: null) ["[#{name}]", is_null] elsif type_expression.end_with?("Type") normalize_type_expression(type_expression[0..-5], mode: mode, null: null) elsif type_expression.start_with?("Types::") normalize_type_expression(type_expression[7..-1], mode: mode, null: null) elsif type_expression.start_with?("types.") normalize_type_expression(type_expression[6..-1], mode: mode, null: null) else case mode when :ruby case type_expression when "Int" ["Integer", null] when "Integer", "Float", "Boolean", "String", "ID" [type_expression, null] else ["Types::#{type_expression.camelize}Type", null] end when :graphql [type_expression.camelize, null] else raise "Unexpected normalize mode: #{mode}" end end end private # @return [String] The user-provided type name, normalized to Ruby code def type_ruby_name @type_ruby_name ||= self.class.normalize_type_expression(type_name, mode: :ruby)[0] end # @return [String] The user-provided type name, as a GraphQL name def type_graphql_name @type_graphql_name ||= self.class.normalize_type_expression(type_name, mode: :graphql)[0] end # @return [String] The user-provided type name, as a file name (without extension) def type_file_name @type_file_name ||= "#{type_graphql_name}Type".underscore end # @return [Array] User-provided fields, in `(name, Ruby type name)` pairs def normalized_fields @normalized_fields ||= fields.map { |f| name, raw_type = f.split(":", 2) type_expr, null = self.class.normalize_type_expression(raw_type, mode: :ruby) NormalizedField.new(name, type_expr, null) } end class NormalizedField def initialize(name, type_expr, null) @name = name @type_expr = type_expr @null = null end def to_ruby "field :#{@name}, #{@type_expr}, null: #{@null}" end end end end end graphql-ruby-1.11.10/lib/generators/graphql/union_generator.rb000066400000000000000000000016531414121453000243700ustar00rootroot00000000000000# frozen_string_literal: true require 'generators/graphql/type_generator' module Graphql module Generators # Generate a union type by name # with the specified member types. # # ``` # rails g graphql:union SearchResultType ImageType AudioType # ``` class UnionGenerator < TypeGeneratorBase desc "Create a GraphQL::UnionType with the given name and possible types" source_root File.expand_path('../templates', __FILE__) argument :possible_types, type: :array, default: [], banner: "type type ...", desc: "Possible types for this union (expressed as Ruby or GraphQL)" def create_type_file template "union.erb", "#{options[:directory]}/types/#{type_file_name}.rb" end private def normalized_possible_types possible_types.map { |t| self.class.normalize_type_expression(t, mode: :ruby)[0] } end end end end graphql-ruby-1.11.10/lib/graphql.rb000066400000000000000000000075451414121453000170270ustar00rootroot00000000000000# frozen_string_literal: true require "delegate" require "json" require "set" require "singleton" require "forwardable" require_relative "./graphql/railtie" if defined? Rails::Railtie module GraphQL # forwards-compat for argument handling module Ruby2Keywords if RUBY_VERSION < "2.7" def ruby2_keywords(*) end end end class Error < StandardError end class RequiredImplementationMissingError < Error end class << self def default_parser @default_parser ||= GraphQL::Language::Parser end attr_writer :default_parser end # Turn a query string or schema definition into an AST # @param graphql_string [String] a GraphQL query string or schema definition # @return [GraphQL::Language::Nodes::Document] def self.parse(graphql_string, tracer: GraphQL::Tracing::NullTracer) parse_with_racc(graphql_string, tracer: tracer) end # Read the contents of `filename` and parse them as GraphQL # @param filename [String] Path to a `.graphql` file containing IDL or query # @return [GraphQL::Language::Nodes::Document] def self.parse_file(filename) content = File.read(filename) parse_with_racc(content, filename: filename) end def self.parse_with_racc(string, filename: nil, tracer: GraphQL::Tracing::NullTracer) GraphQL::Language::Parser.parse(string, filename: filename, tracer: tracer) end # @return [Array] def self.scan(graphql_string) scan_with_ragel(graphql_string) end def self.scan_with_ragel(graphql_string) GraphQL::Language::Lexer.tokenize(graphql_string) end # Support Ruby 2.2 by implementing `-"str"`. If we drop 2.2 support, we can remove this backport. module StringDedupBackport refine String do def -@ if frozen? self else self.dup.freeze end end end end module StringMatchBackport refine String do def match?(pattern) self =~ pattern end end end end # Order matters for these: require "graphql/execution_error" require "graphql/define" require "graphql/base_type" require "graphql/object_type" require "graphql/enum_type" require "graphql/input_object_type" require "graphql/interface_type" require "graphql/list_type" require "graphql/non_null_type" require "graphql/union_type" require "graphql/argument" require "graphql/field" require "graphql/type_kinds" require "graphql/backwards_compatibility" require "graphql/scalar_type" require "graphql/name_validator" require "graphql/language" require "graphql/analysis" require "graphql/tracing" require "graphql/dig" require "graphql/execution" require "graphql/runtime_type_error" require "graphql/unresolved_type_error" require "graphql/invalid_null_error" require "graphql/schema" require "graphql/query" require "graphql/directive" require "graphql/execution" require "graphql/types" require "graphql/relay" require "graphql/boolean_type" require "graphql/float_type" require "graphql/id_type" require "graphql/int_type" require "graphql/string_type" require "graphql/schema/built_in_types" require "graphql/schema/loader" require "graphql/schema/printer" require "graphql/introspection" require "graphql/analysis_error" require "graphql/coercion_error" require "graphql/invalid_name_error" require "graphql/integer_decoding_error" require "graphql/integer_encoding_error" require "graphql/string_encoding_error" require "graphql/internal_representation" require "graphql/static_validation" require "graphql/version" require "graphql/compatibility" require "graphql/function" require "graphql/filter" require "graphql/subscriptions" require "graphql/parse_error" require "graphql/backtrace" require "graphql/deprecated_dsl" require "graphql/authorization" require "graphql/unauthorized_error" require "graphql/unauthorized_field_error" require "graphql/load_application_object_failed_error" require "graphql/pagination" graphql-ruby-1.11.10/lib/graphql/000077500000000000000000000000001414121453000164675ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/analysis.rb000066400000000000000000000005441414121453000206420ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/analysis/ast" require "graphql/analysis/max_query_complexity" require "graphql/analysis/max_query_depth" require "graphql/analysis/query_complexity" require "graphql/analysis/query_depth" require "graphql/analysis/reducer_state" require "graphql/analysis/analyze_query" require "graphql/analysis/field_usage" graphql-ruby-1.11.10/lib/graphql/analysis/000077500000000000000000000000001414121453000203125ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/analysis/analyze_query.rb000066400000000000000000000060321414121453000235300ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis module_function # @return [void] def analyze_multiplex(multiplex, analyzers) multiplex.trace("analyze_multiplex", { multiplex: multiplex }) do reducer_states = analyzers.map { |r| ReducerState.new(r, multiplex) } query_results = multiplex.queries.map do |query| if query.valid? analyze_query(query, query.analyzers, multiplex_states: reducer_states) else [] end end multiplex_results = reducer_states.map(&:finalize_reducer) multiplex_errors = analysis_errors(multiplex_results) multiplex.queries.each_with_index do |query, idx| query.analysis_errors = multiplex_errors + analysis_errors(query_results[idx]) end end nil end # Visit `query`'s internal representation, calling `analyzers` along the way. # # - First, query analyzers are filtered down by calling `.analyze?(query)`, if they respond to that method # - Then, query analyzers are initialized by calling `.initial_value(query)`, if they respond to that method. # - Then, they receive `.call(memo, visit_type, irep_node)`, where visit type is `:enter` or `:leave`. # - Last, they receive `.final_value(memo)`, if they respond to that method. # # It returns an array of final `memo` values in the order that `analyzers` were passed in. # # @param query [GraphQL::Query] # @param analyzers [Array<#call>] Objects that respond to `#call(memo, visit_type, irep_node)` # @return [Array] Results from those analyzers def analyze_query(query, analyzers, multiplex_states: []) query.trace("analyze_query", { query: query }) do analyzers_to_run = analyzers.select do |analyzer| if analyzer.respond_to?(:analyze?) analyzer.analyze?(query) else true end end reducer_states = analyzers_to_run.map { |r| ReducerState.new(r, query) } + multiplex_states irep = query.internal_representation irep.operation_definitions.each do |name, op_node| reduce_node(op_node, reducer_states) end reducer_states.map(&:finalize_reducer) end end private module_function # Enter the node, visit its children, then leave the node. def reduce_node(irep_node, reducer_states) visit_analyzers(:enter, irep_node, reducer_states) irep_node.typed_children.each do |type_defn, children| children.each do |name, child_irep_node| reduce_node(child_irep_node, reducer_states) end end visit_analyzers(:leave, irep_node, reducer_states) end def visit_analyzers(visit_type, irep_node, reducer_states) reducer_states.each do |reducer_state| next_memo = reducer_state.call(visit_type, irep_node) reducer_state.memo = next_memo end end def analysis_errors(results) results.flatten.select { |r| r.is_a?(GraphQL::AnalysisError) } end end end graphql-ruby-1.11.10/lib/graphql/analysis/ast.rb000066400000000000000000000052441414121453000214330ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/analysis/ast/visitor" require "graphql/analysis/ast/analyzer" require "graphql/analysis/ast/field_usage" require "graphql/analysis/ast/query_complexity" require "graphql/analysis/ast/max_query_complexity" require "graphql/analysis/ast/query_depth" require "graphql/analysis/ast/max_query_depth" module GraphQL module Analysis module AST module_function def use(schema_class) schema_class.analysis_engine = GraphQL::Analysis::AST end # Analyze a multiplex, and all queries within. # Multiplex analyzers are ran for all queries, keeping state. # Query analyzers are ran per query, without carrying state between queries. # # @param multiplex [GraphQL::Execution::Multiplex] # @param analyzers [Array] # @return [Array] Results from multiplex analyzers def analyze_multiplex(multiplex, analyzers) multiplex_analyzers = analyzers.map { |analyzer| analyzer.new(multiplex) } multiplex.trace("analyze_multiplex", { multiplex: multiplex }) do query_results = multiplex.queries.map do |query| if query.valid? analyze_query( query, query.analyzers, multiplex_analyzers: multiplex_analyzers ) else [] end end multiplex_results = multiplex_analyzers.map(&:result) multiplex_errors = analysis_errors(multiplex_results) multiplex.queries.each_with_index do |query, idx| query.analysis_errors = multiplex_errors + analysis_errors(query_results[idx]) end multiplex_results end end # @param query [GraphQL::Query] # @param analyzers [Array] # @return [Array] Results from those analyzers def analyze_query(query, analyzers, multiplex_analyzers: []) query.trace("analyze_query", { query: query }) do query_analyzers = analyzers .map { |analyzer| analyzer.new(query) } .select { |analyzer| analyzer.analyze? } analyzers_to_run = query_analyzers + multiplex_analyzers if analyzers_to_run.any? visitor = GraphQL::Analysis::AST::Visitor.new( query: query, analyzers: analyzers_to_run ) visitor.visit query_analyzers.map(&:result) else [] end end end def analysis_errors(results) results.flatten.select { |r| r.is_a?(GraphQL::AnalysisError) } end end end end graphql-ruby-1.11.10/lib/graphql/analysis/ast/000077500000000000000000000000001414121453000211015ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/analysis/ast/analyzer.rb000066400000000000000000000053211414121453000232540ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis module AST # Query analyzer for query ASTs. Query analyzers respond to visitor style methods # but are prefixed by `enter` and `leave`. # # When an analyzer is initialized with a Multiplex, you can always get the current query from # `visitor.query` in the visit methods. # # @param [GraphQL::Query, GraphQL::Execution::Multiplex] The query or multiplex to analyze class Analyzer def initialize(subject) @subject = subject if subject.is_a?(GraphQL::Query) @query = subject @multiplex = nil else @multiplex = subject @query = nil end end # Analyzer hook to decide at analysis time whether a query should # be analyzed or not. # @return [Boolean] If the query should be analyzed or not def analyze? true end # The result for this analyzer. Returning {GraphQL::AnalysisError} results # in a query error. # @return [Any] The analyzer result def result raise GraphQL::RequiredImplementationMissingError end class << self private def build_visitor_hooks(member_name) class_eval(<<-EOS, __FILE__, __LINE__ + 1) def on_enter_#{member_name}(node, parent, visitor) end def on_leave_#{member_name}(node, parent, visitor) end EOS end end build_visitor_hooks :argument build_visitor_hooks :directive build_visitor_hooks :document build_visitor_hooks :enum build_visitor_hooks :field build_visitor_hooks :fragment_spread build_visitor_hooks :inline_fragment build_visitor_hooks :input_object build_visitor_hooks :list_type build_visitor_hooks :non_null_type build_visitor_hooks :null_value build_visitor_hooks :operation_definition build_visitor_hooks :type_name build_visitor_hooks :variable_definition build_visitor_hooks :variable_identifier build_visitor_hooks :abstract_node protected # @return [GraphQL::Query, GraphQL::Execution::Multiplex] Whatever this analyzer is analyzing attr_reader :subject # @return [GraphQL::Query, nil] `nil` if this analyzer is visiting a multiplex # (When this is `nil`, use `visitor.query` inside visit methods to get the current query) attr_reader :query # @return [GraphQL::Execution::Multiplex, nil] `nil` if this analyzer is visiting a query attr_reader :multiplex end end end end graphql-ruby-1.11.10/lib/graphql/analysis/ast/field_usage.rb000066400000000000000000000013611414121453000236760ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis module AST class FieldUsage < Analyzer def initialize(query) super @used_fields = Set.new @used_deprecated_fields = Set.new end def on_leave_field(node, parent, visitor) field_defn = visitor.field_definition field = "#{visitor.parent_type_definition.graphql_name}.#{field_defn.graphql_name}" @used_fields << field @used_deprecated_fields << field if field_defn.deprecation_reason end def result { used_fields: @used_fields.to_a, used_deprecated_fields: @used_deprecated_fields.to_a } end end end end end graphql-ruby-1.11.10/lib/graphql/analysis/ast/max_query_complexity.rb000066400000000000000000000012731414121453000257200ustar00rootroot00000000000000# frozen_string_literal: true require_relative "./query_complexity" module GraphQL module Analysis module AST # Used under the hood to implement complexity validation, # see {Schema#max_complexity} and {Query#max_complexity} class MaxQueryComplexity < QueryComplexity def result return if subject.max_complexity.nil? total_complexity = max_possible_complexity if total_complexity > subject.max_complexity GraphQL::AnalysisError.new("Query has complexity of #{total_complexity}, which exceeds max complexity of #{subject.max_complexity}") else nil end end end end end end graphql-ruby-1.11.10/lib/graphql/analysis/ast/max_query_depth.rb000066400000000000000000000010561414121453000246260ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis module AST class MaxQueryDepth < QueryDepth def result configured_max_depth = if query query.max_depth else multiplex.schema.max_depth end if configured_max_depth && @max_depth > configured_max_depth GraphQL::AnalysisError.new("Query has depth of #{@max_depth}, which exceeds max depth of #{configured_max_depth}") else nil end end end end end end graphql-ruby-1.11.10/lib/graphql/analysis/ast/query_complexity.rb000066400000000000000000000242611414121453000250550ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis # Calculate the complexity of a query, using {Field#complexity} values. module AST class QueryComplexity < Analyzer # State for the query complexity calculation: # - `complexities_on_type` holds complexity scores for each type in an IRep node def initialize(query) super @complexities_on_type_by_query = {} end # Overide this method to use the complexity result def result max_possible_complexity end class ScopedTypeComplexity # A single proc for {#scoped_children} hashes. Use this to avoid repeated allocations, # since the lexical binding isn't important. HASH_CHILDREN = ->(h, k) { h[k] = {} } attr_reader :field_definition, :response_path, :query # @param node [Language::Nodes::Field] The AST node; used for providing argument values when necessary # @param field_definition [GraphQL::Field, GraphQL::Schema::Field] Used for getting the `.complexity` configuration # @param query [GraphQL::Query] Used for `query.possible_types` # @param response_path [Array] The path to the response key for the field def initialize(node, field_definition, query, response_path) @node = node @field_definition = field_definition @query = query @response_path = response_path @scoped_children = nil end # Returns true if this field has no selections, ie, it's a scalar. # We need a quick way to check whether we should continue traversing. def terminal? @scoped_children.nil? end # This value is only calculated when asked for to avoid needless hash allocations. # Also, if it's never asked for, we determine that this scope complexity # is a scalar field ({#terminal?}). # @return [Hash ScopedTypeComplexity>] def scoped_children @scoped_children ||= Hash.new(&HASH_CHILDREN) end def own_complexity(child_complexity) defined_complexity = @field_definition.complexity case defined_complexity when Proc arguments = @query.arguments_for(@node, @field_definition) defined_complexity.call(@query.context, arguments.keyword_arguments, child_complexity) when Numeric defined_complexity + child_complexity else raise("Invalid complexity: #{defined_complexity.inspect} on #{@field_definition.name}") end end end def on_enter_field(node, parent, visitor) # We don't want to visit fragment definitions, # we'll visit them when we hit the spreads instead return if visitor.visiting_fragment_definition? return if visitor.skipping? parent_type = visitor.parent_type_definition field_key = node.alias || node.name # Find the complexity calculation for this field -- # if we're re-entering a selection, we'll already have one. # Otherwise, make a new one and store it. # # `node` and `visitor.field_definition` may appear from a cache, # but I think that's ok. If the arguments _didn't_ match, # then the query would have been rejected as invalid. complexities_on_type = @complexities_on_type_by_query[visitor.query] ||= [ScopedTypeComplexity.new(nil, nil, query, visitor.response_path)] complexity = complexities_on_type.last.scoped_children[parent_type][field_key] ||= ScopedTypeComplexity.new(node, visitor.field_definition, visitor.query, visitor.response_path) # Push it on the stack. complexities_on_type.push(complexity) end def on_leave_field(node, parent, visitor) # We don't want to visit fragment definitions, # we'll visit them when we hit the spreads instead return if visitor.visiting_fragment_definition? return if visitor.skipping? complexities_on_type = @complexities_on_type_by_query[visitor.query] complexities_on_type.pop end private # @return [Integer] def max_possible_complexity @complexities_on_type_by_query.reduce(0) do |total, (query, complexities_on_type)| root_complexity = complexities_on_type.last # Use this entry point to calculate the total complexity total_complexity_for_query = merged_max_complexity_for_scopes(query, [root_complexity.scoped_children]) total + total_complexity_for_query end end # @param query [GraphQL::Query] Used for `query.possible_types` # @param scoped_children_hashes [Array] Array of scoped children hashes # @return [Integer] def merged_max_complexity_for_scopes(query, scoped_children_hashes) # Figure out what scopes are possible here. # Use a hash, but ignore the values; it's just a fast way to work with the keys. all_scopes = {} scoped_children_hashes.each do |h| all_scopes.merge!(h) end # If an abstract scope is present, but _all_ of its concrete types # are also in the list, remove it from the list of scopes to check, # because every possible type is covered by a concrete type. # (That is, there are no remainder types to check.) prev_keys = all_scopes.keys prev_keys.each do |scope| next unless scope.kind.abstract? missing_concrete_types = query.possible_types(scope).select { |t| !all_scopes.key?(t) } # This concrete type is possible _only_ as a member of the abstract type. # So, attribute to it the complexity which belongs to the abstract type. missing_concrete_types.each do |concrete_scope| all_scopes[concrete_scope] = all_scopes[scope] end all_scopes.delete(scope) end # This will hold `{ type => int }` pairs, one for each possible branch complexity_by_scope = {} # For each scope, # find the lexical selections that might apply to it, # and gather them together into an array. # Then, treat the set of selection hashes # as a set and calculate the complexity for them as a unit all_scopes.each do |scope, _| # These will be the selections on `scope` children_for_scope = [] scoped_children_hashes.each do |sc_h| sc_h.each do |inner_scope, children_hash| if applies_to?(query, scope, inner_scope) children_for_scope << children_hash end end end # Calculate the complexity for `scope`, merging all # possible lexical branches. complexity_value = merged_max_complexity(query, children_for_scope) complexity_by_scope[scope] = complexity_value end # Return the max complexity among all scopes complexity_by_scope.each_value.max end def applies_to?(query, left_scope, right_scope) if left_scope == right_scope # This can happen when several branches are being analyzed together true else # Check if these two scopes have _any_ types in common. possible_right_types = query.possible_types(right_scope) possible_left_types = query.possible_types(left_scope) !(possible_right_types & possible_left_types).empty? end end # A hook which is called whenever a field's max complexity is calculated. # Override this method to capture individual field complexity details. # # @param scoped_type_complexity [ScopedTypeComplexity] # @param max_complexity [Numeric] Field's maximum complexity including child complexity # @param child_complexity [Numeric, nil] Field's child complexity def field_complexity(scoped_type_complexity, max_complexity:, child_complexity: nil) end # @param children_for_scope [Array] An array of `scoped_children[scope]` hashes # (`{field_key => complexity}`) # @return [Integer] Complexity value for all these selections in the current scope def merged_max_complexity(query, children_for_scope) all_keys = [] children_for_scope.each do |c| all_keys.concat(c.keys) end all_keys.uniq! complexity_for_keys = {} all_keys.each do |child_key| scoped_children_for_key = nil complexity_for_key = nil children_for_scope.each do |children_hash| next unless children_hash.key?(child_key) complexity_for_key = children_hash[child_key] if complexity_for_key.terminal? # Assume that all terminals would return the same complexity # Since it's a terminal, its child complexity is zero. complexity = complexity_for_key.own_complexity(0) complexity_for_keys[child_key] = complexity field_complexity(complexity_for_key, max_complexity: complexity, child_complexity: nil) else scoped_children_for_key ||= [] scoped_children_for_key << complexity_for_key.scoped_children end end next unless scoped_children_for_key child_complexity = merged_max_complexity_for_scopes(query, scoped_children_for_key) # This is the _last_ one we visited; assume it's representative. max_complexity = complexity_for_key.own_complexity(child_complexity) field_complexity(complexity_for_key, max_complexity: max_complexity, child_complexity: child_complexity) complexity_for_keys[child_key] = max_complexity end # Calculate the child complexity by summing the complexity of all selections complexity_for_keys.each_value.inject(0, &:+) end end end end end graphql-ruby-1.11.10/lib/graphql/analysis/ast/query_depth.rb000066400000000000000000000026211414121453000237600ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis # A query reducer for measuring the depth of a given query. # # See https://graphql-ruby.org/queries/ast_analysis.html for more examples. # # @example Logging the depth of a query # class LogQueryDepth < GraphQL::Analysis::QueryDepth # def result # log("GraphQL query depth: #{@max_depth}") # end # end # # # In your Schema file: # # class MySchema < GraphQL::Schema # use GraphQL::Analysis::AST # query_analyzer LogQueryDepth # end # # # When you run the query, the depth will get logged: # # Schema.execute(query_str) # # GraphQL query depth: 8 # module AST class QueryDepth < Analyzer def initialize(query) @max_depth = 0 @current_depth = 0 super end def on_enter_field(node, parent, visitor) return if visitor.skipping? || visitor.visiting_fragment_definition? @current_depth += 1 end def on_leave_field(node, parent, visitor) return if visitor.skipping? || visitor.visiting_fragment_definition? if @max_depth < @current_depth @max_depth = @current_depth end @current_depth -= 1 end def result @max_depth end end end end end graphql-ruby-1.11.10/lib/graphql/analysis/ast/visitor.rb000066400000000000000000000205651414121453000231350ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis module AST # Depth first traversal through a query AST, calling AST analyzers # along the way. # # The visitor is a special case of GraphQL::Language::Visitor, visiting # only the selected operation, providing helpers for common use cases such # as skipped fields and visiting fragment spreads. # # @see {GraphQL::Analysis::AST::Analyzer} AST Analyzers for queries class Visitor < GraphQL::Language::Visitor def initialize(query:, analyzers:) @analyzers = analyzers @path = [] @object_types = [] @directives = [] @field_definitions = [] @argument_definitions = [] @directive_definitions = [] @query = query @schema = query.schema @response_path = [] @skip_stack = [false] super(query.selected_operation) end # @return [GraphQL::Query] the query being visited attr_reader :query # @return [Array] Types whose scope we've entered attr_reader :object_types def visit return unless @document super end # Visit Helpers # @return [GraphQL::Query::Arguments] Arguments for this node, merging default values, literal values and query variables # @see {GraphQL::Query#arguments_for} def arguments_for(ast_node, field_definition) @query.arguments_for(ast_node, field_definition) end # @return [Boolean] If the visitor is currently inside a fragment definition def visiting_fragment_definition? @in_fragment_def end # @return [Boolean] If the current node should be skipped because of a skip or include directive def skipping? @skipping end # @return [Array] The path to the response key for the current field def response_path @response_path.dup end # Visitor Hooks def on_operation_definition(node, parent) object_type = @schema.root_type_for_operation(node.operation_type) @object_types.push(object_type) @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}") call_analyzers(:on_enter_operation_definition, node, parent) super call_analyzers(:on_leave_operation_definition, node, parent) @object_types.pop @path.pop end def on_fragment_definition(node, parent) on_fragment_with_type(node) do @path.push("fragment #{node.name}") @in_fragment_def = false call_analyzers(:on_enter_fragment_definition, node, parent) super @in_fragment_def = false call_analyzers(:on_leave_fragment_definition, node, parent) end end def on_inline_fragment(node, parent) on_fragment_with_type(node) do @path.push("...#{node.type ? " on #{node.type.name}" : ""}") call_analyzers(:on_enter_inline_fragment, node, parent) super call_analyzers(:on_leave_inline_fragment, node, parent) end end def on_field(node, parent) @response_path.push(node.alias || node.name) parent_type = @object_types.last field_definition = @schema.get_field(parent_type, node.name) @field_definitions.push(field_definition) if !field_definition.nil? next_object_type = field_definition.type.unwrap @object_types.push(next_object_type) else @object_types.push(nil) end @path.push(node.alias || node.name) @skipping = @skip_stack.last || skip?(node) @skip_stack << @skipping call_analyzers(:on_enter_field, node, parent) super @skipping = @skip_stack.pop call_analyzers(:on_leave_field, node, parent) @response_path.pop @field_definitions.pop @object_types.pop @path.pop end def on_directive(node, parent) directive_defn = @schema.directives[node.name] @directive_definitions.push(directive_defn) call_analyzers(:on_enter_directive, node, parent) super call_analyzers(:on_leave_directive, node, parent) @directive_definitions.pop end def on_argument(node, parent) argument_defn = if (arg = @argument_definitions.last) arg_type = arg.type.unwrap if arg_type.kind.input_object? arg_type.arguments[node.name] else nil end elsif (directive_defn = @directive_definitions.last) directive_defn.arguments[node.name] elsif (field_defn = @field_definitions.last) field_defn.arguments[node.name] else nil end @argument_definitions.push(argument_defn) @path.push(node.name) call_analyzers(:on_enter_argument, node, parent) super call_analyzers(:on_leave_argument, node, parent) @argument_definitions.pop @path.pop end def on_fragment_spread(node, parent) @path.push("... #{node.name}") call_analyzers(:on_enter_fragment_spread, node, parent) enter_fragment_spread_inline(node) super leave_fragment_spread_inline(node) call_analyzers(:on_leave_fragment_spread, node, parent) @path.pop end def on_abstract_node(node, parent) call_analyzers(:on_enter_abstract_node, node, parent) super call_analyzers(:on_leave_abstract_node, node, parent) end # @return [GraphQL::BaseType] The current object type def type_definition @object_types.last end # @return [GraphQL::BaseType] The type which the current type came from def parent_type_definition @object_types[-2] end # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one def field_definition @field_definitions.last end # @return [GraphQL::Field, nil] The GraphQL field which returned the object that the current field belongs to def previous_field_definition @field_definitions[-2] end # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one def directive_definition @directive_definitions.last end # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one def argument_definition @argument_definitions.last end # @return [GraphQL::Argument, nil] The previous GraphQL argument def previous_argument_definition @argument_definitions[-2] end private # Visit a fragment spread inline instead of visiting the definition # by itself. def enter_fragment_spread_inline(fragment_spread) fragment_def = query.fragments[fragment_spread.name] object_type = if fragment_def.type @query.warden.get_type(fragment_def.type.name) else object_types.last end object_types << object_type fragment_def.selections.each do |selection| visit_node(selection, fragment_def) end end # Visit a fragment spread inline instead of visiting the definition # by itself. def leave_fragment_spread_inline(_fragment_spread) object_types.pop end def skip?(ast_node) dir = ast_node.directives dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, query) end def call_analyzers(method, node, parent) @analyzers.each do |analyzer| analyzer.public_send(method, node, parent, self) end end def on_fragment_with_type(node) object_type = if node.type @query.warden.get_type(node.type.name) else @object_types.last end @object_types.push(object_type) yield(node) @object_types.pop @path.pop end end end end end graphql-ruby-1.11.10/lib/graphql/analysis/field_usage.rb000066400000000000000000000027231414121453000231120ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis # A query reducer for tracking both field usage and deprecated field usage. # # @example Logging field usage and deprecated field usage # Schema.query_analyzers << GraphQL::Analysis::FieldUsage.new { |query, used_fields, used_deprecated_fields| # puts "Used GraphQL fields: #{used_fields.join(', ')}" # puts "Used deprecated GraphQL fields: #{used_deprecated_fields.join(', ')}" # } # Schema.execute(query_str) # # Used GraphQL fields: Cheese.id, Cheese.fatContent, Query.cheese # # Used deprecated GraphQL fields: Cheese.fatContent # class FieldUsage def initialize(&block) @field_usage_handler = block end def initial_value(query) { query: query, used_fields: Set.new, used_deprecated_fields: Set.new } end def call(memo, visit_type, irep_node) if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field) && visit_type == :leave field = "#{irep_node.owner_type.name}.#{irep_node.definition.name}" memo[:used_fields] << field if irep_node.definition.deprecation_reason memo[:used_deprecated_fields] << field end end memo end def final_value(memo) @field_usage_handler.call(memo[:query], memo[:used_fields].to_a, memo[:used_deprecated_fields].to_a) end end end end graphql-ruby-1.11.10/lib/graphql/analysis/max_query_complexity.rb000066400000000000000000000016571414121453000251370ustar00rootroot00000000000000# frozen_string_literal: true require_relative "./query_complexity" module GraphQL module Analysis # Used under the hood to implement complexity validation, # see {Schema#max_complexity} and {Query#max_complexity} # # @example Assert max complexity of 10 # # DON'T actually do this, graphql-ruby # # Does this for you based on your `max_complexity` setting # MySchema.query_analyzers << GraphQL::Analysis::MaxQueryComplexity.new(10) # class MaxQueryComplexity < GraphQL::Analysis::QueryComplexity def initialize(max_complexity) disallow_excessive_complexity = ->(query, complexity) { if complexity > max_complexity GraphQL::AnalysisError.new("Query has complexity of #{complexity}, which exceeds max complexity of #{max_complexity}") else nil end } super(&disallow_excessive_complexity) end end end end graphql-ruby-1.11.10/lib/graphql/analysis/max_query_depth.rb000066400000000000000000000015201414121453000240330ustar00rootroot00000000000000# frozen_string_literal: true require_relative "./query_depth" module GraphQL module Analysis # Used under the hood to implement depth validation, # see {Schema#max_depth} and {Query#max_depth} # # @example Assert max depth of 10 # # DON'T actually do this, graphql-ruby # # Does this for you based on your `max_depth` setting # MySchema.query_analyzers << GraphQL::Analysis::MaxQueryDepth.new(10) # class MaxQueryDepth < GraphQL::Analysis::QueryDepth def initialize(max_depth) disallow_excessive_depth = ->(query, depth) { if depth > max_depth GraphQL::AnalysisError.new("Query has depth of #{depth}, which exceeds max depth of #{max_depth}") else nil end } super(&disallow_excessive_depth) end end end end graphql-ruby-1.11.10/lib/graphql/analysis/query_complexity.rb000066400000000000000000000063561414121453000242730ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis # Calculate the complexity of a query, using {Field#complexity} values. # # @example Log the complexity of incoming queries # MySchema.query_analyzers << GraphQL::Analysis::QueryComplexity.new do |query, complexity| # Rails.logger.info("Complexity: #{complexity}") # end # class QueryComplexity # @yield [query, complexity] Called for each query analyzed by the schema, before executing it # @yieldparam query [GraphQL::Query] The query that was analyzed # @yieldparam complexity [Numeric] The complexity for this query def initialize(&block) @complexity_handler = block end # State for the query complexity calcuation: # - `target` is passed to handler # - `complexities_on_type` holds complexity scores for each type in an IRep node def initial_value(target) { target: target, complexities_on_type: [TypeComplexity.new], } end # Implement the query analyzer API def call(memo, visit_type, irep_node) if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field) if visit_type == :enter memo[:complexities_on_type].push(TypeComplexity.new) else type_complexities = memo[:complexities_on_type].pop child_complexity = type_complexities.max_possible_complexity own_complexity = get_complexity(irep_node, child_complexity) memo[:complexities_on_type].last.merge(irep_node.owner_type, own_complexity) end end memo end # Send the query and complexity to the block # @return [Object, GraphQL::AnalysisError] Whatever the handler returns def final_value(reduced_value) total_complexity = reduced_value[:complexities_on_type].last.max_possible_complexity @complexity_handler.call(reduced_value[:target], total_complexity) end private # Get a complexity value for a field, # by getting the number or calling its proc def get_complexity(irep_node, child_complexity) field_defn = irep_node.definition defined_complexity = field_defn.complexity case defined_complexity when Proc defined_complexity.call(irep_node.query.context, irep_node.arguments, child_complexity) when Numeric defined_complexity + (child_complexity || 0) else raise("Invalid complexity: #{defined_complexity.inspect} on #{field_defn.name}") end end # Selections on an object may apply differently depending on what is _actually_ returned by the resolve function. # Find the maximum possible complexity among those combinations. class TypeComplexity def initialize @types = Hash.new(0) end # Return the max possible complexity for types in this selection def max_possible_complexity @types.each_value.max || 0 end # Store the complexity for the branch on `type_defn`. # Later we will see if this is the max complexity among branches. def merge(type_defn, complexity) @types[type_defn] += complexity end end end end end graphql-ruby-1.11.10/lib/graphql/analysis/query_depth.rb000066400000000000000000000021561414121453000231740ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis # A query reducer for measuring the depth of a given query. # # @example Logging the depth of a query # Schema.query_analyzers << GraphQL::Analysis::QueryDepth.new { |query, depth| puts "GraphQL query depth: #{depth}" } # Schema.execute(query_str) # # GraphQL query depth: 8 # class QueryDepth def initialize(&block) @depth_handler = block end def initial_value(query) { max_depth: 0, current_depth: 0, query: query, } end def call(memo, visit_type, irep_node) if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field) if visit_type == :enter memo[:current_depth] += 1 else if memo[:max_depth] < memo[:current_depth] memo[:max_depth] = memo[:current_depth] end memo[:current_depth] -= 1 end end memo end def final_value(memo) @depth_handler.call(memo[:query], memo[:max_depth]) end end end end graphql-ruby-1.11.10/lib/graphql/analysis/reducer_state.rb000066400000000000000000000024001414121453000234640ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Analysis class ReducerState attr_reader :reducer attr_accessor :memo, :errors def initialize(reducer, query) @reducer = reducer @memo = initialize_reducer(reducer, query) @errors = [] end def call(visit_type, irep_node) @memo = @reducer.call(@memo, visit_type, irep_node) rescue AnalysisError => err @errors << err end # Respond with any errors, if found. Otherwise, if the reducer accepts # `final_value`, send it the last memo value. # Otherwise, use the last value from the traversal. # @return [Any] final memo value def finalize_reducer if @errors.any? @errors elsif reducer.respond_to?(:final_value) reducer.final_value(@memo) else @memo end end private # If the reducer has an `initial_value` method, call it and store # the result as `memo`. Otherwise, use `nil` as memo. # @return [Any] initial memo value def initialize_reducer(reducer, query) if reducer.respond_to?(:initial_value) reducer.initial_value(query) else nil end end end end end graphql-ruby-1.11.10/lib/graphql/analysis_error.rb000066400000000000000000000001471414121453000220520ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class AnalysisError < GraphQL::ExecutionError end end graphql-ruby-1.11.10/lib/graphql/argument.rb000066400000000000000000000077121414121453000206450ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # @api deprecated class Argument include GraphQL::Define::InstanceDefinable accepts_definitions :name, :type, :description, :default_value, :as, :prepare, :method_access, :deprecation_reason attr_reader :default_value attr_accessor :description, :name, :as, :deprecation_reason attr_accessor :ast_node attr_accessor :method_access alias :graphql_name :name ensure_defined(:name, :description, :default_value, :type=, :type, :as, :expose_as, :prepare, :method_access, :deprecation_reason) # @api private module DefaultPrepare def self.call(value, ctx); value; end end def initialize @prepare_proc = DefaultPrepare end def initialize_copy(other) @expose_as = nil end def default_value? !!@has_default_value end def method_access? # Treat unset as true -- only `false` should override @method_access != false end def default_value=(new_default_value) if new_default_value == NO_DEFAULT_VALUE @has_default_value = false @default_value = nil else @has_default_value = true @default_value = GraphQL::Argument.deep_stringify(new_default_value) end end # @!attribute name # @return [String] The name of this argument on its {GraphQL::Field} or {GraphQL::InputObjectType} # @param new_input_type [GraphQL::BaseType, Proc] Assign a new input type for this argument (if it's a proc, it will be called after schema initialization) def type=(new_input_type) @clean_type = nil @dirty_type = new_input_type end # @return [GraphQL::BaseType] the input type for this argument def type @clean_type ||= GraphQL::BaseType.resolve_related_type(@dirty_type) end # @return [String] The name of this argument inside `resolve` functions def expose_as @expose_as ||= (@as || @name).to_s end # Backport this to support legacy-style directives def keyword @keyword ||= GraphQL::Schema::Member::BuildType.underscore(expose_as).to_sym end # @param value [Object] The incoming value from variables or query string literal # @param ctx [GraphQL::Query::Context] # @return [Object] The prepared `value` for this argument or `value` itself if no `prepare` function exists. def prepare(value, ctx) @prepare_proc.call(value, ctx) end # Assign a `prepare` function to prepare this argument's value before `resolve` functions are called. # @param prepare_proc [#] def prepare=(prepare_proc) @prepare_proc = BackwardsCompatibility.wrap_arity(prepare_proc, from: 1, to: 2, name: "Argument#prepare(value, ctx)") end def type_class metadata[:type_class] end NO_DEFAULT_VALUE = Object.new # @api private def self.from_dsl(name, type_or_argument = nil, description = nil, default_value: NO_DEFAULT_VALUE, as: nil, prepare: DefaultPrepare, **kwargs, &block) name_s = name.to_s # Move some positional args into keywords if they're present description && kwargs[:description] ||= description kwargs[:name] ||= name_s kwargs[:default_value] ||= default_value kwargs[:as] ||= as unless prepare == DefaultPrepare kwargs[:prepare] ||= prepare end if !type_or_argument.nil? && !type_or_argument.is_a?(GraphQL::Argument) # Maybe a string, proc or BaseType kwargs[:type] = type_or_argument end if type_or_argument.is_a?(GraphQL::Argument) type_or_argument.redefine(**kwargs, &block) else GraphQL::Argument.define(**kwargs, &block) end end # @api private def self.deep_stringify(val) case val when Array val.map { |v| deep_stringify(v) } when Hash new_val = {} val.each do |k, v| new_val[k.to_s] = deep_stringify(v) end new_val else val end end end end graphql-ruby-1.11.10/lib/graphql/authorization.rb000066400000000000000000000052321414121453000217160ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Authorization class InaccessibleFieldsError < GraphQL::AnalysisError # @return [Array] Fields that failed `.accessible?` checks attr_reader :fields # @return [GraphQL::Query::Context] The current query's context attr_reader :context # @return [Array] The visited nodes that failed `.accessible?` checks # @see {#fields} for the Field definitions attr_reader :irep_nodes def initialize(fields:, irep_nodes:, context:) @fields = fields @irep_nodes = irep_nodes @context = context super("Some fields in this query are not accessible: #{fields.map(&:graphql_name).join(", ")}") end end # @deprecated authorization at query runtime is generally a better idea. module Analyzer module_function def initial_value(query) { schema: query.schema, context: query.context, inaccessible_nodes: [], } end def call(memo, visit_type, irep_node) if visit_type == :enter field = irep_node.definition if field schema = memo[:schema] ctx = memo[:context] next_field_accessible = schema.accessible?(field, ctx) if !next_field_accessible memo[:inaccessible_nodes] << irep_node else arg_accessible = true irep_node.arguments.argument_values.each do |name, arg_value| arg_accessible = schema.accessible?(arg_value.definition, ctx) if !arg_accessible memo[:inaccessible_nodes] << irep_node break end end if arg_accessible return_type = field.type.unwrap next_type_accessible = schema.accessible?(return_type, ctx) if !next_type_accessible memo[:inaccessible_nodes] << irep_node end end end end end memo end def final_value(memo) nodes = memo[:inaccessible_nodes] if nodes.any? fields = nodes.map do |node| field_inst = node.definition # Get the "source of truth" for this field field_inst.metadata[:type_class] || field_inst end context = memo[:context] err = InaccessibleFieldsError.new(fields: fields, irep_nodes: nodes, context: context) context.schema.inaccessible_fields(err) else nil end end end end end graphql-ruby-1.11.10/lib/graphql/backtrace.rb000066400000000000000000000025201414121453000207320ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/backtrace/inspect_result" require "graphql/backtrace/table" require "graphql/backtrace/traced_error" require "graphql/backtrace/tracer" module GraphQL # Wrap unhandled errors with {TracedError}. # # {TracedError} provides a GraphQL backtrace with arguments and return values. # The underlying error is available as {TracedError#cause}. # # WARNING: {.enable} is not threadsafe because {GraphQL::Tracing.install} is not threadsafe. # # @example toggling backtrace annotation # # to enable: # GraphQL::Backtrace.enable # # later, to disable: # GraphQL::Backtrace.disable # class Backtrace include Enumerable extend Forwardable def_delegators :to_a, :each, :[] def self.enable warn("GraphQL::Backtrace.enable is deprecated, add `use GraphQL::Backtrace` to your schema definition instead.") GraphQL::Tracing.install(Backtrace::Tracer) nil end def self.disable GraphQL::Tracing.uninstall(Backtrace::Tracer) nil end def self.use(schema_defn) schema_defn.tracer(self::Tracer) end def initialize(context, value: nil) @table = Table.new(context, value: value) end def inspect @table.to_table end alias :to_s :inspect def to_a @table.to_backtrace end end end graphql-ruby-1.11.10/lib/graphql/backtrace/000077500000000000000000000000001414121453000204065ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/backtrace/inspect_result.rb000066400000000000000000000021411414121453000237740ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../backtrace.rb module GraphQL class Backtrace module InspectResult module_function def inspect_result(obj) case obj when Hash "{" + obj.map do |key, val| "#{key}: #{inspect_truncated(val)}" end.join(", ") + "}" when Array "[" + obj.map { |v| inspect_truncated(v) }.join(", ") + "]" when Query::Context::SharedMethods if obj.invalid_null? "nil" else inspect_truncated(obj.value) end else inspect_truncated(obj) end end def inspect_truncated(obj) case obj when Hash "{...}" when Array "[...]" when Query::Context::SharedMethods if obj.invalid_null? "nil" else inspect_truncated(obj.value) end when GraphQL::Execution::Lazy "(unresolved)" else "#{obj.inspect}" end end end end end graphql-ruby-1.11.10/lib/graphql/backtrace/table.rb000066400000000000000000000072231414121453000220260ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../backtrace.rb module GraphQL class Backtrace # A class for turning a context into a human-readable table or array class Table MIN_COL_WIDTH = 4 MAX_COL_WIDTH = 100 HEADERS = [ "Loc", "Field", "Object", "Arguments", "Result", ] def initialize(context, value:) @context = context @override_value = value end # @return [String] A table layout of backtrace with metadata def to_table @to_table ||= render_table(rows) end # @return [Array] An array of position + field name entries def to_backtrace @to_backtrace ||= begin backtrace = rows.map { |r| "#{r[0]}: #{r[1]}" } # skip the header entry backtrace.shift backtrace end end private def rows @rows ||= build_rows(@context, rows: [HEADERS], top: true) end # @return [String] def render_table(rows) max = Array.new(HEADERS.length, MIN_COL_WIDTH) rows.each do |row| row.each_with_index do |col, idx| col_len = col.length max_len = max[idx] if col_len > max_len if col_len > MAX_COL_WIDTH max[idx] = MAX_COL_WIDTH else max[idx] = col_len end end end end table = "".dup last_col_idx = max.length - 1 rows.each do |row| table << row.map.each_with_index do |col, idx| max_len = max[idx] if idx < last_col_idx col = col.ljust(max_len) end if col.length > max_len col = col[0, max_len - 3] + "..." end col end.join(" | ") table << "\n" end table end # @return [Array] 5 items for a backtrace table (not `key`) def build_rows(context_entry, rows:, top: false) case context_entry when GraphQL::Query::Context::FieldResolutionContext ctx = context_entry field_name = "#{ctx.irep_node.owner_type.name}.#{ctx.field.name}" position = "#{ctx.ast_node.line}:#{ctx.ast_node.col}" field_alias = ctx.ast_node.alias object = ctx.object if object.is_a?(GraphQL::Schema::Object) object = object.object end rows << [ "#{position}", "#{field_name}#{field_alias ? " as #{field_alias}" : ""}", "#{object.inspect}", ctx.irep_node.arguments.to_h.inspect, Backtrace::InspectResult.inspect_result(top && @override_value ? @override_value : ctx.value), ] build_rows(ctx.parent, rows: rows) when GraphQL::Query::Context query = context_entry.query op = query.selected_operation if op op_type = op.operation_type position = "#{op.line}:#{op.col}" else op_type = "query" position = "?:?" end op_name = query.selected_operation_name object = query.root_value if object.is_a?(GraphQL::Schema::Object) object = object.object end rows << [ "#{position}", "#{op_type}#{op_name ? " #{op_name}" : ""}", "#{object.inspect}", query.variables.to_h.inspect, Backtrace::InspectResult.inspect_result(query.context.value), ] else raise "Unexpected get_rows subject #{context_entry.inspect}" end end end end end graphql-ruby-1.11.10/lib/graphql/backtrace/traced_error.rb000066400000000000000000000033611414121453000234110ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../backtrace.rb module GraphQL class Backtrace # When {Backtrace} is enabled, raised errors are wrapped with {TracedError}. class TracedError < GraphQL::Error # @return [Array] Printable backtrace of GraphQL error context attr_reader :graphql_backtrace # @return [GraphQL::Query::Context] The context at the field where the error was raised attr_reader :context MESSAGE_TEMPLATE = <<-MESSAGE Unhandled error during GraphQL execution: %{cause_message} %{cause_backtrace} %{cause_backtrace_more} Use #cause to access the original exception (including #cause.backtrace). GraphQL Backtrace: %{graphql_table} MESSAGE # This many lines of the original Ruby backtrace # are included in the message CAUSE_BACKTRACE_PREVIEW_LENGTH = 10 def initialize(err, current_ctx) @context = current_ctx backtrace = Backtrace.new(current_ctx, value: err) @graphql_backtrace = backtrace.to_a cause_backtrace_preview = err.backtrace.first(CAUSE_BACKTRACE_PREVIEW_LENGTH).join("\n ") cause_backtrace_remainder_length = err.backtrace.length - CAUSE_BACKTRACE_PREVIEW_LENGTH cause_backtrace_more = if cause_backtrace_remainder_length < 0 "" elsif cause_backtrace_remainder_length == 1 "... and 1 more line\n" else "... and #{cause_backtrace_remainder_length} more lines\n" end message = MESSAGE_TEMPLATE % { cause_message: err.message, cause_backtrace: cause_backtrace_preview, cause_backtrace_more: cause_backtrace_more, graphql_table: backtrace.inspect, } super(message) end end end end graphql-ruby-1.11.10/lib/graphql/backtrace/tracer.rb000066400000000000000000000031331414121453000222130ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Backtrace module Tracer module_function # Implement the {GraphQL::Tracing} API. def trace(key, metadata) push_data = case key when "lex", "parse" # No context here, don't have a query yet nil when "execute_multiplex", "analyze_multiplex" metadata[:multiplex].queries when "validate", "analyze_query", "execute_query", "execute_query_lazy" metadata[:query] || metadata[:queries] when "execute_field", "execute_field_lazy" # The interpreter passes `query:`, legacy passes `context:` metadata[:context] || ((q = metadata[:query]) && q.context) else # Custom key, no backtrace data for this nil end if push_data Thread.current[:last_graphql_backtrace_context] = push_data end if key == "execute_multiplex" begin yield rescue StandardError => err # This is an unhandled error from execution, # Re-raise it with a GraphQL trace. potential_context = Thread.current[:last_graphql_backtrace_context] if potential_context.is_a?(GraphQL::Query::Context) || potential_context.is_a?(GraphQL::Query::Context::FieldResolutionContext) raise TracedError.new(err, potential_context) else raise end ensure Thread.current[:last_graphql_backtrace_context] = nil end else yield end end end end end graphql-ruby-1.11.10/lib/graphql/backwards_compatibility.rb000066400000000000000000000036371414121453000237170ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Helpers for migrating in a backwards-compatible way # @api private module BackwardsCompatibility module_function # Given a callable whose API used to take `from` arguments, # check its arity, and if needed, apply a wrapper so that # it can be called with `to` arguments. # If a wrapper is applied, warn the application with `name`. # # If `last`, then use the last arguments to call the function. def wrap_arity(callable, from:, to:, name:, last: false) arity = get_arity(callable) if arity == to || arity < 0 # It already matches, return it as is callable elsif arity == from # It has the old arity, so wrap it with an arity converter message ="#{name} with #{from} arguments is deprecated, it now accepts #{to} arguments, see:" backtrace = caller(0, 20) # Find the first line in the trace that isn't library internals: user_line = backtrace.find {|l| l !~ /lib\/graphql/ } warn(message + "\n" + user_line + "\n") wrapper = last ? LastArgumentsWrapper : FirstArgumentsWrapper wrapper.new(callable, from) else raise "Can't wrap #{callable} (arity: #{arity}) to have arity #{to}" end end def get_arity(callable) case callable when Method, Proc callable.arity else callable.method(:call).arity end end class FirstArgumentsWrapper def initialize(callable, old_arity) @callable = callable @old_arity = old_arity end def call(*args) backwards_compat_args = args.first(@old_arity) @callable.call(*backwards_compat_args) end end class LastArgumentsWrapper < FirstArgumentsWrapper def call(*args) backwards_compat_args = args.last(@old_arity) @callable.call(*backwards_compat_args) end end end end graphql-ruby-1.11.10/lib/graphql/base_type.rb000066400000000000000000000140671414121453000207770ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/relay/type_extensions" module GraphQL # The parent for all type classes. class BaseType include GraphQL::Define::NonNullWithBang include GraphQL::Define::InstanceDefinable include GraphQL::Relay::TypeExtensions accepts_definitions :name, :description, :introspection, :default_scalar, :default_relay, { connection: GraphQL::Define::AssignConnection, global_id_field: GraphQL::Define::AssignGlobalIdField, } ensure_defined(:graphql_name, :name, :description, :introspection?, :default_scalar?) attr_accessor :ast_node def initialize @introspection = false @default_scalar = false @default_relay = false end def initialize_copy(other) super # Reset these derived defaults @connection_type = nil @edge_type = nil end # @return [String] the name of this type, must be unique within a Schema attr_reader :name # Future-compatible alias # @see {GraphQL::SchemaMember} alias :graphql_name :name # Future-compatible alias # @see {GraphQL::SchemaMember} alias :graphql_definition :itself def type_class metadata[:type_class] end def name=(name) GraphQL::NameValidator.validate!(name) @name = name end # @return [String, nil] a description for this type attr_accessor :description # @return [Boolean] Is this type a predefined introspection type? def introspection? @introspection end # @return [Boolean] Is this type a built-in scalar type? (eg, `String`, `Int`) def default_scalar? @default_scalar end # @return [Boolean] Is this type a built-in Relay type? (`Node`, `PageInfo`) def default_relay? @default_relay end # @api private attr_writer :introspection, :default_scalar, :default_relay # @param other [GraphQL::BaseType] compare to this object # @return [Boolean] are these types equivalent? (incl. non-null, list) # @see {ModifiesAnotherType#==} for override on List & NonNull types def ==(other) other.is_a?(GraphQL::BaseType) && self.name == other.name end # If this type is modifying an underlying type, # return the underlying type. (Otherwise, return `self`.) def unwrap self end # @return [GraphQL::NonNullType] a non-null version of this type def to_non_null_type GraphQL::NonNullType.new(of_type: self) end # @return [GraphQL::ListType] a list version of this type def to_list_type GraphQL::ListType.new(of_type: self) end module ModifiesAnotherType def unwrap self.of_type.unwrap end def ==(other) other.is_a?(ModifiesAnotherType) && other.of_type == of_type end end # Find out which possible type to use for `value`. # Returns self if there are no possible types (ie, not Union or Interface) def resolve_type(value, ctx) self end # Print the human-readable name of this type using the query-string naming pattern def to_s name end alias :inspect :to_s alias :to_type_signature :to_s def valid_isolated_input?(value) valid_input?(value, GraphQL::Query::NullContext) end def validate_isolated_input(value) validate_input(value, GraphQL::Query::NullContext) end def coerce_isolated_input(value) coerce_input(value, GraphQL::Query::NullContext) end def coerce_isolated_result(value) coerce_result(value, GraphQL::Query::NullContext) end def valid_input?(value, ctx = nil) if ctx.nil? warn_deprecated_coerce("valid_isolated_input?") ctx = GraphQL::Query::NullContext end validate_input(value, ctx).valid? end def validate_input(value, ctx = nil) if ctx.nil? warn_deprecated_coerce("validate_isolated_input") ctx = GraphQL::Query::NullContext end if value.nil? GraphQL::Query::InputValidationResult.new else validate_non_null_input(value, ctx) end end def coerce_input(value, ctx = nil) if value.nil? nil else if ctx.nil? warn_deprecated_coerce("coerce_isolated_input") ctx = GraphQL::Query::NullContext end coerce_non_null_input(value, ctx) end end def coerce_result(value, ctx) raise GraphQL::RequiredImplementationMissingError end # Types with fields may override this # @param name [String] field name to lookup for this type # @return [GraphQL::Field, nil] def get_field(name) nil end # During schema definition, types can be defined inside procs or as strings. # This function converts it to a type instance # @return [GraphQL::BaseType] def self.resolve_related_type(type_arg) case type_arg when Proc # lazy-eval it, then try again resolve_related_type(type_arg.call) when String # Get a constant by this name resolve_related_type(Object.const_get(type_arg)) else if type_arg.respond_to?(:graphql_definition) type_arg.graphql_definition else type_arg end end end # Return a GraphQL string for the type definition # @param schema [GraphQL::Schema] # @param printer [GraphQL::Schema::Printer] # @see {GraphQL::Schema::Printer#initialize for additional options} # @return [String] type definition def to_definition(schema, printer: nil, **args) printer ||= GraphQL::Schema::Printer.new(schema, **args) printer.print_type(self) end # Returns true if this is a non-nullable type. A nullable list of non-nullables is considered nullable. def non_null? false end # Returns true if this is a list type. A non-nullable list is considered a list. def list? false end private def warn_deprecated_coerce(alt_method_name) warn("Coercing without a context is deprecated; use `#{alt_method_name}` if you don't want context-awareness") end end end graphql-ruby-1.11.10/lib/graphql/boolean_type.rb000066400000000000000000000001411414121453000214700ustar00rootroot00000000000000# frozen_string_literal: true GraphQL::BOOLEAN_TYPE = GraphQL::Types::Boolean.graphql_definition graphql-ruby-1.11.10/lib/graphql/coercion_error.rb000066400000000000000000000005301414121453000220240ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class CoercionError < GraphQL::Error # @return [Hash] Optional custom data for error objects which will be added # under the `extensions` key. attr_accessor :extensions def initialize(message, extensions: nil) @extensions = extensions super(message) end end end graphql-ruby-1.11.10/lib/graphql/compatibility.rb000066400000000000000000000004121414121453000216620ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/compatibility/execution_specification" require "graphql/compatibility/lazy_execution_specification" require "graphql/compatibility/query_parser_specification" require "graphql/compatibility/schema_parser_specification" graphql-ruby-1.11.10/lib/graphql/compatibility/000077500000000000000000000000001414121453000213405ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/compatibility/execution_specification.rb000066400000000000000000000336741414121453000266050ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/compatibility/execution_specification/counter_schema" require "graphql/compatibility/execution_specification/specification_schema" module GraphQL module Compatibility # Test an execution strategy. This spec is not meant as a development aid. # Rather, when the strategy _works_, run it here to see if it has any differences # from the built-in strategy. # # - Custom scalar input / output # - Null propagation # - Query-level masking # - Directive support # - Typecasting # - Error handling (raise / return GraphQL::ExecutionError) # - Provides Irep & AST node to resolve fn # - Skipping fields # # Some things are explicitly _not_ tested here, because they're handled # by other parts of the system: # # - Schema definition (including types and fields) # - Parsing & parse errors # - AST -> IRep transformation (eg, fragment merging) # - Query validation and analysis # - Relay features # module ExecutionSpecification # Make a minitest suite for this execution strategy, making sure it # fulfills all the requirements of this library. # @param execution_strategy [<#new, #execute>] An execution strategy class # @return [Class] A test suite for this execution strategy def self.build_suite(execution_strategy) Class.new(Minitest::Test) do class << self attr_accessor :counter_schema, :specification_schema end self.specification_schema = SpecificationSchema.build(execution_strategy) self.counter_schema = CounterSchema.build(execution_strategy) def execute_query(query_string, **kwargs) kwargs[:root_value] = SpecificationSchema::DATA self.class.specification_schema.execute(query_string, **kwargs) end def test_it_fetches_data query_string = %| query getData($nodeId: ID = "1001") { flh: node(id: $nodeId) { __typename ... on Person { name @include(if: true) skippedName: name @skip(if: true) birthdate age(on: 1477660133) } ... on NamedEntity { ne_tn: __typename ne_n: name } ... on Organization { org_n: name } } } | res = execute_query(query_string) assert_equal nil, res["errors"], "It doesn't have an errors key" flh = res["data"]["flh"] assert_equal "Fannie Lou Hamer", flh["name"], "It returns values" assert_equal Time.new(1917, 10, 6).to_i, flh["birthdate"], "It returns custom scalars" assert_equal 99, flh["age"], "It runs resolve functions" assert_equal "Person", flh["__typename"], "It serves __typename" assert_equal "Person", flh["ne_tn"], "It serves __typename on interfaces" assert_equal "Fannie Lou Hamer", flh["ne_n"], "It serves interface fields" assert_equal false, flh.key?("skippedName"), "It obeys @skip" assert_equal false, flh.key?("org_n"), "It doesn't apply other type fields" end def test_it_iterates_over_each query_string = %| query getData($nodeId: ID = "1002") { node(id: $nodeId) { ... on Person { organizations { name } } } } | res = execute_query(query_string) assert_equal ["SNCC"], res["data"]["node"]["organizations"].map { |o| o["name"] } end def test_it_skips_skipped_fields query_str = <<-GRAPHQL { o3001: organization(id: "3001") { name } o2001: organization(id: "2001") { name } } GRAPHQL res = execute_query(query_str) assert_equal ["o2001"], res["data"].keys assert_equal false, res.key?("errors") end def test_it_propagates_nulls_to_field query_string = %| query getOrg($id: ID = "2001"){ failure: node(id: $id) { ... on Organization { name leader { name } } } success: node(id: $id) { ... on Organization { name } } } | res = execute_query(query_string) failure = res["data"]["failure"] success = res["data"]["success"] assert_equal nil, failure, "It propagates nulls to the next nullable field" assert_equal({"name" => "SNCC"}, success, "It serves the same object if no invalid null is encountered") assert_equal 1, res["errors"].length , "It returns an error for the invalid null" end def test_it_propages_nulls_to_operation query_string = %| { foundOrg: organization(id: "2001") { name } organization(id: "2999") { name } } | res = execute_query(query_string) assert_equal nil, res["data"] assert_equal 1, res["errors"].length end def test_it_exposes_raised_and_returned_user_execution_errors query_string = %| { organization(id: "2001") { name returnedError raisedError } organizations { returnedError raisedError } } | res = execute_query(query_string) assert_equal "SNCC", res["data"]["organization"]["name"], "It runs the rest of the query" expected_errors = [ { "message"=>"This error was returned", "locations"=>[{"line"=>5, "column"=>19}], "path"=>["organization", "returnedError"] }, { "message"=>"This error was raised", "locations"=>[{"line"=>6, "column"=>19}], "path"=>["organization", "raisedError"] }, { "message"=>"This error was raised", "locations"=>[{"line"=>10, "column"=>19}], "path"=>["organizations", 0, "raisedError"] }, { "message"=>"This error was raised", "locations"=>[{"line"=>10, "column"=>19}], "path"=>["organizations", 1, "raisedError"] }, { "message"=>"This error was returned", "locations"=>[{"line"=>9, "column"=>19}], "path"=>["organizations", 0, "returnedError"] }, { "message"=>"This error was returned", "locations"=>[{"line"=>9, "column"=>19}], "path"=>["organizations", 1, "returnedError"] }, ] expected_errors.each do |expected_err| assert_includes res["errors"], expected_err end end def test_it_applies_masking no_org = ->(member, ctx) { member.name == "Organization" } query_string = %| { node(id: "2001") { __typename } }| err = assert_raises(GraphQL::UnresolvedTypeError) { execute_query(query_string, except: no_org) } query_string = %| { organization(id: "2001") { name } }| res = execute_query(query_string, except: no_org) assert_equal nil, res["data"] assert_equal 1, res["errors"].length assert_equal "SNCC", err.value.name assert_equal GraphQL::Relay::Node.interface, err.field.type assert_equal 1, err.possible_types.length assert_equal "Organization", err.resolved_type.name assert_equal "Query", err.parent_type.name query_string = %| { __type(name: "Organization") { name } }| res = execute_query(query_string, except: no_org) assert_equal nil, res["data"]["__type"] assert_equal nil, res["errors"] end def test_it_provides_nodes_to_resolve query_string = %| { organization(id: "2001") { name nodePresence } }| res = execute_query(query_string) assert_equal "SNCC", res["data"]["organization"]["name"] assert_equal [true, true, false], res["data"]["organization"]["nodePresence"] end def test_it_runs_the_introspection_query execute_query(GraphQL::Introspection::INTROSPECTION_QUERY) end def test_it_propagates_deeply_nested_nulls query_string = %| { node(id: "1001") { ... on Person { name first_organization { leader { name } } } } } | res = execute_query(query_string) assert_equal nil, res["data"]["node"] assert_equal 1, res["errors"].length end def test_it_doesnt_add_errors_for_invalid_nulls_from_execution_errors query_string = %| query getOrg($id: ID = "2001"){ failure: node(id: $id) { ... on Organization { name leader { name } } } } | res = execute_query(query_string, context: {return_error: true}) error_messages = res["errors"].map { |e| e["message"] } assert_equal ["Error on Nullable"], error_messages end def test_it_only_resolves_fields_once_on_typed_fragments res = self.class.counter_schema.execute(" { counter { count } ... on HasCounter { counter { count } } } ") expected_data = { "counter" => { "count" => 1 } } assert_equal expected_data, res["data"] assert_equal 1, self.class.counter_schema.metadata[:count] # Deep typed children are correctly distinguished: res = self.class.counter_schema.execute(" { counter { ... on Counter { counter { count } } ... on AltCounter { counter { count, t: __typename } } } } ") expected_data = { "counter" => { "counter" => { "count" => 2 } } } assert_equal expected_data, res["data"] end def test_it_runs_middleware log = [] query_string = %| { node(id: "2001") { __typename } }| execute_query(query_string, context: {middleware_log: log}) assert_equal ["node", "__typename"], log end def test_it_uses_type_error_hooks_for_invalid_nulls log = [] query_string = %| { node(id: "1001") { ... on Person { name first_organization { leader { name } } } } }| res = execute_query(query_string, context: { type_errors: log }) assert_equal nil, res["data"]["node"] assert_equal [nil], log end def test_it_uses_type_error_hooks_for_failed_type_resolution log = [] query_string = %| { node(id: "2003") { __typename } }| assert_raises(GraphQL::UnresolvedTypeError) { execute_query(query_string, context: { type_errors: log }) } assert_equal [SpecificationSchema::BOGUS_NODE], log end def test_it_treats_failed_type_resolution_like_nil log = [] ctx = { type_errors: log, gobble: true } query_string = %| { node(id: "2003") { __typename } }| res = execute_query(query_string, context: ctx) assert_equal nil, res["data"]["node"] assert_equal false, res.key?("errors") assert_equal [SpecificationSchema::BOGUS_NODE], log query_string_2 = %| { requiredNode(id: "2003") { __typename } }| res = execute_query(query_string_2, context: ctx) assert_equal nil, res["data"] assert_equal false, res.key?("errors") assert_equal [SpecificationSchema::BOGUS_NODE, SpecificationSchema::BOGUS_NODE], log end def test_it_skips_connections query_type = GraphQL::ObjectType.define do name "Query" connection :skipped, types[query_type], resolve: ->(o,a,c) { c.skip } end schema = GraphQL::Schema.define(query: query_type) res = schema.execute("{ skipped { __typename } }") assert_equal({"data" => nil}, res) end end end end end end graphql-ruby-1.11.10/lib/graphql/compatibility/execution_specification/000077500000000000000000000000001414121453000262435ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/compatibility/execution_specification/counter_schema.rb000066400000000000000000000034241414121453000315720ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Compatibility module ExecutionSpecification module CounterSchema def self.build(execution_strategy) counter_type = nil schema = nil has_count_interface = GraphQL::InterfaceType.define do name "HasCount" field :count, types.Int field :counter, ->{ has_count_interface } end counter_type = GraphQL::ObjectType.define do name "Counter" interfaces [has_count_interface] field :count, types.Int, resolve: ->(o,a,c) { schema.metadata[:count] += 1 } field :counter, has_count_interface, resolve: ->(o,a,c) { :counter } end alt_counter_type = GraphQL::ObjectType.define do name "AltCounter" interfaces [has_count_interface] field :count, types.Int, resolve: ->(o,a,c) { schema.metadata[:count] += 1 } field :counter, has_count_interface, resolve: ->(o,a,c) { :counter } end has_counter_interface = GraphQL::InterfaceType.define do name "HasCounter" field :counter, has_count_interface end query_type = GraphQL::ObjectType.define do name "Query" interfaces [has_counter_interface] field :counter, has_count_interface, resolve: ->(o,a,c) { :counter } end schema = GraphQL::Schema.define( query: query_type, resolve_type: ->(t, o, c) { o == :counter ? counter_type : nil }, orphan_types: [alt_counter_type, counter_type], query_execution_strategy: execution_strategy, ) schema.metadata[:count] = 0 schema end end end end end graphql-ruby-1.11.10/lib/graphql/compatibility/execution_specification/specification_schema.rb000066400000000000000000000144541414121453000327400ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Compatibility module ExecutionSpecification module SpecificationSchema BOGUS_NODE = OpenStruct.new({ bogus: true }) DATA = { "1001" => OpenStruct.new({ name: "Fannie Lou Hamer", birthdate: Time.new(1917, 10, 6), organization_ids: [], }), "1002" => OpenStruct.new({ name: "John Lewis", birthdate: Time.new(1940, 2, 21), organization_ids: ["2001"], }), "1003" => OpenStruct.new({ name: "Diane Nash", birthdate: Time.new(1938, 5, 15), organization_ids: ["2001", "2002"], }), "1004" => OpenStruct.new({ name: "Ralph Abernathy", birthdate: Time.new(1926, 3, 11), organization_ids: ["2002"], }), "2001" => OpenStruct.new({ name: "SNCC", leader_id: nil, # fail on purpose }), "2002" => OpenStruct.new({ name: "SCLC", leader_id: "1004", }), "2003" => BOGUS_NODE, } # A list object must implement #each class CustomCollection def initialize(storage) @storage = storage end def each(&block) @storage.each(&block) end end module TestMiddleware def self.call(parent_type, parent_object, field_definition, field_args, query_context, &next_middleware) query_context[:middleware_log] && query_context[:middleware_log] << field_definition.name next_middleware.call end end def self.build(execution_strategy) organization_type = nil timestamp_type = GraphQL::ScalarType.define do name "Timestamp" coerce_input ->(value, _ctx) { Time.at(value.to_i) } coerce_result ->(value, _ctx) { value.to_i } end named_entity_interface_type = GraphQL::InterfaceType.define do name "NamedEntity" field :name, !types.String end person_type = GraphQL::ObjectType.define do name "Person" interfaces [named_entity_interface_type] field :name, !types.String field :birthdate, timestamp_type field :age, types.Int do argument :on, !timestamp_type resolve ->(obj, args, ctx) { if obj.birthdate.nil? nil else age_on = args[:on] age_years = age_on.year - obj.birthdate.year this_year_birthday = Time.new(age_on.year, obj.birthdate.month, obj.birthdate.day) if this_year_birthday > age_on age_years -= 1 end end age_years } end field :organizations, types[organization_type] do resolve ->(obj, args, ctx) { CustomCollection.new(obj.organization_ids.map { |id| DATA[id] }) } end field :first_organization, !organization_type do resolve ->(obj, args, ctx) { DATA[obj.organization_ids.first] } end end organization_type = GraphQL::ObjectType.define do name "Organization" interfaces [named_entity_interface_type] field :name, !types.String field :leader, !person_type do resolve ->(obj, args, ctx) { DATA[obj.leader_id] || (ctx[:return_error] ? ExecutionError.new("Error on Nullable") : nil) } end field :returnedError, types.String do resolve ->(o, a, c) { GraphQL::ExecutionError.new("This error was returned") } end field :raisedError, types.String do resolve ->(o, a, c) { raise GraphQL::ExecutionError.new("This error was raised") } end field :nodePresence, !types[!types.Boolean] do resolve ->(o, a, ctx) { [ ctx.irep_node.is_a?(GraphQL::InternalRepresentation::Node), ctx.ast_node.is_a?(GraphQL::Language::Nodes::AbstractNode), false, # just testing ] } end end node_union_type = GraphQL::UnionType.define do name "Node" possible_types [person_type, organization_type] end query_type = GraphQL::ObjectType.define do name "Query" field :node, node_union_type do argument :id, !types.ID resolve ->(obj, args, ctx) { obj[args[:id]] } end field :requiredNode, node_union_type.to_non_null_type do argument :id, !types.ID resolve ->(obj, args, ctx) { obj[args[:id]] } end field :organization, !organization_type do argument :id, !types.ID resolve ->(obj, args, ctx) { if args[:id].start_with?("2") obj[args[:id]] else # test context.skip ctx.skip end } end field :organizations, types[organization_type] do resolve ->(obj, args, ctx) { [obj["2001"], obj["2002"]] } end end GraphQL::Schema.define do query_execution_strategy execution_strategy query query_type resolve_type ->(type, obj, ctx) { if obj.respond_to?(:birthdate) person_type elsif obj.respond_to?(:leader_id) organization_type else nil end } type_error ->(err, ctx) { ctx[:type_errors] && (ctx[:type_errors] << err.value) ctx[:gobble] || GraphQL::Schema::DefaultTypeError.call(err, ctx) } middleware(TestMiddleware) end end end end end end graphql-ruby-1.11.10/lib/graphql/compatibility/lazy_execution_specification.rb000066400000000000000000000143161414121453000276340ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/compatibility/lazy_execution_specification/lazy_schema" module GraphQL module Compatibility module LazyExecutionSpecification # @param execution_strategy [<#new, #execute>] An execution strategy class # @return [Class] A test suite for this execution strategy def self.build_suite(execution_strategy) Class.new(Minitest::Test) do class << self attr_accessor :lazy_schema end self.lazy_schema = LazySchema.build(execution_strategy) def test_it_resolves_lazy_values pushes = [] query_str = %| { p1: push(value: 1) { value } p2: push(value: 2) { push(value: 3) { value push(value: 21) { value } } } p3: push(value: 4) { push(value: 5) { value push(value: 22) { value } } } } | res = self.class.lazy_schema.execute(query_str, context: {pushes: pushes}) expected_data = { "p1"=>{"value"=>1}, "p2"=>{"push"=>{"value"=>3, "push"=>{"value"=>21}}}, "p3"=>{"push"=>{"value"=>5, "push"=>{"value"=>22}}}, } assert_equal expected_data, res["data"] expected_pushes = [ [1,2,4], # first level [3,5], # second level [21, 22], ] assert_equal expected_pushes, pushes end def test_it_maintains_path query_str = %| { push(value: 2) { push(value: 3) { fail1: push(value: 14) { value } fail2: push(value: 14) { value } } } } | res = self.class.lazy_schema.execute(query_str, context: {pushes: []}) assert_equal nil, res["data"] # The first fail causes the second field to never resolve assert_equal 1, res["errors"].length assert_equal ["push", "push", "fail1", "value"], res["errors"][0]["path"] end def test_it_resolves_mutation_values_eagerly pushes = [] query_str = %| mutation { p1: push(value: 1) { value } p2: push(value: 2) { push(value: 3) { value } } p3: push(value: 4) { p5: push(value: 5) { value } p6: push(value: 6) { value } } } | res = self.class.lazy_schema.execute(query_str, context: {pushes: pushes}) expected_data = { "p1"=>{"value"=>1}, "p2"=>{"push"=>{"value"=>3}}, "p3"=>{"p5"=>{"value"=>5},"p6"=>{"value"=>6}}, } assert_equal expected_data, res["data"] expected_pushes = [ [1], # first operation [2], [3], # second operation [4], [5, 6], # third operation ] assert_equal expected_pushes, pushes end def test_it_resolves_lazy_connections pushes = [] query_str = %| { pushes(values: [1,2,3]) { edges { node { value push(value: 4) { value } } } } } | res = self.class.lazy_schema.execute(query_str, context: {pushes: pushes}) expected_edges = [ {"node"=>{"value"=>1, "push"=>{"value"=>4}}}, {"node"=>{"value"=>2, "push"=>{"value"=>4}}}, {"node"=>{"value"=>3, "push"=>{"value"=>4}}}, ] assert_equal expected_edges, res["data"]["pushes"]["edges"] assert_equal [[1, 2, 3], [4, 4, 4]], pushes end def test_it_calls_lazy_resolve_instrumentation query_str = %| { p1: push(value: 1) { value } p2: push(value: 2) { push(value: 3) { value } } pushes(values: [1,2,3]) { edges { node { value push(value: 4) { value } } } } } | log = [] self.class.lazy_schema.execute(query_str, context: {lazy_instrumentation: log, pushes: []}) expected_log = [ "PUSH", "Query.push: 1", "Query.push: 2", "Query.pushes: [1, 2, 3]", "PUSH", "LazyPush.push: 3", "LazyPushEdge.node: 1", "LazyPushEdge.node: 2", "LazyPushEdge.node: 3", "PUSH", "LazyPush.push: 4", "LazyPush.push: 4", "LazyPush.push: 4", ] assert_equal expected_log, log end def test_it_skips_ctx_skip query_string = <<-GRAPHQL { p0: push(value: 15) { value } p1: push(value: 1) { value } p2: push(value: 2) { value p3: push(value: 15) { value } } } GRAPHQL pushes = [] res = self.class.lazy_schema.execute(query_string, context: {pushes: pushes}) assert_equal [[1,2]], pushes assert_equal({"data"=>{"p1"=>{"value"=>1}, "p2"=>{"value"=>2}}}, res) end end end end end end graphql-ruby-1.11.10/lib/graphql/compatibility/lazy_execution_specification/000077500000000000000000000000001414121453000273025ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb000066400000000000000000000063421414121453000321330ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Compatibility module LazyExecutionSpecification module LazySchema class LazyPush attr_reader :value def initialize(ctx, value) if value == 13 @value = nil elsif value == 14 @value = GraphQL::ExecutionError.new("oops!") elsif value == 15 @skipped = true @value = ctx.skip else @value = value end @context = ctx pushes = @context[:lazy_pushes] ||= [] if !@skipped pushes << @value end end def push if @skipped @value else if @context[:lazy_pushes].include?(@value) @context[:lazy_instrumentation] && @context[:lazy_instrumentation] << "PUSH" @context[:pushes] << @context[:lazy_pushes] @context[:lazy_pushes] = [] end # Something that _behaves_ like this object, but isn't registered lazy OpenStruct.new(value: @value) end end end class LazyPushCollection def initialize(ctx, values) @ctx = ctx @values = values end def push @values.map { |v| LazyPush.new(@ctx, v) } end def value @values end end module LazyInstrumentation def self.instrument(type, field) prev_lazy_resolve = field.lazy_resolve_proc field.redefine { lazy_resolve ->(o, a, c) { result = prev_lazy_resolve.call(o, a, c) c[:lazy_instrumentation] && c[:lazy_instrumentation].push("#{type.name}.#{field.name}: #{o.value}") result } } end end def self.build(execution_strategy) lazy_push_type = GraphQL::ObjectType.define do name "LazyPush" field :value, !types.Int field :push, !lazy_push_type do argument :value, types.Int resolve ->(o, a, c) { LazyPush.new(c, a[:value]) } end end query_type = GraphQL::ObjectType.define do name "Query" field :push, !lazy_push_type do argument :value, types.Int resolve ->(o, a, c) { LazyPush.new(c, a[:value]) } end connection :pushes, lazy_push_type.connection_type do argument :values, types[types.Int], method_access: false resolve ->(o, a, c) { LazyPushCollection.new(c, a[:values]) } end end GraphQL::Schema.define do query(query_type) mutation(query_type) query_execution_strategy(execution_strategy) mutation_execution_strategy(execution_strategy) lazy_resolve(LazyPush, :push) lazy_resolve(LazyPushCollection, :push) instrument(:field, LazyInstrumentation) end end end end end end graphql-ruby-1.11.10/lib/graphql/compatibility/query_parser_specification.rb000066400000000000000000000224131414121453000273100ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/compatibility/query_parser_specification/query_assertions" require "graphql/compatibility/query_parser_specification/parse_error_specification" module GraphQL module Compatibility # This asserts that a given parse function turns a string into # the proper tree of {{GraphQL::Language::Nodes}}. module QueryParserSpecification # @yieldparam query_string [String] A query string to parse # @yieldreturn [GraphQL::Language::Nodes::Document] # @return [Class] A test suite for this parse function def self.build_suite(&block) Class.new(Minitest::Test) do include QueryAssertions include ParseErrorSpecification @@parse_fn = block def parse(query_string) @@parse_fn.call(query_string) end def test_it_parses_queries document = parse(QUERY_STRING) query = document.definitions.first assert_valid_query(query) assert_valid_fragment(document.definitions.last) assert_valid_variable(query.variables.first) field = query.selections.first assert_valid_field(field) assert_valid_variable_argument(field.arguments.first) assert_valid_literal_argument(field.arguments.last) assert_valid_directive(field.directives.first) fragment_spread = query.selections[1].selections.last assert_valid_fragment_spread(fragment_spread) assert_valid_typed_inline_fragment(query.selections[2]) assert_valid_typeless_inline_fragment(query.selections[3]) end def test_it_parses_unnamed_queries document = parse("{ name, age, height }") operation = document.definitions.first assert_equal 1, document.definitions.length assert_equal "query", operation.operation_type assert_equal nil, operation.name assert_equal 3, operation.selections.length end def test_it_parses_the_introspection_query parse(GraphQL::Introspection::INTROSPECTION_QUERY) end def test_it_parses_inputs query_string = %| { field( int: 3, float: 4.7e-24, bool: false, string: "☀︎🏆 \\b \\f \\n \\r \\t \\" \u00b6 \\u00b6 / \\/", enum: ENUM_NAME, array: [7, 8, 9] object: {a: [1,2,3], b: {c: "4"}} unicode_bom: "\xef\xbb\xbfquery" keywordEnum: on nullValue: null nullValueInObject: {a: null, b: "b"} nullValueInArray: ["a", null, "b"] blockString: """ Hello, World """ ) } | document = parse(query_string) inputs = document.definitions.first.selections.first.arguments assert_equal 3, inputs[0].value, "Integers" assert_equal 0.47e-23, inputs[1].value, "Floats" assert_equal false, inputs[2].value, "Booleans" assert_equal %|☀︎🏆 \b \f \n \r \t " ¶ ¶ / /|, inputs[3].value, "Strings" assert_instance_of GraphQL::Language::Nodes::Enum, inputs[4].value assert_equal "ENUM_NAME", inputs[4].value.name, "Enums" assert_equal [7,8,9], inputs[5].value, "Lists" obj = inputs[6].value assert_equal "a", obj.arguments[0].name assert_equal [1,2,3], obj.arguments[0].value assert_equal "b", obj.arguments[1].name assert_equal "c", obj.arguments[1].value.arguments[0].name assert_equal "4", obj.arguments[1].value.arguments[0].value assert_equal %|\xef\xbb\xbfquery|, inputs[7].value, "Unicode BOM" assert_equal "on", inputs[8].value.name, "Enum value 'on'" assert_instance_of GraphQL::Language::Nodes::NullValue, inputs[9].value args = inputs[10].value.arguments assert_instance_of GraphQL::Language::Nodes::NullValue, args.find{ |arg| arg.name == 'a' }.value assert_equal 'b', args.find{ |arg| arg.name == 'b' }.value values = inputs[11].value assert_equal 'a', values[0] assert_instance_of GraphQL::Language::Nodes::NullValue, values[1] assert_equal 'b', values[2] block_str_value = inputs[12].value assert_equal "Hello,\n World", block_str_value end def test_it_doesnt_parse_nonsense_variables query_string_1 = "query Vars($var1) { cheese(id: $var1) { flavor } }" query_string_2 = "query Vars2($var1: Int = $var1) { cheese(id: $var1) { flavor } }" err_1 = assert_raises(GraphQL::ParseError) do parse(query_string_1) end assert_equal [1,17], [err_1.line, err_1.col] err_2 = assert_raises(GraphQL::ParseError) do parse(query_string_2) end assert_equal [1,26], [err_2.line, err_2.col] end def test_enum_value_definitions_have_a_position document = parse(""" enum Enum { VALUE } """) assert_equal [3, 17], document.definitions[0].values[0].position end def test_field_definitions_have_a_position document = parse(""" type A { field: String } """) assert_equal [3, 17], document.definitions[0].fields[0].position end def test_input_value_definitions_have_a_position document = parse(""" input A { field: String } """) assert_equal [3, 17], document.definitions[0].fields[0].position end def test_parses_when_there_are_no_interfaces schema = " type A { a: String } " document = parse(schema) assert_equal [], document.definitions[0].interfaces.map(&:name) end def test_parses_implements_with_leading_ampersand schema = " type A implements & B { a: String } " document = parse(schema) assert_equal ["B"], document.definitions[0].interfaces.map(&:name) assert_equal [2, 35], document.definitions[0].interfaces[0].position end def test_parses_implements_with_leading_ampersand_and_multiple_interfaces schema = " type A implements & B & C { a: String } " document = parse(schema) assert_equal ["B", "C"], document.definitions[0].interfaces.map(&:name) assert_equal [2, 35], document.definitions[0].interfaces[0].position assert_equal [2, 39], document.definitions[0].interfaces[1].position end def test_parses_implements_without_leading_ampersand schema = " type A implements B { a: String } " document = parse(schema) assert_equal ["B"], document.definitions[0].interfaces.map(&:name) assert_equal [2, 33], document.definitions[0].interfaces[0].position end def test_parses_implements_without_leading_ampersand_and_multiple_interfaces schema = " type A implements B & C { a: String } " document = parse(schema) assert_equal ["B", "C"], document.definitions[0].interfaces.map(&:name) assert_equal [2, 33], document.definitions[0].interfaces[0].position assert_equal [2, 37], document.definitions[0].interfaces[1].position end def test_supports_old_syntax_for_parsing_multiple_interfaces schema = " type A implements B, C { a: String } " document = parse(schema) assert_equal ["B", "C"], document.definitions[0].interfaces.map(&:name) assert_equal [2, 33], document.definitions[0].interfaces[0].position assert_equal [2, 36], document.definitions[0].interfaces[1].position end end end QUERY_STRING = %| query getStuff($someVar: Int = 1, $anotherVar: [String!] ) @skip(if: false) { myField: someField(someArg: $someVar, ok: 1.4) @skip(if: $anotherVar) @thing(or: "Whatever") anotherField(someArg: [1,2,3]) { nestedField ... moreNestedFields @skip(if: true) } ... on OtherType @include(unless: false){ field(arg: [{key: "value", anotherKey: 0.9, anotherAnotherKey: WHATEVER}]) anotherField } ... { id } } fragment moreNestedFields on NestedType @or(something: "ok") { anotherNestedField @enum(directive: true) } | end end end graphql-ruby-1.11.10/lib/graphql/compatibility/query_parser_specification/000077500000000000000000000000001414121453000267615ustar00rootroot00000000000000parse_error_specification.rb000066400000000000000000000052331414121453000344550ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/compatibility/query_parser_specification# frozen_string_literal: true module GraphQL module Compatibility module QueryParserSpecification module ParseErrorSpecification def assert_raises_parse_error(query_string) assert_raises(GraphQL::ParseError) { parse(query_string) } end def test_it_includes_line_and_column err = assert_raises_parse_error(" query getCoupons { allCoupons: {data{id}} } ") assert_includes(err.message, '{') assert_equal(3, err.line) assert_equal(27, err.col) end def test_it_rejects_unterminated_strings assert_raises_parse_error('{ " }') assert_raises_parse_error(%|{ "\n" }|) end def test_it_rejects_unexpected_ends assert_raises_parse_error("query { stuff { thing }") end def assert_rejects_character(char) err = assert_raises_parse_error("{ field#{char} }") expected_char = char.inspect.gsub('"', '').downcase msg_downcase = err.message.downcase # Case-insensitive for UTF-8 printing assert_includes(msg_downcase, expected_char, "The message includes the invalid character") end def test_it_rejects_invalid_characters assert_rejects_character(";") assert_rejects_character("\a") assert_rejects_character("\xef") assert_rejects_character("\v") assert_rejects_character("\f") assert_rejects_character("\xa0") end def test_it_rejects_bad_unicode assert_raises_parse_error(%|{ field(arg:"\\x") }|) assert_raises_parse_error(%|{ field(arg:"\\u1") }|) assert_raises_parse_error(%|{ field(arg:"\\u0XX1") }|) assert_raises_parse_error(%|{ field(arg:"\\uXXXX") }|) assert_raises_parse_error(%|{ field(arg:"\\uFXXX") }|) assert_raises_parse_error(%|{ field(arg:"\\uXXXF") }|) end def test_it_rejects_empty_inline_fragments assert_raises_parse_error(" query { viewer { login { ... on String { } } } } ") end def test_it_rejects_blank_queries assert_raises_parse_error("") assert_raises_parse_error(" ") assert_raises_parse_error("\t \t") assert_raises_parse_error(" # comment ") end def test_it_restricts_on assert_raises_parse_error("{ ...on }") assert_raises_parse_error("fragment on on Type { field }") end end end end end graphql-ruby-1.11.10/lib/graphql/compatibility/query_parser_specification/query_assertions.rb000066400000000000000000000057171414121453000327370ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Compatibility module QueryParserSpecification module QueryAssertions def assert_valid_query(query) assert query.is_a?(GraphQL::Language::Nodes::OperationDefinition) assert_equal "getStuff", query.name assert_equal "query", query.operation_type assert_equal 2, query.variables.length assert_equal 4, query.selections.length assert_equal 1, query.directives.length assert_equal [2, 13], [query.line, query.col] end def assert_valid_fragment(fragment_def) assert fragment_def.is_a?(GraphQL::Language::Nodes::FragmentDefinition) assert_equal "moreNestedFields", fragment_def.name assert_equal 1, fragment_def.selections.length assert_equal "NestedType", fragment_def.type.name assert_equal 1, fragment_def.directives.length assert_equal [20, 13], fragment_def.position end def assert_valid_variable(variable) assert_equal "someVar", variable.name assert_equal "Int", variable.type.name assert_equal 1, variable.default_value assert_equal [2, 28], variable.position end def assert_valid_field(field) assert_equal "someField", field.name assert_equal "myField", field.alias assert_equal 2, field.directives.length assert_equal 2, field.arguments.length assert_equal 0, field.selections.length assert_equal [3, 15], field.position end def assert_valid_literal_argument(argument) assert_equal "ok", argument.name assert_equal 1.4, argument.value end def assert_valid_variable_argument(argument) assert_equal "someArg", argument.name assert_equal "someVar", argument.value.name end def assert_valid_fragment_spread(fragment_spread) assert_equal "moreNestedFields", fragment_spread.name assert_equal 1, fragment_spread.directives.length assert_equal [7, 17], fragment_spread.position end def assert_valid_directive(directive) assert_equal "skip", directive.name assert_equal "if", directive.arguments.first.name assert_equal 1, directive.arguments.length assert_equal [3, 62], directive.position end def assert_valid_typed_inline_fragment(inline_fragment) assert_equal "OtherType", inline_fragment.type.name assert_equal 2, inline_fragment.selections.length assert_equal 1, inline_fragment.directives.length assert_equal [10, 15], inline_fragment.position end def assert_valid_typeless_inline_fragment(inline_fragment) assert_equal nil, inline_fragment.type assert_equal 1, inline_fragment.selections.length assert_equal 0, inline_fragment.directives.length end end end end end graphql-ruby-1.11.10/lib/graphql/compatibility/schema_parser_specification.rb000066400000000000000000000713601414121453000274100ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Compatibility # This asserts that a given parse function turns a string into # the proper tree of {{GraphQL::Language::Nodes}}. module SchemaParserSpecification # @yieldparam query_string [String] A query string to parse # @yieldreturn [GraphQL::Language::Nodes::Document] # @return [Class] A test suite for this parse function def self.build_suite(&block) Class.new(Minitest::Test) do @@parse_fn = block def parse(query_string) @@parse_fn.call(query_string) end def test_it_parses_object_types document = parse(' # This is what # somebody said about something type Comment implements Node @deprecated(reason: "No longer supported") { id: ID! } ') type = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeDefinition, type.class assert_equal 'Comment', type.name assert_equal "This is what\nsomebody said about something", type.description assert_equal ['Node'], type.interfaces.map(&:name) assert_equal ['id'], type.fields.map(&:name) assert_equal [], type.fields[0].arguments assert_equal 'ID', type.fields[0].type.of_type.name assert_equal 1, type.directives.length deprecated_directive = type.directives[0] assert_equal 'deprecated', deprecated_directive.name assert_equal 'reason', deprecated_directive.arguments[0].name assert_equal 'No longer supported', deprecated_directive.arguments[0].value end def test_it_parses_scalars document = parse('scalar DateTime') type = document.definitions.first assert_equal GraphQL::Language::Nodes::ScalarTypeDefinition, type.class assert_equal 'DateTime', type.name end def test_it_parses_enum_types document = parse(' enum DogCommand { # Good dog SIT DOWN @deprecated(reason: "No longer supported") HEEL } ') type = document.definitions.first assert_equal GraphQL::Language::Nodes::EnumTypeDefinition, type.class assert_equal 'DogCommand', type.name assert_equal 3, type.values.length assert_equal 'SIT', type.values[0].name assert_equal [], type.values[0].directives assert_equal "Good dog", type.values[0].description assert_equal 'DOWN', type.values[1].name assert_equal 1, type.values[1].directives.length deprecated_directive = type.values[1].directives[0] assert_equal 'deprecated', deprecated_directive.name assert_equal 'reason', deprecated_directive.arguments[0].name assert_equal 'No longer supported', deprecated_directive.arguments[0].value assert_equal 'HEEL', type.values[2].name assert_equal [], type.values[2].directives end def test_it_parses_union_types document = parse( "union BagOfThings = \n" \ "A |\n" \ "B |\n" \ "C" ) union = document.definitions.first assert_equal GraphQL::Language::Nodes::UnionTypeDefinition, union.class assert_equal 'BagOfThings', union.name assert_equal 3, union.types.length assert_equal [1, 1], union.position assert_equal GraphQL::Language::Nodes::TypeName, union.types[0].class assert_equal 'A', union.types[0].name assert_equal [2, 1], union.types[0].position assert_equal GraphQL::Language::Nodes::TypeName, union.types[1].class assert_equal 'B', union.types[1].name assert_equal [3, 1], union.types[1].position assert_equal GraphQL::Language::Nodes::TypeName, union.types[2].class assert_equal 'C', union.types[2].name assert_equal [4, 1], union.types[2].position end def test_it_parses_input_types document = parse(' input EmptyMutationInput { clientMutationId: String } ') type = document.definitions.first assert_equal GraphQL::Language::Nodes::InputObjectTypeDefinition, type.class assert_equal 'EmptyMutationInput', type.name assert_equal ['clientMutationId'], type.fields.map(&:name) assert_equal 'String', type.fields[0].type.name assert_equal nil, type.fields[0].default_value end def test_it_parses_directives document = parse(' directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT ') type = document.definitions.first assert_equal GraphQL::Language::Nodes::DirectiveDefinition, type.class assert_equal 'include', type.name assert_equal 1, type.arguments.length assert_equal 'if', type.arguments[0].name assert_equal 'Boolean', type.arguments[0].type.of_type.name assert_equal 3, type.locations.length assert_instance_of GraphQL::Language::Nodes::DirectiveLocation, type.locations[0] assert_equal 'FIELD', type.locations[0].name assert_equal [3, 20], type.locations[0].position assert_instance_of GraphQL::Language::Nodes::DirectiveLocation, type.locations[1] assert_equal 'FRAGMENT_SPREAD', type.locations[1].name assert_equal [4, 19], type.locations[1].position assert_instance_of GraphQL::Language::Nodes::DirectiveLocation, type.locations[2] assert_equal 'INLINE_FRAGMENT', type.locations[2].name assert_equal [5, 19], type.locations[2].position end def test_it_parses_field_arguments document = parse(' type Mutation { post( id: ID! @deprecated(reason: "Not used"), # This is what goes in the post data: String ): Post } ') field = document.definitions.first.fields.first assert_equal ['id', 'data'], field.arguments.map(&:name) id_arg = field.arguments[0] deprecated_directive = id_arg.directives[0] assert_equal 'deprecated', deprecated_directive.name assert_equal 'reason', deprecated_directive.arguments[0].name assert_equal 'Not used', deprecated_directive.arguments[0].value data_arg = field.arguments[1] assert_equal "data", data_arg.name assert_equal "This is what goes in the post", data_arg.description end def test_it_parses_schema_definition document = parse(' schema { query: QueryRoot mutation: MutationRoot subscription: SubscriptionRoot } ') schema = document.definitions.first assert_equal 'QueryRoot', schema.query assert_equal 'MutationRoot', schema.mutation assert_equal 'SubscriptionRoot', schema.subscription end def test_it_parses_schema_extensions document = parse(' extend schema { query: QueryRoot mutation: MutationRoot subscription: SubscriptionRoot } ') schema_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::SchemaExtension, schema_extension.class assert_equal [2, 15], schema_extension.position assert_equal 'QueryRoot', schema_extension.query assert_equal 'MutationRoot', schema_extension.mutation assert_equal 'SubscriptionRoot', schema_extension.subscription end def test_it_parses_schema_extensions_with_directives document = parse(' extend schema @something { query: QueryRoot } ') schema_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::SchemaExtension, schema_extension.class assert_equal 1, schema_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, schema_extension.directives.first.class assert_equal 'something', schema_extension.directives.first.name assert_equal 'QueryRoot', schema_extension.query assert_equal nil, schema_extension.mutation assert_equal nil, schema_extension.subscription end def test_it_parses_schema_extensions_with_only_directives document = parse(' extend schema @something ') schema_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::SchemaExtension, schema_extension.class assert_equal 1, schema_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, schema_extension.directives.first.class assert_equal 'something', schema_extension.directives.first.name assert_equal nil, schema_extension.query assert_equal nil, schema_extension.mutation assert_equal nil, schema_extension.subscription end def test_it_parses_scalar_extensions document = parse(' extend scalar Date @something @somethingElse ') scalar_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ScalarTypeExtension, scalar_extension.class assert_equal 'Date', scalar_extension.name assert_equal [2, 15], scalar_extension.position assert_equal 2, scalar_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, scalar_extension.directives.first.class assert_equal 'something', scalar_extension.directives.first.name assert_equal GraphQL::Language::Nodes::Directive, scalar_extension.directives.last.class assert_equal 'somethingElse', scalar_extension.directives.last.name end def test_it_parses_object_type_extensions_with_field_definitions document = parse(' extend type User { login: String! } ') object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeExtension, object_type_extension.class assert_equal 'User', object_type_extension.name assert_equal [2, 15], object_type_extension.position assert_equal 1, object_type_extension.fields.length assert_equal GraphQL::Language::Nodes::FieldDefinition, object_type_extension.fields.first.class end def test_it_parses_object_type_extensions_with_field_definitions_and_directives document = parse(' extend type User @deprecated { login: String! } ') object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeExtension, object_type_extension.class assert_equal 'User', object_type_extension.name assert_equal [2, 15], object_type_extension.position assert_equal 1, object_type_extension.fields.length assert_equal GraphQL::Language::Nodes::FieldDefinition, object_type_extension.fields.first.class assert_equal 1, object_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, object_type_extension.directives.first.class end def test_it_parses_object_type_extensions_with_field_definitions_and_implements document = parse(' extend type User implements Node { login: String! } ') object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeExtension, object_type_extension.class assert_equal 'User', object_type_extension.name assert_equal [2, 15], object_type_extension.position assert_equal 1, object_type_extension.fields.length assert_equal GraphQL::Language::Nodes::FieldDefinition, object_type_extension.fields.first.class assert_equal 1, object_type_extension.interfaces.length assert_equal GraphQL::Language::Nodes::TypeName, object_type_extension.interfaces.first.class end def test_it_parses_object_type_extensions_with_only_directives document = parse(' extend type User @deprecated ') object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeExtension, object_type_extension.class assert_equal 'User', object_type_extension.name assert_equal [2, 15], object_type_extension.position assert_equal 1, object_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, object_type_extension.directives.first.class assert_equal 'deprecated', object_type_extension.directives.first.name end def test_it_parses_object_type_extensions_with_implements_and_directives document = parse(' extend type User implements Node @deprecated ') object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeExtension, object_type_extension.class assert_equal 'User', object_type_extension.name assert_equal [2, 15], object_type_extension.position assert_equal 1, object_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, object_type_extension.directives.first.class assert_equal 'deprecated', object_type_extension.directives.first.name assert_equal 1, object_type_extension.interfaces.length assert_equal GraphQL::Language::Nodes::TypeName, object_type_extension.interfaces.first.class assert_equal 'Node', object_type_extension.interfaces.first.name end def test_it_parses_object_type_extensions_with_only_implements document = parse(' extend type User implements Node ') object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeExtension, object_type_extension.class assert_equal 'User', object_type_extension.name assert_equal [2, 15], object_type_extension.position assert_equal 1, object_type_extension.interfaces.length assert_equal GraphQL::Language::Nodes::TypeName, object_type_extension.interfaces.first.class assert_equal 'Node', object_type_extension.interfaces.first.name end def test_it_parses_interface_type_extensions_with_directives_and_fields document = parse(' extend interface Node @directive { field: String } ') interface_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::InterfaceTypeExtension, interface_type_extension.class assert_equal 'Node', interface_type_extension.name assert_equal [2, 15], interface_type_extension.position assert_equal 1, interface_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, interface_type_extension.directives.first.class assert_equal 'directive', interface_type_extension.directives.first.name assert_equal 1, interface_type_extension.fields.length assert_equal GraphQL::Language::Nodes::FieldDefinition, interface_type_extension.fields.first.class assert_equal 'field', interface_type_extension.fields.first.name end def test_it_parses_interface_type_extensions_with_fields document = parse(' extend interface Node { field: String } ') interface_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::InterfaceTypeExtension, interface_type_extension.class assert_equal 'Node', interface_type_extension.name assert_equal [2, 15], interface_type_extension.position assert_equal 0, interface_type_extension.directives.length assert_equal 1, interface_type_extension.fields.length assert_equal GraphQL::Language::Nodes::FieldDefinition, interface_type_extension.fields.first.class assert_equal 'field', interface_type_extension.fields.first.name end def test_it_parses_interface_type_extensions_with_directives document = parse(' extend interface Node @directive ') interface_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::InterfaceTypeExtension, interface_type_extension.class assert_equal 'Node', interface_type_extension.name assert_equal [2, 15], interface_type_extension.position assert_equal 1, interface_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, interface_type_extension.directives.first.class assert_equal 'directive', interface_type_extension.directives.first.name end def test_it_parses_union_type_extension_with_union_members document = parse(' extend union BagOfThings = A | B ') union_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::UnionTypeExtension, union_type_extension.class assert_equal 'BagOfThings', union_type_extension.name assert_equal [2, 15], union_type_extension.position assert_equal 0, union_type_extension.directives.length assert_equal 2, union_type_extension.types.length assert_equal GraphQL::Language::Nodes::TypeName, union_type_extension.types.first.class assert_equal 'A', union_type_extension.types.first.name end def test_it_parses_union_type_extension_with_directives_and_union_members document = parse(' extend union BagOfThings @directive = A | B ') union_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::UnionTypeExtension, union_type_extension.class assert_equal 'BagOfThings', union_type_extension.name assert_equal [2, 15], union_type_extension.position assert_equal 1, union_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, union_type_extension.directives.first.class assert_equal 'directive', union_type_extension.directives.first.name assert_equal 2, union_type_extension.types.length assert_equal GraphQL::Language::Nodes::TypeName, union_type_extension.types.first.class assert_equal 'A', union_type_extension.types.first.name end def test_it_parses_union_type_extension_with_directives document = parse(' extend union BagOfThings @directive ') union_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::UnionTypeExtension, union_type_extension.class assert_equal 'BagOfThings', union_type_extension.name assert_equal [2, 15], union_type_extension.position assert_equal 1, union_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, union_type_extension.directives.first.class assert_equal 'directive', union_type_extension.directives.first.name assert_equal 0, union_type_extension.types.length end def test_it_parses_enum_type_extension_with_values document = parse(' extend enum Status { DRAFT PUBLISHED } ') enum_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::EnumTypeExtension, enum_type_extension.class assert_equal 'Status', enum_type_extension.name assert_equal [2, 15], enum_type_extension.position assert_equal 0, enum_type_extension.directives.length assert_equal 2, enum_type_extension.values.length assert_equal GraphQL::Language::Nodes::EnumValueDefinition, enum_type_extension.values.first.class assert_equal 'DRAFT', enum_type_extension.values.first.name end def test_it_parses_enum_type_extension_with_directives_and_values document = parse(' extend enum Status @directive { DRAFT PUBLISHED } ') enum_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::EnumTypeExtension, enum_type_extension.class assert_equal 'Status', enum_type_extension.name assert_equal [2, 15], enum_type_extension.position assert_equal 1, enum_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, enum_type_extension.directives.first.class assert_equal 'directive', enum_type_extension.directives.first.name assert_equal 2, enum_type_extension.values.length assert_equal GraphQL::Language::Nodes::EnumValueDefinition, enum_type_extension.values.first.class assert_equal 'DRAFT', enum_type_extension.values.first.name end def test_it_parses_enum_type_extension_with_directives document = parse(' extend enum Status @directive ') enum_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::EnumTypeExtension, enum_type_extension.class assert_equal 'Status', enum_type_extension.name assert_equal [2, 15], enum_type_extension.position assert_equal 1, enum_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, enum_type_extension.directives.first.class assert_equal 'directive', enum_type_extension.directives.first.name assert_equal 0, enum_type_extension.values.length end def test_it_parses_input_object_type_extension_with_fields document = parse(' extend input UserInput { login: String! } ') input_object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::InputObjectTypeExtension, input_object_type_extension.class assert_equal 'UserInput', input_object_type_extension.name assert_equal [2, 15], input_object_type_extension.position assert_equal 1, input_object_type_extension.fields.length assert_equal GraphQL::Language::Nodes::InputValueDefinition, input_object_type_extension.fields.first.class assert_equal 'login', input_object_type_extension.fields.first.name assert_equal 0, input_object_type_extension.directives.length end def test_it_parses_input_object_type_extension_with_directives_and_fields document = parse(' extend input UserInput @deprecated { login: String! } ') input_object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::InputObjectTypeExtension, input_object_type_extension.class assert_equal 'UserInput', input_object_type_extension.name assert_equal [2, 15], input_object_type_extension.position assert_equal 1, input_object_type_extension.fields.length assert_equal GraphQL::Language::Nodes::InputValueDefinition, input_object_type_extension.fields.first.class assert_equal 'login', input_object_type_extension.fields.first.name assert_equal 1, input_object_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, input_object_type_extension.directives.first.class assert_equal 'deprecated', input_object_type_extension.directives.first.name end def test_it_parses_input_object_type_extension_with_directives document = parse(' extend input UserInput @deprecated ') input_object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::InputObjectTypeExtension, input_object_type_extension.class assert_equal 'UserInput', input_object_type_extension.name assert_equal [2, 15], input_object_type_extension.position assert_equal 0, input_object_type_extension.fields.length assert_equal 1, input_object_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, input_object_type_extension.directives.first.class assert_equal 'deprecated', input_object_type_extension.directives.first.name end def test_it_parses_whole_definition_with_descriptions document = parse(SCHEMA_DEFINITION_STRING) assert_equal 6, document.definitions.size schema_definition, directive_definition, enum_type_definition, object_type_definition, input_object_type_definition, interface_type_definition = document.definitions assert_equal GraphQL::Language::Nodes::SchemaDefinition, schema_definition.class assert_equal GraphQL::Language::Nodes::DirectiveDefinition, directive_definition.class assert_equal 'This is a directive', directive_definition.description assert_equal GraphQL::Language::Nodes::EnumTypeDefinition, enum_type_definition.class assert_equal "Multiline comment\n\nWith an enum", enum_type_definition.description assert_nil enum_type_definition.values[0].description assert_equal 'Not a creative color', enum_type_definition.values[1].description assert_equal GraphQL::Language::Nodes::ObjectTypeDefinition, object_type_definition.class assert_equal 'Comment without preceding space', object_type_definition.description assert_equal 'And a field to boot', object_type_definition.fields[0].description assert_equal GraphQL::Language::Nodes::InputObjectTypeDefinition, input_object_type_definition.class assert_equal 'Comment for input object types', input_object_type_definition.description assert_equal 'Color of the car', input_object_type_definition.fields[0].description assert_equal GraphQL::Language::Nodes::InterfaceTypeDefinition, interface_type_definition.class assert_equal 'Comment for interface definitions', interface_type_definition.description assert_equal 'Amount of wheels', interface_type_definition.fields[0].description brand_field = interface_type_definition.fields[1] assert_equal 1, brand_field.arguments.length assert_equal 'argument', brand_field.arguments[0].name assert_instance_of GraphQL::Language::Nodes::NullValue, brand_field.arguments[0].default_value end end end SCHEMA_DEFINITION_STRING = %| # Schema at beginning of file schema { query: Hello } # Comment between two definitions are omitted # This is a directive directive @foo( # It has an argument arg: Int ) on FIELD # Multiline comment # # With an enum enum Color { RED # Not a creative color GREEN BLUE } #Comment without preceding space type Hello { # And a field to boot str: String } # Comment for input object types input Car { # Color of the car color: String! } # Comment for interface definitions interface Vehicle { # Amount of wheels wheels: Int! brand(argument: String = null): String! } # Comment at the end of schema | end end end graphql-ruby-1.11.10/lib/graphql/define.rb000066400000000000000000000023771414121453000202570ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/define/assign_argument" require "graphql/define/assign_connection" require "graphql/define/assign_enum_value" require "graphql/define/assign_global_id_field" require "graphql/define/assign_mutation_function" require "graphql/define/assign_object_field" require "graphql/define/defined_object_proxy" require "graphql/define/instance_definable" require "graphql/define/no_definition_error" require "graphql/define/non_null_with_bang" require "graphql/define/type_definer" module GraphQL module Define # A helper for definitions that store their value in `#metadata`. # # @example Storing application classes with GraphQL types # # Make a custom definition # GraphQL::ObjectType.accepts_definitions(resolves_to_class_names: GraphQL::Define.assign_metadata_key(:resolves_to_class_names)) # # # After definition, read the key from metadata # PostType.metadata[:resolves_to_class_names] # => [...] # # @param key [Object] the key to assign in metadata # @return [#call(defn, value)] an assignment for `.accepts_definitions` which writes `key` to `#metadata` def self.assign_metadata_key(key) GraphQL::Define::InstanceDefinable::AssignMetadataKey.new(key) end end end graphql-ruby-1.11.10/lib/graphql/define/000077500000000000000000000000001414121453000177215ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/define/assign_argument.rb000066400000000000000000000005321414121453000234340ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define # Turn argument configs into a {GraphQL::Argument}. module AssignArgument def self.call(target, *args, **kwargs, &block) argument = GraphQL::Argument.from_dsl(*args, **kwargs, &block) target.arguments[argument.name] = argument end end end end graphql-ruby-1.11.10/lib/graphql/define/assign_connection.rb000066400000000000000000000007741414121453000237610ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define module AssignConnection def self.call(type_defn, *field_args, max_page_size: nil, **field_kwargs, &field_block) underlying_field = GraphQL::Define::AssignObjectField.call(type_defn, *field_args, **field_kwargs, &field_block) underlying_field.connection_max_page_size = max_page_size underlying_field.connection = true type_defn.fields[underlying_field.name] = underlying_field end end end end graphql-ruby-1.11.10/lib/graphql/define/assign_enum_value.rb000066400000000000000000000007501414121453000237540ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define # @api deprecated module AssignEnumValue def self.call(enum_type, name, desc = nil, deprecation_reason: nil, value: name, &block) enum_value = GraphQL::EnumType::EnumValue.define( name: name.to_s, description: desc, deprecation_reason: deprecation_reason, value: value, &block ) enum_type.add_value(enum_value) end end end end graphql-ruby-1.11.10/lib/graphql/define/assign_global_id_field.rb000066400000000000000000000006141414121453000246720ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define module AssignGlobalIdField def self.call(type_defn, field_name, **field_kwargs) resolve = GraphQL::Relay::GlobalIdResolve.new(type: type_defn) GraphQL::Define::AssignObjectField.call(type_defn, field_name, **field_kwargs, type: GraphQL::ID_TYPE.to_non_null_type, resolve: resolve) end end end end graphql-ruby-1.11.10/lib/graphql/define/assign_mutation_function.rb000066400000000000000000000021071414121453000253570ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define module AssignMutationFunction def self.call(target, function) # TODO: get all this logic somewhere easier to test if !function.type.is_a?(GraphQL::ObjectType) raise "Mutation functions must return object types (not #{function.type.unwrap})" end target.return_type = function.type.redefine { name(target.name + "Payload") field :clientMutationId, types.String, "A unique identifier for the client performing the mutation.", property: :client_mutation_id } target.arguments = function.arguments target.description = function.description target.resolve = ->(o, a, c) { res = function.call(o, a, c) ResultProxy.new(res, a[:clientMutationId]) } end class ResultProxy < SimpleDelegator attr_reader :client_mutation_id def initialize(target, client_mutation_id) @client_mutation_id = client_mutation_id super(target) end end end end end graphql-ruby-1.11.10/lib/graphql/define/assign_object_field.rb000066400000000000000000000023461414121453000242300ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define # @api deprecated module AssignObjectField def self.call(owner_type, name, type_or_field = nil, desc = nil, function: nil, field: nil, relay_mutation_function: nil, **kwargs, &block) name_s = name.to_s # Move some positional args into keywords if they're present desc && kwargs[:description] ||= desc name && kwargs[:name] ||= name_s if !type_or_field.nil? && !type_or_field.is_a?(GraphQL::Field) # Maybe a string, proc or BaseType kwargs[:type] = type_or_field end base_field = if type_or_field.is_a?(GraphQL::Field) type_or_field.redefine(name: name_s) elsif function func_field = GraphQL::Function.build_field(function) func_field.name = name_s func_field elsif field.is_a?(GraphQL::Field) field.redefine(name: name_s) else nil end obj_field = if base_field base_field.redefine(**kwargs, &block) else GraphQL::Field.define(**kwargs, &block) end # Attach the field to the type owner_type.fields[name_s] = obj_field end end end end graphql-ruby-1.11.10/lib/graphql/define/defined_object_proxy.rb000066400000000000000000000032501414121453000244330ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define # This object delegates most methods to a dictionary of functions, {@dictionary}. # {@target} is passed to the specified function, along with any arguments and block. # This allows a method-based DSL without adding methods to the defined class. class DefinedObjectProxy extend GraphQL::Ruby2Keywords # The object which will be defined by definition functions attr_reader :target def initialize(target) @target = target @dictionary = target.class.dictionary end # Provides shorthand access to GraphQL's built-in types def types GraphQL::Define::TypeDefiner.instance end # Allow `plugin` to perform complex initialization on the definition. # Calls `plugin.use(defn, **kwargs)`. # @param plugin [<#use(defn, **kwargs)>] A plugin object # @param kwargs [Hash] Any options for the plugin def use(plugin, **kwargs) # https://bugs.ruby-lang.org/issues/10708 if kwargs == {} plugin.use(self) else plugin.use(self, **kwargs) end end # Lookup a function from the dictionary and call it if it's found. def method_missing(name, *args, &block) definition = @dictionary[name] if definition definition.call(@target, *args, &block) else msg = "#{@target.class.name} can't define '#{name}'" raise NoDefinitionError, msg, caller end end ruby2_keywords :method_missing def respond_to_missing?(name, include_private = false) @dictionary[name] || super end end end end graphql-ruby-1.11.10/lib/graphql/define/instance_definable.rb000066400000000000000000000147751414121453000240610ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define # @api deprecated module InstanceDefinable def self.included(base) base.extend(ClassMethods) base.ensure_defined(:metadata) end # @api deprecated def metadata @metadata ||= {} end # @api deprecated def define(**kwargs, &block) # make sure the previous definition_proc was executed: ensure_defined stash_dependent_methods @pending_definition = Definition.new(kwargs, block) nil end # @api deprecated def redefine(**kwargs, &block) ensure_defined new_inst = self.dup new_inst.define(**kwargs, &block) new_inst end def initialize_copy(other) super @metadata = other.metadata.dup end private # Run the definition block if it hasn't been run yet. # This can only be run once: the block is deleted after it's used. # You have to call this before using any value which could # come from the definition block. # @return [void] def ensure_defined if @pending_definition defn = @pending_definition @pending_definition = nil revive_dependent_methods begin defn_proxy = DefinedObjectProxy.new(self) # Apply definition from `define(...)` kwargs defn.define_keywords.each do |keyword, value| # Don't splat string hashes, which blows up on Rubies before 2.7 if value.is_a?(Hash) && value.each_key.all? { |k| k.is_a?(Symbol) } defn_proxy.public_send(keyword, **value) else defn_proxy.public_send(keyword, value) end end # and/or apply definition from `define { ... }` block if defn.define_proc defn_proxy.instance_eval(&defn.define_proc) end rescue StandardError # The definition block failed to run, so make this object pending again: stash_dependent_methods @pending_definition = defn raise end end nil end # Take the pending methods and put them back on this object's singleton class. # This reverts the process done by {#stash_dependent_methods} # @return [void] def revive_dependent_methods pending_methods = @pending_methods self.singleton_class.class_eval { pending_methods.each do |method| undef_method(method.name) if method_defined?(method.name) define_method(method.name, method) end } @pending_methods = nil end # Find the method names which were declared as definition-dependent, # then grab the method definitions off of this object's class # and store them for later. # # Then make a dummy method for each of those method names which: # # - Triggers the pending definition, if there is one # - Calls the same method again. # # It's assumed that {#ensure_defined} will put the original method definitions # back in place with {#revive_dependent_methods}. # @return [void] def stash_dependent_methods method_names = self.class.ensure_defined_method_names @pending_methods = method_names.map { |n| self.class.instance_method(n) } self.singleton_class.class_eval do method_names.each do |method_name| undef_method(method_name) if method_defined?(method_name) define_method(method_name) { |*args, &block| ensure_defined self.send(method_name, *args, &block) } end end end class Definition attr_reader :define_keywords, :define_proc def initialize(define_keywords, define_proc) @define_keywords = define_keywords @define_proc = define_proc end end module ClassMethods # Create a new instance # and prepare a definition using its {.definitions}. # @param kwargs [Hash] Key-value pairs corresponding to defininitions from `accepts_definitions` # @param block [Proc] Block which calls helper methods from `accepts_definitions` def define(**kwargs, &block) instance = self.new instance.define(**kwargs, &block) instance end # Attach definitions to this class. # Each symbol in `accepts` will be assigned with `{key}=`. # The last entry in accepts may be a hash of name-proc pairs for custom definitions. def accepts_definitions(*accepts) new_assignments = if accepts.last.is_a?(Hash) accepts.pop.dup else {} end accepts.each do |key| new_assignments[key] = AssignAttribute.new(key) end @own_dictionary = own_dictionary.merge(new_assignments) end def ensure_defined(*method_names) @ensure_defined_method_names ||= [] @ensure_defined_method_names.concat(method_names) nil end def ensure_defined_method_names own_method_names = @ensure_defined_method_names || [] if superclass.respond_to?(:ensure_defined_method_names) superclass.ensure_defined_method_names + own_method_names else own_method_names end end # @return [Hash] combined definitions for self and ancestors def dictionary if superclass.respond_to?(:dictionary) own_dictionary.merge(superclass.dictionary) else own_dictionary end end # @return [Hash] definitions for this class only def own_dictionary @own_dictionary ||= {} end end class AssignMetadataKey def initialize(key) @key = key end def call(defn, value = true) defn.metadata[@key] = value end end class AssignAttribute extend GraphQL::Ruby2Keywords def initialize(attr_name) @attr_assign_method = :"#{attr_name}=" end # Even though we're just using the first value here, # We have to add a splat here to use `ruby2_keywords`, # so that it will accept a `[{}]` input from the caller. def call(defn, *value) defn.public_send(@attr_assign_method, value.first) end ruby2_keywords :call end end end end graphql-ruby-1.11.10/lib/graphql/define/no_definition_error.rb000066400000000000000000000001741414121453000243050ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define class NoDefinitionError < GraphQL::Error end end end graphql-ruby-1.11.10/lib/graphql/define/non_null_with_bang.rb000066400000000000000000000006071414121453000241170ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define # Wrap the object in NonNullType in response to `!` # @example required Int type # !GraphQL::INT_TYPE # module NonNullWithBang # Make the type non-null # @return [GraphQL::NonNullType] a non-null type which wraps the original type def ! to_non_null_type end end end end graphql-ruby-1.11.10/lib/graphql/define/type_definer.rb000066400000000000000000000017501414121453000227260ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Define # Some conveniences for definining return & argument types. # # Passed into initialization blocks, eg {ObjectType#initialize}, {Field#initialize} class TypeDefiner include Singleton # rubocop:disable Naming/MethodName def Int; GraphQL::INT_TYPE; end def String; GraphQL::STRING_TYPE; end def Float; GraphQL::FLOAT_TYPE; end def Boolean; GraphQL::BOOLEAN_TYPE; end def ID; GraphQL::ID_TYPE; end # rubocop:enable Naming/MethodName # Make a {ListType} which wraps the input type # # @example making a list type # list_of_strings = types[types.String] # list_of_strings.inspect # # => "[String]" # # @param type [Type] A type to be wrapped in a ListType # @return [GraphQL::ListType] A ListType wrapping `type` def [](type) type.to_list_type end end end end graphql-ruby-1.11.10/lib/graphql/deprecated_dsl.rb000066400000000000000000000020351414121453000217560ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # There are two ways to apply the deprecated `!` DSL to class-style schema definitions: # # 1. Scoped by file (CRuby only), add to the top of the file: # # using GraphQL::DeprecatedDSL # # (This is a "refinement", there are also other ways to scope it.) # # 2. Global application, add before schema definition: # # GraphQL::DeprecatedDSL.activate # module DeprecatedDSL TYPE_CLASSES = [ GraphQL::Schema::Scalar, GraphQL::Schema::Enum, GraphQL::Schema::InputObject, GraphQL::Schema::Union, GraphQL::Schema::Interface, GraphQL::Schema::Object, ] def self.activate TYPE_CLASSES.each { |c| c.extend(Methods) } GraphQL::Schema::List.include(Methods) GraphQL::Schema::NonNull.include(Methods) end module Methods def ! to_non_null_type end end TYPE_CLASSES.each do |type_class| refine type_class.singleton_class do include Methods end end end end graphql-ruby-1.11.10/lib/graphql/dig.rb000066400000000000000000000011151414121453000175550ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Dig # implemented using the old activesupport #dig instead of the ruby built-in # so we can use some of the magic in Schema::InputObject and Query::Arguments # to handle stringified/symbolized keys. # # @param args [Array<[String, Symbol>] Retrieves the value object corresponding to the each key objects repeatedly # @return [Object] def dig(own_key, *rest_keys) val = self[own_key] if val.nil? || rest_keys.empty? val else val.dig(*rest_keys) end end end end graphql-ruby-1.11.10/lib/graphql/directive.rb000066400000000000000000000101301414121453000207650ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Directives are server-defined hooks for modifying execution. # # Two directives are included out-of-the-box: # - `@skip(if: ...)` Skips the tagged field if the value of `if` is true # - `@include(if: ...)` Includes the tagged field _only_ if `if` is true # class Directive include GraphQL::Define::InstanceDefinable accepts_definitions :locations, :name, :description, :arguments, :default_directive, argument: GraphQL::Define::AssignArgument attr_accessor :locations, :arguments, :name, :description, :arguments_class attr_accessor :ast_node # @api private attr_writer :default_directive ensure_defined(:locations, :arguments, :graphql_name, :name, :description, :default_directive?) # Future-compatible alias # @see {GraphQL::SchemaMember} alias :graphql_name :name # Future-compatible alias # @see {GraphQL::SchemaMember} alias :graphql_definition :itself LOCATIONS = [ QUERY = :QUERY, MUTATION = :MUTATION, SUBSCRIPTION = :SUBSCRIPTION, FIELD = :FIELD, FRAGMENT_DEFINITION = :FRAGMENT_DEFINITION, FRAGMENT_SPREAD = :FRAGMENT_SPREAD, INLINE_FRAGMENT = :INLINE_FRAGMENT, SCHEMA = :SCHEMA, SCALAR = :SCALAR, OBJECT = :OBJECT, FIELD_DEFINITION = :FIELD_DEFINITION, ARGUMENT_DEFINITION = :ARGUMENT_DEFINITION, INTERFACE = :INTERFACE, UNION = :UNION, ENUM = :ENUM, ENUM_VALUE = :ENUM_VALUE, INPUT_OBJECT = :INPUT_OBJECT, INPUT_FIELD_DEFINITION = :INPUT_FIELD_DEFINITION, ] LOCATION_DESCRIPTIONS = { QUERY: 'Location adjacent to a query operation.', MUTATION: 'Location adjacent to a mutation operation.', SUBSCRIPTION: 'Location adjacent to a subscription operation.', FIELD: 'Location adjacent to a field.', FRAGMENT_DEFINITION: 'Location adjacent to a fragment definition.', FRAGMENT_SPREAD: 'Location adjacent to a fragment spread.', INLINE_FRAGMENT: 'Location adjacent to an inline fragment.', SCHEMA: 'Location adjacent to a schema definition.', SCALAR: 'Location adjacent to a scalar definition.', OBJECT: 'Location adjacent to an object type definition.', FIELD_DEFINITION: 'Location adjacent to a field definition.', ARGUMENT_DEFINITION: 'Location adjacent to an argument definition.', INTERFACE: 'Location adjacent to an interface definition.', UNION: 'Location adjacent to a union definition.', ENUM: 'Location adjacent to an enum definition.', ENUM_VALUE: 'Location adjacent to an enum value definition.', INPUT_OBJECT: 'Location adjacent to an input object type definition.', INPUT_FIELD_DEFINITION: 'Location adjacent to an input object field definition.', } def initialize @arguments = {} @default_directive = false end def to_s "" end def on_field? locations.include?(FIELD) end def on_fragment? locations.include?(FRAGMENT_SPREAD) && locations.include?(INLINE_FRAGMENT) end def on_operation? locations.include?(QUERY) && locations.include?(MUTATION) && locations.include?(SUBSCRIPTION) end # @return [Boolean] Is this directive supplied by default? (eg `@skip`) def default_directive? @default_directive end def inspect "#" end def type_class metadata[:type_class] end def get_argument(argument_name) arguments[argument_name] end end end require "graphql/directive/include_directive" require "graphql/directive/skip_directive" require "graphql/directive/deprecated_directive" graphql-ruby-1.11.10/lib/graphql/directive/000077500000000000000000000000001414121453000204455ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/directive/deprecated_directive.rb000066400000000000000000000002021414121453000251220ustar00rootroot00000000000000# frozen_string_literal: true GraphQL::Directive::DeprecatedDirective = GraphQL::Schema::Directive::Deprecated.graphql_definition graphql-ruby-1.11.10/lib/graphql/directive/include_directive.rb000066400000000000000000000001741414121453000244550ustar00rootroot00000000000000# frozen_string_literal: true GraphQL::Directive::IncludeDirective = GraphQL::Schema::Directive::Include.graphql_definition graphql-ruby-1.11.10/lib/graphql/directive/skip_directive.rb000066400000000000000000000001661414121453000240010ustar00rootroot00000000000000# frozen_string_literal: true GraphQL::Directive::SkipDirective = GraphQL::Schema::Directive::Skip.graphql_definition graphql-ruby-1.11.10/lib/graphql/enum_type.rb000066400000000000000000000070511414121453000210240ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # @api deprecated class EnumType < GraphQL::BaseType accepts_definitions :values, value: GraphQL::Define::AssignEnumValue ensure_defined(:values, :validate_non_null_input, :coerce_non_null_input, :coerce_result) attr_accessor :ast_node def initialize super @values_by_name = {} end def initialize_copy(other) super self.values = other.values.values end # @param new_values [Array] The set of values contained in this type def values=(new_values) @values_by_name = {} new_values.each { |enum_value| add_value(enum_value) } end # @param enum_value [EnumValue] A value to add to this type's set of values def add_value(enum_value) if @values_by_name.key?(enum_value.name) raise "Enum value names must be unique. Value `#{enum_value.name}` already exists on Enum `#{name}`." end @values_by_name[enum_value.name] = enum_value end # @return [Hash EnumValue>] `{name => value}` pairs contained in this type def values @values_by_name end def kind GraphQL::TypeKinds::ENUM end def coerce_result(value, ctx = nil) if ctx.nil? warn_deprecated_coerce("coerce_isolated_result") ctx = GraphQL::Query::NullContext end warden = ctx.warden all_values = warden ? warden.enum_values(self) : @values_by_name.each_value enum_value = all_values.find { |val| val.value == value } if enum_value enum_value.name else raise(UnresolvedValueError, "Can't resolve enum #{name} for #{value.inspect}") end end def to_s name end # A value within an {EnumType} # # Created with the `value` helper class EnumValue include GraphQL::Define::InstanceDefinable ATTRIBUTES = [:name, :description, :deprecation_reason, :value] accepts_definitions(*ATTRIBUTES) attr_accessor(*ATTRIBUTES) attr_accessor :ast_node ensure_defined(*ATTRIBUTES) undef name= def name=(new_name) # Validate that the name is correct GraphQL::NameValidator.validate!(new_name) @name = new_name end def graphql_name name end def type_class metadata[:type_class] end end class UnresolvedValueError < GraphQL::Error end private # Get the underlying value for this enum value # # @example get episode value from Enum # episode = EpisodeEnum.coerce("NEWHOPE") # episode # => 6 # # @param value_name [String] the string representation of this enum value # @return [Object] the underlying value for this enum value def coerce_non_null_input(value_name, ctx) if @values_by_name.key?(value_name) @values_by_name.fetch(value_name).value elsif match_by_value = @values_by_name.find { |k, v| v.value == value_name } # this is for matching default values, which are "inputs", but they're # the Ruby value, not the GraphQL string. match_by_value[1].value else nil end end def validate_non_null_input(value_name, ctx) result = GraphQL::Query::InputValidationResult.new allowed_values = ctx.warden.enum_values(self) matching_value = allowed_values.find { |v| v.name == value_name } if matching_value.nil? result.add_problem("Expected #{GraphQL::Language.serialize(value_name)} to be one of: #{allowed_values.map(&:name).join(', ')}") end result end end end graphql-ruby-1.11.10/lib/graphql/execution.rb000066400000000000000000000006341414121453000210220ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/execution/directive_checks" require "graphql/execution/execute" require "graphql/execution/flatten" require "graphql/execution/instrumentation" require "graphql/execution/interpreter" require "graphql/execution/lazy" require "graphql/execution/lookahead" require "graphql/execution/multiplex" require "graphql/execution/typecast" require "graphql/execution/errors" graphql-ruby-1.11.10/lib/graphql/execution/000077500000000000000000000000001414121453000204725ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/execution/directive_checks.rb000066400000000000000000000020341414121453000243140ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution # Boolean checks for how an AST node's directives should # influence its execution # @api private module DirectiveChecks SKIP = "skip" INCLUDE = "include" module_function # @return [Boolean] Should this node be included in the query? def include?(directive_ast_nodes, query) directive_ast_nodes.each do |directive_ast_node| name = directive_ast_node.name directive_defn = query.schema.directives[name] case name when SKIP args = query.arguments_for(directive_ast_node, directive_defn) if args[:if] == true return false end when INCLUDE args = query.arguments_for(directive_ast_node, directive_defn) if args[:if] == false return false end else # Undefined directive, or one we don't care about end end true end end end end graphql-ruby-1.11.10/lib/graphql/execution/errors.rb000066400000000000000000000034641414121453000223420ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution # A plugin that wraps query execution with error handling. # Supports class-based schemas and the new {Interpreter} runtime only. # # @example Handling ActiveRecord::NotFound # # class MySchema < GraphQL::Schema # use GraphQL::Execution::Errors # # rescue_from(ActiveRecord::NotFound) do |err, obj, args, ctx, field| # ErrorTracker.log("Not Found: #{err.message}") # nil # end # end # class Errors def self.use(schema) schema.error_handler = self.new(schema) end def initialize(schema) @schema = schema end class NullErrorHandler def self.with_error_handling(_ctx) yield end end # Call the given block with the schema's configured error handlers. # # If the block returns a lazy value, it's not wrapped with error handling. That area will have to be wrapped itself. # # @param ctx [GraphQL::Query::Context] # @return [Object] Either the result of the given block, or some object to replace the result, in case of error handling. def with_error_handling(ctx) yield rescue StandardError => err rescues = ctx.schema.rescues _err_class, handler = rescues.find { |err_class, handler| err.is_a?(err_class) } if handler runtime_info = ctx.namespace(:interpreter) || {} obj = runtime_info[:current_object] args = runtime_info[:current_arguments] field = runtime_info[:current_field] if obj.is_a?(GraphQL::Schema::Object) obj = obj.object end handler.call(err, obj, args, ctx, field) else raise err end end end end end graphql-ruby-1.11.10/lib/graphql/execution/execute.rb000066400000000000000000000253251414121453000224700ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution # A valid execution strategy # @api private class Execute # @api private class Skip; end # Just a singleton for implementing {Query::Context#skip} # @api private SKIP = Skip.new # @api private class PropagateNull end # @api private PROPAGATE_NULL = PropagateNull.new def execute(ast_operation, root_type, query) result = resolve_root_selection(query) lazy_resolve_root_selection(result, **{query: query}) GraphQL::Execution::Flatten.call(query.context) end def self.begin_multiplex(_multiplex) end def self.begin_query(query, _multiplex) ExecutionFunctions.resolve_root_selection(query) end def self.finish_multiplex(results, multiplex) ExecutionFunctions.lazy_resolve_root_selection(results, multiplex: multiplex) end def self.finish_query(query, _multiplex) { "data" => Execution::Flatten.call(query.context) } end # @api private module ExecutionFunctions module_function def resolve_root_selection(query) query.trace("execute_query", query: query) do operation = query.selected_operation op_type = operation.operation_type root_type = query.root_type_for_operation(op_type) if query.context[:__root_unauthorized] # This was set by member/instrumentation.rb so that we wouldn't continue. else resolve_selection( query.root_value, root_type, query.context, mutation: query.mutation? ) end end end def lazy_resolve_root_selection(result, query: nil, multiplex: nil) if query.nil? && multiplex.queries.length == 1 query = multiplex.queries[0] end tracer = (query || multiplex) tracer.trace("execute_query_lazy", {multiplex: multiplex, query: query}) do GraphQL::Execution::Lazy.resolve(result) end end def resolve_selection(object, current_type, current_ctx, mutation: false ) # Assign this _before_ resolving the children # so that when a child propagates null, the selection result is # ready for it. current_ctx.value = {} selections_on_type = current_ctx.irep_node.typed_children[current_type] selections_on_type.each do |name, child_irep_node| field_ctx = current_ctx.spawn_child( key: name, object: object, irep_node: child_irep_node, ) field_result = resolve_field( object, field_ctx ) if field_result.is_a?(Skip) next end if mutation GraphQL::Execution::Lazy.resolve(field_ctx) end # If the last subselection caused a null to propagate to _this_ selection, # then we may as well quit executing fields because they # won't be in the response if current_ctx.invalid_null? break else current_ctx.value[name] = field_ctx end end current_ctx.value end def resolve_field(object, field_ctx) query = field_ctx.query irep_node = field_ctx.irep_node parent_type = irep_node.owner_type field = field_ctx.field raw_value = begin begin arguments = query.arguments_for(irep_node, field) field_ctx.trace("execute_field", { context: field_ctx }) do field_ctx.schema.middleware.invoke([parent_type, object, field, arguments, field_ctx]) end rescue GraphQL::UnauthorizedFieldError => err err.field ||= field field_ctx.schema.unauthorized_field(err) rescue GraphQL::UnauthorizedError => err field_ctx.schema.unauthorized_object(err) end rescue GraphQL::ExecutionError => err err end if field_ctx.schema.lazy?(raw_value) field_ctx.value = Execution::Lazy.new { inner_value = field_ctx.trace("execute_field_lazy", {context: field_ctx}) { begin begin field_ctx.field.lazy_resolve(raw_value, arguments, field_ctx) rescue GraphQL::UnauthorizedError => err field_ctx.schema.unauthorized_object(err) end rescue GraphQL::ExecutionError => err err end } continue_or_wait(inner_value, field_ctx.type, field_ctx) } else continue_or_wait(raw_value, field_ctx.type, field_ctx) end end # If the returned object is lazy (unfinished), # assign the lazy object to `.value=` so we can resolve it later. # When we resolve it later, reassign it to `.value=` so that # the finished value replaces the unfinished one. # # If the returned object is finished, continue to coerce # and resolve child fields def continue_or_wait(raw_value, field_type, field_ctx) if field_ctx.schema.lazy?(raw_value) field_ctx.value = Execution::Lazy.new { inner_value = begin begin field_ctx.schema.sync_lazy(raw_value) rescue GraphQL::UnauthorizedError => err field_ctx.schema.unauthorized_object(err) end rescue GraphQL::ExecutionError => err err end field_ctx.value = continue_or_wait(inner_value, field_type, field_ctx) } else field_ctx.value = continue_resolve_field(raw_value, field_type, field_ctx) end end def continue_resolve_field(raw_value, field_type, field_ctx) if field_ctx.parent.invalid_null? return nil end query = field_ctx.query case raw_value when GraphQL::ExecutionError raw_value.ast_node ||= field_ctx.ast_node raw_value.path = field_ctx.path query.context.errors.push(raw_value) when Array if field_type.non_null? # List type errors are handled above, this is for the case of fields returning an array of errors list_errors = raw_value.each_with_index.select { |value, _| value.is_a?(GraphQL::ExecutionError) } if list_errors.any? list_errors.each do |error, index| error.ast_node = field_ctx.ast_node error.path = field_ctx.path + (field_ctx.type.list? ? [index] : []) query.context.errors.push(error) end end end end resolve_value( raw_value, field_type, field_ctx, ) end def resolve_value(value, field_type, field_ctx) field_defn = field_ctx.field if value.nil? if field_type.kind.non_null? parent_type = field_ctx.irep_node.owner_type type_error = GraphQL::InvalidNullError.new(parent_type, field_defn, value) field_ctx.schema.type_error(type_error, field_ctx) PROPAGATE_NULL else nil end elsif value.is_a?(GraphQL::ExecutionError) if field_type.kind.non_null? PROPAGATE_NULL else nil end elsif value.is_a?(Array) && value.any? && value.all? {|v| v.is_a?(GraphQL::ExecutionError)} if field_type.kind.non_null? PROPAGATE_NULL else nil end elsif value.is_a?(Skip) field_ctx.value = value else case field_type.kind when GraphQL::TypeKinds::SCALAR, GraphQL::TypeKinds::ENUM field_type.coerce_result(value, field_ctx) when GraphQL::TypeKinds::LIST inner_type = field_type.of_type i = 0 result = [] field_ctx.value = result value.each do |inner_value| inner_ctx = field_ctx.spawn_child( key: i, object: inner_value, irep_node: field_ctx.irep_node, ) inner_result = continue_or_wait( inner_value, inner_type, inner_ctx, ) return PROPAGATE_NULL if inner_result == PROPAGATE_NULL result << inner_ctx i += 1 end result when GraphQL::TypeKinds::NON_NULL inner_type = field_type.of_type resolve_value( value, inner_type, field_ctx, ) when GraphQL::TypeKinds::OBJECT resolve_selection( value, field_type, field_ctx ) when GraphQL::TypeKinds::UNION, GraphQL::TypeKinds::INTERFACE query = field_ctx.query resolved_type_or_lazy = field_type.resolve_type(value, field_ctx) query.schema.after_lazy(resolved_type_or_lazy) do |resolved_type| possible_types = query.possible_types(field_type) if !possible_types.include?(resolved_type) parent_type = field_ctx.irep_node.owner_type type_error = GraphQL::UnresolvedTypeError.new(value, field_defn, parent_type, resolved_type, possible_types) field_ctx.schema.type_error(type_error, field_ctx) PROPAGATE_NULL else resolve_value( value, resolved_type, field_ctx, ) end end else raise("Unknown type kind: #{field_type.kind}") end end end end include ExecutionFunctions # A `.call`-able suitable to be the last step in a middleware chain module FieldResolveStep # Execute the field's resolve method def self.call(_parent_type, parent_object, field_definition, field_args, context, _next = nil) field_definition.resolve(parent_object, field_args, context) end end end end end graphql-ruby-1.11.10/lib/graphql/execution/flatten.rb000066400000000000000000000015351414121453000224600ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution # Starting from a root context, # create a hash out of the context tree. # @api private module Flatten def self.call(ctx) flatten(ctx) end class << self private def flatten(obj) case obj when Hash flattened = {} obj.each do |key, val| flattened[key] = flatten(val) end flattened when Array obj.map { |v| flatten(v) } when Query::Context::SharedMethods if obj.invalid_null? nil elsif obj.skipped? && obj.value.empty? nil else flatten(obj.value) end else obj end end end end end end graphql-ruby-1.11.10/lib/graphql/execution/instrumentation.rb000066400000000000000000000066741414121453000242770ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution module Instrumentation # This function implements the instrumentation policy: # # - Instrumenters are a stack; the first `before_query` will have the last `after_query` # - If a `before_` hook returned without an error, its corresponding `after_` hook will run. # - If the `before_` hook did _not_ run, the `after_` hook will not be called. # # When errors are raised from `after_` hooks: # - Subsequent `after_` hooks _are_ called # - The first raised error is captured; later errors are ignored # - If an error was capture, it's re-raised after all hooks are finished # # Partial runs of instrumentation are possible: # - If a `before_multiplex` hook raises an error, no `before_query` hooks will run # - If a `before_query` hook raises an error, subsequent `before_query` hooks will not run (on any query) def self.apply_instrumenters(multiplex) schema = multiplex.schema queries = multiplex.queries query_instrumenters = schema.instrumenters[:query] multiplex_instrumenters = schema.instrumenters[:multiplex] # First, run multiplex instrumentation, then query instrumentation for each query call_hooks(multiplex_instrumenters, multiplex, :before_multiplex, :after_multiplex) do each_query_call_hooks(query_instrumenters, queries) do # Let them be executed yield end end end class << self private # Call the before_ hooks of each query, # Then yield if no errors. # `call_hooks` takes care of appropriate cleanup. def each_query_call_hooks(instrumenters, queries, i = 0) if i >= queries.length yield else query = queries[i] call_hooks(instrumenters, query, :before_query, :after_query) { each_query_call_hooks(instrumenters, queries, i + 1) { yield } } end end # Call each before hook, and if they all succeed, yield. # If they don't all succeed, call after_ for each one that succeeded. def call_hooks(instrumenters, object, before_hook_name, after_hook_name) begin successful = [] instrumenters.each do |instrumenter| instrumenter.public_send(before_hook_name, object) successful << instrumenter end # if any before hooks raise an exception, quit calling before hooks, # but call the after hooks on anything that succeeded but also # raise the exception that came from the before hook. rescue GraphQL::ExecutionError => err object.context.errors << err rescue => e raise call_after_hooks(successful, object, after_hook_name, e) end begin yield # Call the user code ensure ex = call_after_hooks(successful, object, after_hook_name, nil) raise ex if ex end end def call_after_hooks(instrumenters, object, after_hook_name, ex) instrumenters.reverse_each do |instrumenter| begin instrumenter.public_send(after_hook_name, object) rescue => e ex = e end end ex end end end end end graphql-ruby-1.11.10/lib/graphql/execution/interpreter.rb000066400000000000000000000105731414121453000233700ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/execution/interpreter/argument_value" require "graphql/execution/interpreter/arguments" require "graphql/execution/interpreter/arguments_cache" require "graphql/execution/interpreter/execution_errors" require "graphql/execution/interpreter/hash_response" require "graphql/execution/interpreter/runtime" require "graphql/execution/interpreter/resolve" require "graphql/execution/interpreter/handles_raw_value" module GraphQL module Execution class Interpreter def initialize end # Support `Executor` :S def execute(_operation, _root_type, query) runtime = evaluate(query) sync_lazies(query: query) runtime.final_value end def self.use(schema_class) schema_class.interpreter = true schema_class.query_execution_strategy(GraphQL::Execution::Interpreter) schema_class.mutation_execution_strategy(GraphQL::Execution::Interpreter) schema_class.subscription_execution_strategy(GraphQL::Execution::Interpreter) schema_class.add_subscription_extension_if_necessary GraphQL::Schema::Object.include(HandlesRawValue) end def self.begin_multiplex(multiplex) # Since this is basically the batching context, # share it for a whole multiplex multiplex.context[:interpreter_instance] ||= self.new end def self.begin_query(query, multiplex) # The batching context is shared by the multiplex, # so fetch it out and use that instance. interpreter = query.context.namespace(:interpreter)[:interpreter_instance] = multiplex.context[:interpreter_instance] interpreter.evaluate(query) query end def self.finish_multiplex(_results, multiplex) interpreter = multiplex.context[:interpreter_instance] interpreter.sync_lazies(multiplex: multiplex) end def self.finish_query(query, _multiplex) { "data" => query.context.namespace(:interpreter)[:runtime].final_value } end # Run the eager part of `query` # @return {Interpreter::Runtime} def evaluate(query) # Although queries in a multiplex _share_ an Interpreter instance, # they also have another item of state, which is private to that query # in particular, assign it here: runtime = Runtime.new( query: query, response: HashResponse.new, ) query.context.namespace(:interpreter)[:runtime] = runtime query.trace("execute_query", {query: query}) do runtime.run_eager end runtime end # Run the lazy part of `query` or `multiplex`. # @return [void] def sync_lazies(query: nil, multiplex: nil) tracer = query || multiplex if query.nil? && multiplex.queries.length == 1 query = multiplex.queries[0] end queries = multiplex ? multiplex.queries : [query] final_values = queries.map do |query| runtime = query.context.namespace(:interpreter)[:runtime] # it might not be present if the query has an error runtime ? runtime.final_value : nil end final_values.compact! tracer.trace("execute_query_lazy", {multiplex: multiplex, query: query}) do Interpreter::Resolve.resolve_all(final_values) end queries.each do |query| runtime = query.context.namespace(:interpreter)[:runtime] if runtime runtime.delete_interpreter_context(:current_path) runtime.delete_interpreter_context(:current_field) runtime.delete_interpreter_context(:current_object) runtime.delete_interpreter_context(:current_arguments) end end nil end class ListResultFailedError < GraphQL::Error def initialize(value:, path:, field:) message = "Failed to build a GraphQL list result for field `#{field.path}` at path `#{path.join(".")}`.\n".dup message << "Expected `#{value.inspect}` to implement `.each` to satisfy the GraphQL return type `#{field.type.to_type_signature}`.\n" if field.connection? message << "\nThis field was treated as a Relay-style connection; add `connection: false` to the `field(...)` to disable this behavior." end super(message) end end end end end graphql-ruby-1.11.10/lib/graphql/execution/interpreter/000077500000000000000000000000001414121453000230355ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/execution/interpreter/argument_value.rb000066400000000000000000000015731414121453000264060ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Interpreter # A container for metadata regarding arguments present in a GraphQL query. # @see Interpreter::Arguments#argument_values for a hash of these objects. class ArgumentValue def initialize(definition:, value:, default_used:) @definition = definition @value = value @default_used = default_used end # @return [Object] The Ruby-ready value for this Argument attr_reader :value # @return [GraphQL::Schema::Argument] The definition instance for this argument attr_reader :definition # @return [Boolean] `true` if the schema-defined `default_value:` was applied in this case. (No client-provided value was present.) def default_used? @default_used end end end end end graphql-ruby-1.11.10/lib/graphql/execution/interpreter/arguments.rb000066400000000000000000000025731414121453000253760ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Interpreter # A wrapper for argument hashes in GraphQL queries. # # @see GraphQL::Query#arguments_for to get access to these objects. class Arguments extend Forwardable include GraphQL::Dig # The Ruby-style arguments hash, ready for a resolver. # This hash is the one used at runtime. # # @return [Hash] def keyword_arguments @keyword_arguments ||= begin kwargs = {} argument_values.each do |name, arg_val| kwargs[name] = arg_val.value end kwargs end end # @param argument_values [nil, Hash{Symbol => ArgumentValue}] def initialize(argument_values:) @argument_values = argument_values @empty = argument_values.nil? || argument_values.empty? end # @return [Hash{Symbol => ArgumentValue}] def argument_values @argument_values ||= {} end def empty? @empty end def_delegators :keyword_arguments, :key?, :[], :fetch, :keys, :each, :values def_delegators :argument_values, :each_value def inspect "#<#{self.class} @keyword_arguments=#{keyword_arguments.inspect}>" end end end end end graphql-ruby-1.11.10/lib/graphql/execution/interpreter/arguments_cache.rb000066400000000000000000000052311414121453000265130ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Interpreter class ArgumentsCache def initialize(query) @query = query @storage = Hash.new do |h, ast_node| h[ast_node] = Hash.new do |h2, arg_owner| h2[arg_owner] = Hash.new do |h3, parent_object| # First, normalize all AST or Ruby values to a plain Ruby hash args_hash = prepare_args_hash(ast_node) # Then call into the schema to coerce those incoming values args = arg_owner.coerce_arguments(parent_object, args_hash, query.context) h3[parent_object] = @query.schema.after_lazy(args) do |resolved_args| # when this promise is resolved, update the cache with the resolved value h3[parent_object] = resolved_args end end end end end def fetch(ast_node, argument_owner, parent_object) @storage[ast_node][argument_owner][parent_object] end private NO_ARGUMENTS = {}.freeze NO_VALUE_GIVEN = Object.new def prepare_args_hash(ast_arg_or_hash_or_value) case ast_arg_or_hash_or_value when Hash if ast_arg_or_hash_or_value.empty? return NO_ARGUMENTS end args_hash = {} ast_arg_or_hash_or_value.each do |k, v| args_hash[k] = prepare_args_hash(v) end args_hash when Array ast_arg_or_hash_or_value.map { |v| prepare_args_hash(v) } when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InputObject, GraphQL::Language::Nodes::Directive if ast_arg_or_hash_or_value.arguments.empty? return NO_ARGUMENTS end args_hash = {} ast_arg_or_hash_or_value.arguments.each do |arg| v = prepare_args_hash(arg.value) if v != NO_VALUE_GIVEN args_hash[arg.name] = v end end args_hash when GraphQL::Language::Nodes::VariableIdentifier if @query.variables.key?(ast_arg_or_hash_or_value.name) variable_value = @query.variables[ast_arg_or_hash_or_value.name] prepare_args_hash(variable_value) else NO_VALUE_GIVEN end when GraphQL::Language::Nodes::Enum ast_arg_or_hash_or_value.name when GraphQL::Language::Nodes::NullValue nil else ast_arg_or_hash_or_value end end end end end end graphql-ruby-1.11.10/lib/graphql/execution/interpreter/execution_errors.rb000066400000000000000000000013471414121453000267660ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Interpreter class ExecutionErrors def initialize(ctx, ast_node, path) @context = ctx @ast_node = ast_node @path = path end def add(err_or_msg) err = case err_or_msg when String GraphQL::ExecutionError.new(err_or_msg) when GraphQL::ExecutionError err_or_msg else raise ArgumentError, "expected String or GraphQL::ExecutionError, not #{err_or_msg.class} (#{err_or_msg.inspect})" end err.ast_node ||= @ast_node err.path ||= @path @context.add_error(err) end end end end end graphql-ruby-1.11.10/lib/graphql/execution/interpreter/handles_raw_value.rb000066400000000000000000000007021414121453000270440ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Interpreter # Wrapper for raw values class RawValue def initialize(obj = nil) @object = obj end def resolve @object end end # Allows to return "raw" value from the resolver module HandlesRawValue def raw_value(obj) RawValue.new(obj) end end end end end graphql-ruby-1.11.10/lib/graphql/execution/interpreter/hash_response.rb000066400000000000000000000020561414121453000262260ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Interpreter # This response class handles `#write` by accumulating # values into a Hash. class HashResponse def initialize @result = {} end def final_value @result end def inspect "#<#{self.class.name} result=#{@result.inspect}>" end # Add `value` at `path`. # @return [void] def write(path, value) if path.empty? @result = value elsif (write_target = @result) i = 0 prefinal_steps = path.size - 1 # Use `while` to avoid a closure while i < prefinal_steps path_part = path[i] i += 1 write_target = write_target[path_part] end path_part = path[i] write_target[path_part] = value else # The response is completely nulled out end nil end end end end end graphql-ruby-1.11.10/lib/graphql/execution/interpreter/resolve.rb000066400000000000000000000034511414121453000250440ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Interpreter module Resolve # Continue field results in `results` until there's nothing else to continue. # @return [void] def self.resolve_all(results) while results.any? results = resolve(results) end end # After getting `results` back from an interpreter evaluation, # continue it until you get a response-ready Ruby value. # # `results` is one level of _depth_ of a query or multiplex. # # Resolve all lazy values in that depth before moving on # to the next level. # # It's assumed that the lazies will # return {Lazy} instances if there's more work to be done, # or return {Hash}/{Array} if the query should be continued. # # @param results [Array] # @return [Array] Same size, filled with finished values def self.resolve(results) next_results = [] # Work through the queue until it's empty while results.size > 0 result_value = results.shift if result_value.is_a?(Lazy) result_value = result_value.value end if result_value.is_a?(Lazy) # Since this field returned another lazy, # add it to the same queue results << result_value elsif result_value.is_a?(Hash) # This is part of the next level, add it next_results.concat(result_value.values) elsif result_value.is_a?(Array) # This is part of the next level, add it next_results.concat(result_value) end end next_results end end end end end graphql-ruby-1.11.10/lib/graphql/execution/interpreter/runtime.rb000066400000000000000000000630351414121453000250540ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Interpreter # I think it would be even better if we could somehow make # `continue_field` not recursive. "Trampolining" it somehow. # # @api private class Runtime # @return [GraphQL::Query] attr_reader :query # @return [Class] attr_reader :schema # @return [GraphQL::Query::Context] attr_reader :context def initialize(query:, response:) @query = query @schema = query.schema @context = query.context @interpreter_context = @context.namespace(:interpreter) @response = response @dead_paths = {} @types_at_paths = {} # A cache of { Class => { String => Schema::Field } } # Which assumes that MyObject.get_field("myField") will return the same field # during the lifetime of a query @fields_cache = Hash.new { |h, k| h[k] = {} } end def final_value @response.final_value end def inspect "#<#{self.class.name} response=#{@response.inspect}>" end # This _begins_ the execution. Some deferred work # might be stored up in lazies. # @return [void] def run_eager root_operation = query.selected_operation root_op_type = root_operation.operation_type || "query" root_type = schema.root_type_for_operation(root_op_type) path = [] set_all_interpreter_context(query.root_value, nil, nil, path) object_proxy = authorized_new(root_type, query.root_value, context, path) object_proxy = schema.sync_lazy(object_proxy) if object_proxy.nil? # Root .authorized? returned false. write_in_response(path, nil) else evaluate_selections(path, context.scoped_context, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type) end delete_interpreter_context(:current_path) delete_interpreter_context(:current_field) delete_interpreter_context(:current_object) delete_interpreter_context(:current_arguments) nil end def gather_selections(owner_object, owner_type, selections, selections_by_name) selections.each do |node| # Skip gathering this if the directive says so if !directives_include?(node, owner_object, owner_type) next end case node when GraphQL::Language::Nodes::Field response_key = node.alias || node.name selections = selections_by_name[response_key] # if there was already a selection of this field, # use an array to hold all selections, # otherise, use the single node to represent the selection if selections # This field was already selected at least once, # add this node to the list of selections s = Array(selections) s << node selections_by_name[response_key] = s else # No selection was found for this field yet selections_by_name[response_key] = node end when GraphQL::Language::Nodes::InlineFragment if node.type type_defn = schema.get_type(node.type.name) # Faster than .map{}.include?() query.warden.possible_types(type_defn).each do |t| if t == owner_type gather_selections(owner_object, owner_type, node.selections, selections_by_name) break end end else # it's an untyped fragment, definitely continue gather_selections(owner_object, owner_type, node.selections, selections_by_name) end when GraphQL::Language::Nodes::FragmentSpread fragment_def = query.fragments[node.name] type_defn = schema.get_type(fragment_def.type.name) possible_types = query.warden.possible_types(type_defn) possible_types.each do |t| if t == owner_type gather_selections(owner_object, owner_type, fragment_def.selections, selections_by_name) break end end else raise "Invariant: unexpected selection class: #{node.class}" end end end NO_ARGS = {}.freeze def evaluate_selections(path, scoped_context, owner_object, owner_type, selections, root_operation_type: nil) set_all_interpreter_context(owner_object, nil, nil, path) selections_by_name = {} gather_selections(owner_object, owner_type, selections, selections_by_name) selections_by_name.each do |result_name, field_ast_nodes_or_ast_node| # As a performance optimization, the hash key will be a `Node` if # there's only one selection of the field. But if there are multiple # selections of the field, it will be an Array of nodes if field_ast_nodes_or_ast_node.is_a?(Array) field_ast_nodes = field_ast_nodes_or_ast_node ast_node = field_ast_nodes.first else field_ast_nodes = nil ast_node = field_ast_nodes_or_ast_node end field_name = ast_node.name field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name) is_introspection = false if field_defn.nil? field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name)) is_introspection = true entry_point_field elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name)) is_introspection = true dynamic_field else raise "Invariant: no field for #{owner_type}.#{field_name}" end end return_type = field_defn.type next_path = path.dup next_path << result_name next_path.freeze # This seems janky, but we need to know # the field's return type at this path in order # to propagate `null` set_type_at_path(next_path, return_type) # Set this before calling `run_with_directives`, so that the directive can have the latest path set_all_interpreter_context(nil, field_defn, nil, next_path) context.scoped_context = scoped_context object = owner_object if is_introspection object = authorized_new(field_defn.owner, object, context, next_path) end begin kwarg_arguments = arguments(object, field_defn, ast_node) rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => e continue_value(next_path, e, owner_type, field_defn, return_type.non_null?, ast_node) next end after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |resolved_arguments| case resolved_arguments when GraphQL::ExecutionError, GraphQL::UnauthorizedError continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node) next end if resolved_arguments.empty? && field_defn.extras.empty? # We can avoid allocating the `{ Symbol => Object }` hash in this case kwarg_arguments = NO_ARGS else kwarg_arguments = resolved_arguments.keyword_arguments field_defn.extras.each do |extra| case extra when :ast_node kwarg_arguments[:ast_node] = ast_node when :execution_errors kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path) when :path kwarg_arguments[:path] = next_path when :lookahead if !field_ast_nodes field_ast_nodes = [ast_node] end kwarg_arguments[:lookahead] = Execution::Lookahead.new( query: query, ast_nodes: field_ast_nodes, field: field_defn, ) when :argument_details kwarg_arguments[:argument_details] = resolved_arguments else kwarg_arguments[extra] = field_defn.fetch_extra(extra, context) end end end set_all_interpreter_context(nil, nil, kwarg_arguments, nil) # Optimize for the case that field is selected only once if field_ast_nodes.nil? || field_ast_nodes.size == 1 next_selections = ast_node.selections else next_selections = [] field_ast_nodes.each { |f| next_selections.concat(f.selections) } end field_result = resolve_with_directives(object, ast_node) do # Actually call the field resolver and capture the result app_result = begin query.with_error_handling do query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do field_defn.resolve(object, kwarg_arguments, context) end end rescue GraphQL::ExecutionError => err err end after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result| continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node) if RawValue === continue_value # Write raw value directly to the response without resolving nested objects write_in_response(next_path, continue_value.resolve) elsif HALT != continue_value continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments) end end end # If this field is a root mutation field, immediately resolve # all of its child fields before moving on to the next root mutation field. # (Subselections of this mutation will still be resolved level-by-level.) if root_operation_type == "mutation" Interpreter::Resolve.resolve_all([field_result]) else field_result end end end end HALT = Object.new def continue_value(path, value, parent_type, field, is_non_null, ast_node) if value.nil? if is_non_null err = parent_type::InvalidNullError.new(parent_type, field, value) write_invalid_null_in_response(path, err) else write_in_response(path, nil) end HALT elsif value.is_a?(GraphQL::ExecutionError) value.path ||= path value.ast_node ||= ast_node write_execution_errors_in_response(path, [value]) HALT elsif value.is_a?(Array) && value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) } value.each_with_index do |error, index| error.ast_node ||= ast_node error.path ||= path + (field.type.list? ? [index] : []) end write_execution_errors_in_response(path, value) HALT elsif value.is_a?(GraphQL::UnauthorizedError) # this hook might raise & crash, or it might return # a replacement value next_value = begin schema.unauthorized_object(value) rescue GraphQL::ExecutionError => err err end continue_value(path, next_value, parent_type, field, is_non_null, ast_node) elsif GraphQL::Execution::Execute::SKIP == value HALT else value end end # The resolver for `field` returned `value`. Continue to execute the query, # treating `value` as `type` (probably the return type of the field). # # Use `next_selections` to resolve object fields, if there are any. # # Location information from `path` and `ast_node`. # # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later def continue_field(path, value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments) # rubocop:disable Metrics/ParameterLists case current_type.kind.name when "SCALAR", "ENUM" r = current_type.coerce_result(value, context) write_in_response(path, r) r when "UNION", "INTERFACE" resolved_type_or_lazy, resolved_value = resolve_type(current_type, value, path) resolved_value ||= value after_lazy(resolved_type_or_lazy, owner: current_type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |resolved_type| possible_types = query.possible_types(current_type) if !possible_types.include?(resolved_type) parent_type = field.owner_type err_class = current_type::UnresolvedTypeError type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types) schema.type_error(type_error, context) write_in_response(path, nil) nil else continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments) end end when "OBJECT" object_proxy = begin authorized_new(current_type, value, context, path) rescue GraphQL::ExecutionError => err err end after_lazy(object_proxy, owner: current_type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |inner_object| continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node) if HALT != continue_value response_hash = {} write_in_response(path, response_hash) evaluate_selections(path, context.scoped_context, continue_value, current_type, next_selections) response_hash end end when "LIST" response_list = [] write_in_response(path, response_list) inner_type = current_type.of_type idx = 0 scoped_context = context.scoped_context begin value.each do |inner_value| next_path = path.dup next_path << idx next_path.freeze idx += 1 set_type_at_path(next_path, inner_type) # This will update `response_list` with the lazy after_lazy(inner_value, owner: inner_type, path: next_path, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value| continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node) if HALT != continue_value continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments) end end end rescue NoMethodError => err # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.) if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true) # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug. raise ListResultFailedError.new(value: value, field: field, path: path) else # This was some other NoMethodError -- let it bubble to reveal the real error. raise end end response_list when "NON_NULL" inner_type = current_type.of_type # Don't `set_type_at_path` because we want the static type, # we're going to use that to determine whether a `nil` should be propagated or not. continue_field(path, value, owner_type, field, inner_type, ast_node, next_selections, true, owner_object, arguments) else raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})" end end def resolve_with_directives(object, ast_node, &block) return yield if ast_node.directives.empty? run_directive(object, ast_node, 0, &block) end def run_directive(object, ast_node, idx, &block) dir_node = ast_node.directives[idx] if !dir_node yield else dir_defn = schema.directives.fetch(dir_node.name) if !dir_defn.is_a?(Class) dir_defn = dir_defn.type_class || raise("Only class-based directives are supported (not `@#{dir_node.name}`)") end dir_args = arguments(nil, dir_defn, dir_node).keyword_arguments dir_defn.resolve(object, dir_args, context) do run_directive(object, ast_node, idx + 1, &block) end end end # Check {Schema::Directive.include?} for each directive that's present def directives_include?(node, graphql_object, parent_type) node.directives.each do |dir_node| dir_defn = schema.directives.fetch(dir_node.name).type_class || raise("Only class-based directives are supported (not #{dir_node.name.inspect})") args = arguments(graphql_object, dir_defn, dir_node).keyword_arguments if !dir_defn.include?(graphql_object, args, context) return false end end true end def set_all_interpreter_context(object, field, arguments, path) if object @context[:current_object] = @interpreter_context[:current_object] = object end if field @context[:current_field] = @interpreter_context[:current_field] = field end if arguments @context[:current_arguments] = @interpreter_context[:current_arguments] = arguments end if path @context[:current_path] = @interpreter_context[:current_path] = path end end # @param obj [Object] Some user-returned value that may want to be batched # @param path [Array] # @param field [GraphQL::Schema::Field] # @param eager [Boolean] Set to `true` for mutation root fields only # @param trace [Boolean] If `false`, don't wrap this with field tracing # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it. def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false, trace: true, &block) set_all_interpreter_context(owner_object, field, arguments, path) if schema.lazy?(lazy_obj) lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do set_all_interpreter_context(owner_object, field, arguments, path) context.scoped_context = scoped_context # Wrap the execution of _this_ method with tracing, # but don't wrap the continuation below inner_obj = begin query.with_error_handling do if trace query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do schema.sync_lazy(lazy_obj) end else schema.sync_lazy(lazy_obj) end end rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err err end after_lazy(inner_obj, owner: owner, field: field, path: path, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager, trace: trace, &block) end if eager lazy.value else write_in_response(path, lazy) lazy end else yield(lazy_obj) end end def arguments(graphql_object, arg_owner, ast_node) # Don't cache arguments if field extras or extensions are requested since they can mutate the argument data structure if arg_owner.arguments_statically_coercible? && (!arg_owner.is_a?(GraphQL::Schema::Field) || (arg_owner.extras.empty? && arg_owner.extensions.empty?)) query.arguments_for(ast_node, arg_owner) else # The arguments must be prepared in the context of the given object query.arguments_for(ast_node, arg_owner, parent_object: graphql_object) end end def write_invalid_null_in_response(path, invalid_null_error) if !dead_path?(path) schema.type_error(invalid_null_error, context) write_in_response(path, nil) add_dead_path(path) end end def write_execution_errors_in_response(path, errors) if !dead_path?(path) errors.each do |v| context.errors << v end write_in_response(path, nil) add_dead_path(path) end end def write_in_response(path, value) if dead_path?(path) return else if value.nil? && path.any? && type_at(path).non_null? # This nil is invalid, try writing it at the previous spot propagate_path = path[0..-2] write_in_response(propagate_path, value) add_dead_path(propagate_path) else @response.write(path, value) end end end # To propagate nulls, we have to know what the field type was # at previous parts of the response. # This hash matches the response def type_at(path) @types_at_paths.fetch(path) end def set_type_at_path(path, type) @types_at_paths[path] = type nil end # Mark `path` as having been permanently nulled out. # No values will be added beyond that path. def add_dead_path(path) dead = @dead_paths path.each do |part| dead = dead[part] ||= {} end dead[:__dead] = true end def dead_path?(path) res = @dead_paths path.each do |part| if res if res[:__dead] break else res = res[part] end end end res && res[:__dead] end # Set this pair in the Query context, but also in the interpeter namespace, # for compatibility. def set_interpreter_context(key, value) @interpreter_context[key] = value @context[key] = value end def delete_interpreter_context(key) @interpreter_context.delete(key) @context.delete(key) end def resolve_type(type, value, path) trace_payload = { context: context, type: type, object: value, path: path } resolved_type, resolved_value = query.trace("resolve_type", trace_payload) do query.resolve_type(type, value) end if schema.lazy?(resolved_type) GraphQL::Execution::Lazy.new do query.trace("resolve_type_lazy", trace_payload) do schema.sync_lazy(resolved_type) end end else [resolved_type, resolved_value] end end def authorized_new(type, value, context, path) trace_payload = { context: context, type: type, object: value, path: path } auth_val = context.query.trace("authorized", trace_payload) do type.authorized_new(value, context) end if context.schema.lazy?(auth_val) GraphQL::Execution::Lazy.new do context.query.trace("authorized_lazy", trace_payload) do context.schema.sync_lazy(auth_val) end end else auth_val end end end end end end graphql-ruby-1.11.10/lib/graphql/execution/lazy.rb000066400000000000000000000044531414121453000220040ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/execution/lazy/lazy_method_map" require "graphql/execution/lazy/resolve" module GraphQL module Execution # This wraps a value which is available, but not yet calculated, like a promise or future. # # Calling `#value` will trigger calculation & return the "lazy" value. # # This is an itty-bitty promise-like object, with key differences: # - It has only two states, not-resolved and resolved # - It has no error-catching functionality # @api private class Lazy # Traverse `val`, lazily resolving any values along the way # @param val [Object] A data structure containing mixed plain values and `Lazy` instances # @return void def self.resolve(val) Resolve.resolve(val) end attr_reader :path, :field # Create a {Lazy} which will get its inner value by calling the block # @param path [Array] # @param field [GraphQL::Schema::Field] # @param get_value_func [Proc] a block to get the inner value (later) def initialize(path: nil, field: nil, &get_value_func) @get_value_func = get_value_func @resolved = false @path = path @field = field end # @return [Object] The wrapped value, calling the lazy block if necessary def value if !@resolved @resolved = true @value = begin v = @get_value_func.call if v.is_a?(Lazy) v = v.value end v rescue GraphQL::ExecutionError => err err end end if @value.is_a?(StandardError) raise @value else @value end end # @return [Lazy] A {Lazy} whose value depends on another {Lazy}, plus any transformations in `block` def then self.class.new { yield(value) } end # @param lazies [Array] Maybe-lazy objects # @return [Lazy] A lazy which will sync all of `lazies` def self.all(lazies) self.new { lazies.map { |l| l.is_a?(Lazy) ? l.value : l } } end # This can be used for fields which _had no_ lazy results # @api private NullResult = Lazy.new(){} NullResult.value end end end graphql-ruby-1.11.10/lib/graphql/execution/lazy/000077500000000000000000000000001414121453000214515ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/execution/lazy/lazy_method_map.rb000066400000000000000000000055771414121453000251700ustar00rootroot00000000000000# frozen_string_literal: true require 'thread' begin require 'concurrent' rescue LoadError # no problem, we'll fallback to our own map end module GraphQL module Execution class Lazy # {GraphQL::Schema} uses this to match returned values to lazy resolution methods. # Methods may be registered for classes, they apply to its subclasses also. # The result of this lookup is cached for future resolutions. # Instances of this class are thread-safe. # @api private # @see {Schema#lazy?} looks up values from this map class LazyMethodMap def initialize(use_concurrent: defined?(Concurrent::Map)) @storage = use_concurrent ? Concurrent::Map.new : ConcurrentishMap.new end def initialize_copy(other) @storage = other.storage.dup end # @param lazy_class [Class] A class which represents a lazy value (subclasses may also be used) # @param lazy_value_method [Symbol] The method to call on this class to get its value def set(lazy_class, lazy_value_method) @storage[lazy_class] = lazy_value_method end # @param value [Object] an object which may have a `lazy_value_method` registered for its class or superclasses # @return [Symbol, nil] The `lazy_value_method` for this object, or nil def get(value) @storage.compute_if_absent(value.class) { find_superclass_method(value.class) } end def each @storage.each_pair { |k, v| yield(k, v) } end protected attr_reader :storage private def find_superclass_method(value_class) @storage.each_pair { |lazy_class, lazy_value_method| return lazy_value_method if value_class < lazy_class } nil end # Mock the Concurrent::Map API class ConcurrentishMap extend Forwardable # Technically this should be under the mutex too, # but I know it's only used when the lock is already acquired. def_delegators :@storage, :each_pair, :size def initialize @semaphore = Mutex.new # Access to this hash must always be managed by the mutex # since it may be modified at runtime @storage = {} end def []=(key, value) @semaphore.synchronize { @storage[key] = value } end def compute_if_absent(key) @semaphore.synchronize { @storage.fetch(key) { @storage[key] = yield } } end def initialize_copy(other) @semaphore = Mutex.new @storage = other.copy_storage end protected def copy_storage @semaphore.synchronize { @storage.dup } end end end end end end graphql-ruby-1.11.10/lib/graphql/execution/lazy/resolve.rb000066400000000000000000000050621414121453000234600ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution class Lazy # Helpers for dealing with data structures containing {Lazy} instances # @api private module Resolve # Mutate `value`, replacing {Lazy} instances in place with their resolved values # @return [void] # This object can be passed like an array, but it doesn't allocate an # array until it's used. # # There's one crucial difference: you have to _capture_ the result # of `#<<`. (This _works_ with arrays but isn't required, since it has a side-effect.) # @api private module NullAccumulator def self.<<(item) [item] end def self.empty? true end end def self.resolve(value) lazies = resolve_in_place(value) deep_sync(lazies) end def self.resolve_in_place(value) acc = each_lazy(NullAccumulator, value) if acc.empty? Lazy::NullResult else Lazy.new { acc.each_with_index { |ctx, idx| acc[idx] = ctx.value.value } resolve_in_place(acc) } end end # If `value` is a collection, # add any {Lazy} instances in the collection # to `acc` # @return [void] def self.each_lazy(acc, value) case value when Hash value.each do |key, field_result| acc = each_lazy(acc, field_result) end when Array value.each do |field_result| acc = each_lazy(acc, field_result) end when Query::Context::SharedMethods field_value = value.value case field_value when Lazy acc = acc << value when Enumerable # shortcut for Hash & Array acc = each_lazy(acc, field_value) end end acc end # Traverse `val`, triggering resolution for each {Lazy}. # These {Lazy}s are expected to mutate their owner data structures # during resolution! (They're created with the `.then` calls in `resolve_in_place`). # @return [void] def self.deep_sync(val) case val when Lazy deep_sync(val.value) when Array val.each { |v| deep_sync(v.value) } when Hash val.each { |k, v| deep_sync(v.value) } end end end end end end graphql-ruby-1.11.10/lib/graphql/execution/lookahead.rb000066400000000000000000000275541414121453000227630ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution # Lookahead creates a uniform interface to inspect the forthcoming selections. # # It assumes that the AST it's working with is valid. (So, it's safe to use # during execution, but if you're using it directly, be sure to validate first.) # # A field may get access to its lookahead by adding `extras: [:lookahead]` # to its configuration. # # @example looking ahead in a field # field :articles, [Types::Article], null: false, # extras: [:lookahead] # # # For example, imagine a faster database call # # may be issued when only some fields are requested. # # # # Imagine that _full_ fetch must be made to satisfy `fullContent`, # # we can look ahead to see if we need that field. If we do, # # we make the expensive database call instead of the cheap one. # def articles(lookahead:) # if lookahead.selects?(:full_content) # fetch_full_articles(object) # else # fetch_preview_articles(object) # end # end class Lookahead # @param query [GraphQL::Query] # @param ast_nodes [Array, Array] # @param field [GraphQL::Schema::Field] if `ast_nodes` are fields, this is the field definition matching those nodes # @param root_type [Class] if `ast_nodes` are operation definition, this is the root type for that operation def initialize(query:, ast_nodes:, field: nil, root_type: nil, owner_type: nil) @ast_nodes = ast_nodes.freeze @field = field @root_type = root_type @query = query @selected_type = @field ? @field.type.unwrap : root_type @owner_type = owner_type end # @return [Array] attr_reader :ast_nodes # @return [GraphQL::Schema::Field] attr_reader :field # @return [GraphQL::Schema::Object, GraphQL::Schema::Union, GraphQL::Schema::Interface] attr_reader :owner_type # @return [Hash] def arguments if defined?(@arguments) @arguments else @arguments = if @field @query.schema.after_lazy(@query.arguments_for(@ast_nodes.first, @field)) do |args| args.is_a?(Execution::Interpreter::Arguments) ? args.keyword_arguments : args end else nil end end end # True if this node has a selection on `field_name`. # If `field_name` is a String, it is treated as a GraphQL-style (camelized) # field name and used verbatim. If `field_name` is a Symbol, it is # treated as a Ruby-style (underscored) name and camelized before comparing. # # If `arguments:` is provided, each provided key/value will be matched # against the arguments in the next selection. This method will return false # if any of the given `arguments:` are not present and matching in the next selection. # (But, the next selection may contain _more_ than the given arguments.) # @param field_name [String, Symbol] # @param arguments [Hash] Arguments which must match in the selection # @return [Boolean] def selects?(field_name, arguments: nil) selection(field_name, arguments: arguments).selected? end # @return [Boolean] True if this lookahead represents a field that was requested def selected? true end # Like {#selects?}, but can be used for chaining. # It returns a null object (check with {#selected?}) # @return [GraphQL::Execution::Lookahead] def selection(field_name, selected_type: @selected_type, arguments: nil) next_field_name = normalize_name(field_name) next_field_defn = get_class_based_field(selected_type, next_field_name) if next_field_defn next_nodes = [] @ast_nodes.each do |ast_node| ast_node.selections.each do |selection| find_selected_nodes(selection, next_field_name, next_field_defn, arguments: arguments, matches: next_nodes) end end if next_nodes.any? Lookahead.new(query: @query, ast_nodes: next_nodes, field: next_field_defn, owner_type: selected_type) else NULL_LOOKAHEAD end else NULL_LOOKAHEAD end end # Like {#selection}, but for all nodes. # It returns a list of Lookaheads for all Selections # # If `arguments:` is provided, each provided key/value will be matched # against the arguments in each selection. This method will filter the selections # if any of the given `arguments:` do not match the given selection. # # @example getting the name of a selection # def articles(lookahead:) # next_lookaheads = lookahead.selections # => [#, ...] # next_lookaheads.map(&:name) #=> [:full_content, :title] # end # # @param arguments [Hash] Arguments which must match in the selection # @return [Array] def selections(arguments: nil) subselections_by_type = {} subselections_on_type = subselections_by_type[@selected_type] = {} @ast_nodes.each do |node| find_selections(subselections_by_type, subselections_on_type, @selected_type, node.selections, arguments) end subselections = [] subselections_by_type.each do |type, ast_nodes_by_response_key| ast_nodes_by_response_key.each do |response_key, ast_nodes| field_defn = get_class_based_field(type, ast_nodes.first.name) lookahead = Lookahead.new(query: @query, ast_nodes: ast_nodes, field: field_defn, owner_type: type) subselections.push(lookahead) end end subselections end # The method name of the field. # It returns the method_sym of the Lookahead's field. # # @example getting the name of a selection # def articles(lookahead:) # article.selection(:full_content).name # => :full_content # # ... # end # # @return [Symbol] def name @field && @field.original_name end def inspect "#" end # This is returned for {Lookahead#selection} when a non-existent field is passed class NullLookahead < Lookahead # No inputs required here. def initialize end def selected? false end def selects?(*) false end def selection(*) NULL_LOOKAHEAD end def selections(*) [] end def inspect "#" end end # A singleton, so that misses don't come with overhead. NULL_LOOKAHEAD = NullLookahead.new private # If it's a symbol, stringify and camelize it def normalize_name(name) if name.is_a?(Symbol) Schema::Member::BuildType.camelize(name.to_s) else name end end def normalize_keyword(keyword) if keyword.is_a?(String) Schema::Member::BuildType.underscore(keyword).to_sym else keyword end end # Wrap get_field and ensure that it returns a GraphQL::Schema::Field. # Remove this when legacy execution is removed. def get_class_based_field(type, name) f = @query.get_field(type, name) f && f.type_class end def skipped_by_directive?(ast_selection) ast_selection.directives.each do |directive| dir_defn = @query.schema.directives.fetch(directive.name) directive_class = dir_defn.type_class if directive_class dir_args = @query.arguments_for(directive, dir_defn) return true unless directive_class.static_include?(dir_args, @query.context) end end false end def find_selections(subselections_by_type, selections_on_type, selected_type, ast_selections, arguments) ast_selections.each do |ast_selection| next if skipped_by_directive?(ast_selection) case ast_selection when GraphQL::Language::Nodes::Field response_key = ast_selection.alias || ast_selection.name if selections_on_type.key?(response_key) selections_on_type[response_key] << ast_selection elsif arguments.nil? || arguments.empty? selections_on_type[response_key] = [ast_selection] else field_defn = get_class_based_field(selected_type, ast_selection.name) if arguments_match?(arguments, field_defn, ast_selection) selections_on_type[response_key] = [ast_selection] end end when GraphQL::Language::Nodes::InlineFragment on_type = selected_type subselections_on_type = selections_on_type if (t = ast_selection.type) # Assuming this is valid, that `t` will be found. on_type = @query.schema.get_type(t.name).type_class subselections_on_type = subselections_by_type[on_type] ||= {} end find_selections(subselections_by_type, subselections_on_type, on_type, ast_selection.selections, arguments) when GraphQL::Language::Nodes::FragmentSpread frag_defn = @query.fragments[ast_selection.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{ast_selection.name} (found: #{@query.fragments.keys})") # Again, assuming a valid AST on_type = @query.schema.get_type(frag_defn.type.name).type_class subselections_on_type = subselections_by_type[on_type] ||= {} find_selections(subselections_by_type, subselections_on_type, on_type, frag_defn.selections, arguments) else raise "Invariant: Unexpected selection type: #{ast_selection.class}" end end end # If a selection on `node` matches `field_name` (which is backed by `field_defn`) # and matches the `arguments:` constraints, then add that node to `matches` def find_selected_nodes(node, field_name, field_defn, arguments:, matches:) return if skipped_by_directive?(node) case node when GraphQL::Language::Nodes::Field if node.name == field_name if arguments.nil? || arguments.empty? # No constraint applied matches << node elsif arguments_match?(arguments, field_defn, node) matches << node end end when GraphQL::Language::Nodes::InlineFragment node.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches) } when GraphQL::Language::Nodes::FragmentSpread frag_defn = @query.fragments[node.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{node.name} (found: #{@query.fragments.keys})") frag_defn.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches) } else raise "Unexpected selection comparison on #{node.class.name} (#{node})" end end def arguments_match?(arguments, field_defn, field_node) query_kwargs = @query.arguments_for(field_node, field_defn) arguments.all? do |arg_name, arg_value| arg_name = normalize_keyword(arg_name) # Make sure the constraint is present with a matching value query_kwargs.key?(arg_name) && query_kwargs[arg_name] == arg_value end end end end end graphql-ruby-1.11.10/lib/graphql/execution/multiplex.rb000066400000000000000000000174501414121453000230510ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution # Execute multiple queries under the same multiplex "umbrella". # They can share a batching context and reduce redundant database hits. # # The flow is: # # - Multiplex instrumentation setup # - Query instrumentation setup # - Analyze the multiplex + each query # - Begin each query # - Resolve lazy values, breadth-first across all queries # - Finish each query (eg, get errors) # - Query instrumentation teardown # - Multiplex instrumentation teardown # # If one query raises an application error, all queries will be in undefined states. # # Validation errors and {GraphQL::ExecutionError}s are handled in isolation: # one of these errors in one query will not affect the other queries. # # @see {Schema#multiplex} for public API # @api private class Multiplex # Used internally to signal that the query shouldn't be executed # @api private NO_OPERATION = {}.freeze include Tracing::Traceable attr_reader :context, :queries, :schema, :max_complexity def initialize(schema:, queries:, context:, max_complexity:) @schema = schema @queries = queries @context = context @tracers = schema.tracers + (context[:tracers] || []) # Support `context: {backtrace: true}` if context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer) @tracers << GraphQL::Backtrace::Tracer end @max_complexity = max_complexity end class << self def run_all(schema, query_options, **kwargs) queries = query_options.map { |opts| GraphQL::Query.new(schema, nil, **opts) } run_queries(schema, queries, **kwargs) end # @param schema [GraphQL::Schema] # @param queries [Array] # @param context [Hash] # @param max_complexity [Integer, nil] # @return [Array] One result per query def run_queries(schema, queries, context: {}, max_complexity: schema.max_complexity) multiplex = self.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity) multiplex.trace("execute_multiplex", { multiplex: multiplex }) do if supports_multiplexing?(schema) instrument_and_analyze(multiplex) do run_as_multiplex(multiplex) end else if queries.length != 1 raise ArgumentError, "Multiplexing doesn't support custom execution strategies, run one query at a time instead" else instrument_and_analyze(multiplex) do [run_one_legacy(schema, queries.first)] end end end end end private def run_as_multiplex(multiplex) multiplex.schema.query_execution_strategy.begin_multiplex(multiplex) queries = multiplex.queries # Do as much eager evaluation of the query as possible results = queries.map do |query| begin_query(query, multiplex) end # Then, work through lazy results in a breadth-first way multiplex.schema.query_execution_strategy.finish_multiplex(results, multiplex) # Then, find all errors and assign the result to the query object results.each_with_index.map do |data_result, idx| query = queries[idx] finish_query(data_result, query, multiplex) # Get the Query::Result, not the Hash query.result end rescue Exception # TODO rescue at a higher level so it will catch errors in analysis, too # Assign values here so that the query's `@executed` becomes true queries.map { |q| q.result_values ||= {} } raise end # @param query [GraphQL::Query] # @return [Hash] The initial result (may not be finished if there are lazy values) def begin_query(query, multiplex) operation = query.selected_operation if operation.nil? || !query.valid? || query.context.errors.any? NO_OPERATION else begin # These were checked to be the same in `#supports_multiplexing?` query.schema.query_execution_strategy.begin_query(query, multiplex) rescue GraphQL::ExecutionError => err query.context.errors << err NO_OPERATION end end end # @param data_result [Hash] The result for the "data" key, if any # @param query [GraphQL::Query] The query which was run # @return [Hash] final result of this query, including all values and errors def finish_query(data_result, query, multiplex) # Assign the result so that it can be accessed in instrumentation query.result_values = if data_result.equal?(NO_OPERATION) if !query.valid? || query.context.errors.any? # A bit weird, but `Query#static_errors` _includes_ `query.context.errors` { "errors" => query.static_errors.map(&:to_h) } else data_result end else # Use `context.value` which was assigned during execution result = query.schema.query_execution_strategy.finish_query(query, multiplex) if query.context.errors.any? error_result = query.context.errors.map(&:to_h) result["errors"] = error_result end result end end # use the old `query_execution_strategy` etc to run this query def run_one_legacy(schema, query) query.result_values = if !query.valid? all_errors = query.validation_errors + query.analysis_errors + query.context.errors if all_errors.any? { "errors" => all_errors.map(&:to_h) } else nil end else GraphQL::Query::Executor.new(query).result end end DEFAULT_STRATEGIES = [ GraphQL::Execution::Execute, GraphQL::Execution::Interpreter ] # @return [Boolean] True if the schema is only using one strategy, and it's one that supports multiplexing. def supports_multiplexing?(schema) schema_strategies = [schema.query_execution_strategy, schema.mutation_execution_strategy, schema.subscription_execution_strategy] schema_strategies.uniq! schema_strategies.size == 1 && DEFAULT_STRATEGIES.include?(schema_strategies.first) end # Apply multiplex & query instrumentation to `queries`. # # It yields when the queries should be executed, then runs teardown. def instrument_and_analyze(multiplex) GraphQL::Execution::Instrumentation.apply_instrumenters(multiplex) do schema = multiplex.schema if schema.interpreter? && schema.analysis_engine != GraphQL::Analysis::AST raise <<-ERR Can't use `GraphQL::Execution::Interpreter` without `GraphQL::Analysis::AST`, please add this plugin to your schema: use GraphQL::Analysis::AST For information about the new analysis engine: https://graphql-ruby.org/queries/ast_analysis.html ERR end multiplex_analyzers = schema.multiplex_analyzers if multiplex.max_complexity multiplex_analyzers += if schema.using_ast_analysis? [GraphQL::Analysis::AST::MaxQueryComplexity] else [GraphQL::Analysis::MaxQueryComplexity.new(multiplex.max_complexity)] end end schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers) yield end end end end end end graphql-ruby-1.11.10/lib/graphql/execution/typecast.rb000066400000000000000000000031061414121453000226530ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Execution # @api private module Typecast # @return [Boolean] def self.subtype?(parent_type, child_type) if parent_type == child_type # Equivalent types are subtypes true elsif child_type.is_a?(GraphQL::NonNullType) # A non-null type is a subtype of a nullable type # if its inner type is a subtype of that type if parent_type.is_a?(GraphQL::NonNullType) subtype?(parent_type.of_type, child_type.of_type) else subtype?(parent_type, child_type.of_type) end else case parent_type when GraphQL::InterfaceType # A type is a subtype of an interface # if it implements that interface case child_type when GraphQL::ObjectType child_type.interfaces.include?(parent_type) else false end when GraphQL::UnionType # A type is a subtype of that union # if the union includes that type parent_type.possible_types.include?(child_type) when GraphQL::ListType # A list type is a subtype of another list type # if its inner type is a subtype of the other inner type case child_type when GraphQL::ListType subtype?(parent_type.of_type, child_type.of_type) else false end else false end end end end end end graphql-ruby-1.11.10/lib/graphql/execution_error.rb000066400000000000000000000032171414121453000222330ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # If a field's resolve function returns a {ExecutionError}, # the error will be inserted into the response's `"errors"` key # and the field will resolve to `nil`. class ExecutionError < GraphQL::Error # @return [GraphQL::Language::Nodes::Field] the field where the error occurred attr_accessor :ast_node # @return [String] an array describing the JSON-path into the execution # response which corresponds to this error. attr_accessor :path # @return [Hash] Optional data for error objects # @deprecated Use `extensions` instead of `options`. The GraphQL spec # recommends that any custom entries in an error be under the # `extensions` key. attr_accessor :options # @return [Hash] Optional custom data for error objects which will be added # under the `extensions` key. attr_accessor :extensions def initialize(message, ast_node: nil, options: nil, extensions: nil) @ast_node = ast_node @options = options @extensions = extensions super(message) end # @return [Hash] An entry for the response's "errors" key def to_h hash = { "message" => message, } if ast_node hash["locations"] = [ { "line" => ast_node.line, "column" => ast_node.col, } ] end if path hash["path"] = path end if options hash.merge!(options) end if extensions hash["extensions"] = extensions.each_with_object({}) { |(key, value), ext| ext[key.to_s] = value } end hash end end end graphql-ruby-1.11.10/lib/graphql/field.rb000066400000000000000000000163321414121453000201040ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/field/resolve" module GraphQL # @api deprecated class Field include GraphQL::Define::InstanceDefinable accepts_definitions :name, :description, :deprecation_reason, :resolve, :lazy_resolve, :type, :arguments, :property, :hash_key, :complexity, :mutation, :function, :edge_class, :relay_node_field, :relay_nodes_field, :subscription_scope, :trace, :introspection, argument: GraphQL::Define::AssignArgument ensure_defined( :name, :deprecation_reason, :description, :description=, :property, :hash_key, :mutation, :arguments, :complexity, :function, :resolve, :resolve=, :lazy_resolve, :lazy_resolve=, :lazy_resolve_proc, :resolve_proc, :type, :type=, :name=, :property=, :hash_key=, :relay_node_field, :relay_nodes_field, :edges?, :edge_class, :subscription_scope, :introspection? ) # @return [Boolean] True if this is the Relay find-by-id field attr_accessor :relay_node_field # @return [Boolean] True if this is the Relay find-by-ids field attr_accessor :relay_nodes_field # @return [<#call(obj, args, ctx)>] A proc-like object which can be called to return the field's value attr_reader :resolve_proc # @return [<#call(obj, args, ctx)>] A proc-like object which can be called trigger a lazy resolution attr_reader :lazy_resolve_proc # @return [String] The name of this field on its {GraphQL::ObjectType} (or {GraphQL::InterfaceType}) attr_reader :name alias :graphql_name :name # @return [String, nil] The client-facing description of this field attr_accessor :description # @return [String, nil] The client-facing reason why this field is deprecated (if present, the field is deprecated) attr_accessor :deprecation_reason # @return [Hash GraphQL::Argument>] Map String argument names to their {GraphQL::Argument} implementations attr_accessor :arguments # @return [GraphQL::Relay::Mutation, nil] The mutation this field was derived from, if it was derived from a mutation attr_accessor :mutation # @return [Numeric, Proc] The complexity for this field (default: 1), as a constant or a proc like `->(query_ctx, args, child_complexity) { } # Numeric` attr_accessor :complexity # @return [Symbol, nil] The method to call on `obj` to return this field (overrides {#name} if present) attr_reader :property # @return [Object, nil] The key to access with `obj.[]` to resolve this field (overrides {#name} if present) attr_reader :hash_key # @return [Object, GraphQL::Function] The function used to derive this field attr_accessor :function attr_accessor :arguments_class attr_writer :connection attr_writer :introspection # @return [nil, String] Prefix for subscription names from this field attr_accessor :subscription_scope # @return [Boolean] True if this field should be traced. By default, fields are only traced if they are not a ScalarType or EnumType. attr_accessor :trace attr_accessor :ast_node # Future-compatible alias # @see {GraphQL::SchemaMember} alias :graphql_definition :itself # @return [Boolean] def connection? @connection end # @return [nil, Class] # @api private attr_accessor :edge_class # @return [Boolean] def edges? !!@edge_class end # @return [nil, Integer] attr_accessor :connection_max_page_size def initialize @complexity = 1 @arguments = {} @resolve_proc = build_default_resolver @lazy_resolve_proc = DefaultLazyResolve @relay_node_field = false @connection = false @connection_max_page_size = nil @edge_class = nil @trace = nil @introspection = false end def initialize_copy(other) ensure_defined super @arguments = other.arguments.dup end # @return [Boolean] Is this field a predefined introspection field? def introspection? @introspection end # Get a value for this field # @example resolving a field value # field.resolve(obj, args, ctx) # # @param object [Object] The object this field belongs to # @param arguments [Hash] Arguments declared in the query # @param context [GraphQL::Query::Context] def resolve(object, arguments, context) resolve_proc.call(object, arguments, context) end # Provide a new callable for this field's resolve function. If `nil`, # a new resolve proc will be build based on its {#name}, {#property} or {#hash_key}. # @param new_resolve_proc [<#call(obj, args, ctx)>, nil] def resolve=(new_resolve_proc) @resolve_proc = new_resolve_proc || build_default_resolver end def type=(new_return_type) @clean_type = nil @dirty_type = new_return_type end # Get the return type for this field. def type @clean_type ||= GraphQL::BaseType.resolve_related_type(@dirty_type) end def name=(new_name) old_name = defined?(@name) ? @name : nil @name = new_name if old_name != new_name && @resolve_proc.is_a?(Field::Resolve::NameResolve) # Since the NameResolve would use the old field name, # reset resolve proc when the name has changed self.resolve = nil end end # @param new_property [Symbol] A method to call to resolve this field. Overrides the existing resolve proc. def property=(new_property) @property = new_property self.resolve = nil # reset resolve proc end # @param new_hash_key [Symbol] A key to access with `#[key]` to resolve this field. Overrides the existing resolve proc. def hash_key=(new_hash_key) @hash_key = new_hash_key self.resolve = nil # reset resolve proc end def to_s "" end # If {#resolve} returned an object which should be handled lazily, # this method will be called later to force the object to return its value. # @param obj [Object] The {#resolve}-provided object, registered with {Schema#lazy_resolve} # @param args [GraphQL::Query::Arguments] Arguments to this field # @param ctx [GraphQL::Query::Context] Context for this field # @return [Object] The result of calling the registered method on `obj` def lazy_resolve(obj, args, ctx) @lazy_resolve_proc.call(obj, args, ctx) end # Assign a new resolve proc to this field. Used for {#lazy_resolve} def lazy_resolve=(new_lazy_resolve_proc) @lazy_resolve_proc = new_lazy_resolve_proc end # Prepare a lazy value for this field. It may be `then`-ed and resolved later. # @return [GraphQL::Execution::Lazy] A lazy wrapper around `obj` and its registered method name def prepare_lazy(obj, args, ctx) GraphQL::Execution::Lazy.new { lazy_resolve(obj, args, ctx) } end def type_class metadata[:type_class] end def get_argument(argument_name) arguments[argument_name] end private def build_default_resolver GraphQL::Field::Resolve.create_proc(self) end module DefaultLazyResolve def self.call(obj, args, ctx) ctx.schema.sync_lazy(obj) end end end end graphql-ruby-1.11.10/lib/graphql/field/000077500000000000000000000000001414121453000175525ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/field/resolve.rb000066400000000000000000000030031414121453000215520ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Field # Create resolve procs ahead of time based on a {GraphQL::Field}'s `name`, `property`, and `hash_key` configuration. module Resolve module_function # @param field [GraphQL::Field] A field that needs a resolve proc # @return [Proc] A resolver for this field, based on its config def create_proc(field) if field.property MethodResolve.new(field) elsif !field.hash_key.nil? HashKeyResolve.new(field.hash_key) else NameResolve.new(field) end end # These only require `obj` as input class BuiltInResolve end # Resolve the field by `public_send`ing `@method_name` class MethodResolve < BuiltInResolve def initialize(field) @method_name = field.property.to_sym end def call(obj, args, ctx) obj.public_send(@method_name) end end # Resolve the field by looking up `@hash_key` with `#[]` class HashKeyResolve < BuiltInResolve def initialize(hash_key) @hash_key = hash_key end def call(obj, args, ctx) obj[@hash_key] end end # Call the field's name at query-time since # it might have changed class NameResolve < BuiltInResolve def initialize(field) @field = field end def call(obj, args, ctx) obj.public_send(@field.name) end end end end end graphql-ruby-1.11.10/lib/graphql/filter.rb000066400000000000000000000023101414121453000202750ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # @api private class Filter def initialize(only: nil, except: nil) @only = only @except = except end # Returns true if `member, ctx` passes this filter def call(member, ctx) (@only ? @only.call(member, ctx) : true) && (@except ? !@except.call(member, ctx) : true) end def merge(only: nil, except: nil) onlies = [self].concat(Array(only)) merged_only = MergedOnly.build(onlies) merged_except = MergedExcept.build(Array(except)) self.class.new(only: merged_only, except: merged_except) end private class MergedOnly def initialize(first, second) @first = first @second = second end def call(member, ctx) @first.call(member, ctx) && @second.call(member, ctx) end def self.build(onlies) case onlies.size when 0 nil when 1 onlies[0] else onlies.reduce { |memo, only| self.new(memo, only) } end end end class MergedExcept < MergedOnly def call(member, ctx) @first.call(member, ctx) || @second.call(member, ctx) end end end end graphql-ruby-1.11.10/lib/graphql/float_type.rb000066400000000000000000000001351414121453000211610ustar00rootroot00000000000000# frozen_string_literal: true GraphQL::FLOAT_TYPE = GraphQL::Types::Float.graphql_definition graphql-ruby-1.11.10/lib/graphql/function.rb000066400000000000000000000064701414121453000206500ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # @api deprecated class Function # @return [Hash GraphQL::Argument>] Arguments, keyed by name def arguments self.class.arguments end # @return [GraphQL::BaseType] Return type def type self.class.type end # @return [Object] This function's resolver def call(obj, args, ctx) raise GraphQL::RequiredImplementationMissingError end # @return [String, nil] def description self.class.description end # @return [String, nil] def deprecation_reason self.class.deprecation_reason end # @return [Integer, Proc] def complexity self.class.complexity || 1 end class << self # Define an argument for this function & its subclasses # @see {GraphQL::Field} same arguments as the `argument` definition helper # @return [void] def argument(*args, **kwargs, &block) argument = GraphQL::Argument.from_dsl(*args, **kwargs, &block) own_arguments[argument.name] = argument nil end # @return [Hash GraphQL::Argument>] Arguments for this function class, including inherited arguments def arguments if parent_function? own_arguments.merge(superclass.arguments) else own_arguments.dup end end # Provides shorthand access to GraphQL's built-in types def types GraphQL::Define::TypeDefiner.instance end # Get or set the return type for this function class & descendants # @return [GraphQL::BaseType] def type(premade_type = nil, &block) if block_given? @type = GraphQL::ObjectType.define(&block) elsif premade_type @type = premade_type elsif parent_function? @type || superclass.type else @type end end def build_field(function) GraphQL::Field.define( arguments: function.arguments, complexity: function.complexity, type: function.type, resolve: function, description: function.description, function: function, deprecation_reason: function.deprecation_reason, ) end # Class-level reader/writer which is inherited # @api private def self.inherited_value(name) self.class_eval <<-RUBY def #{name}(new_value = nil) if new_value @#{name} = new_value elsif parent_function? @#{name} || superclass.#{name} else @#{name} end end RUBY end # @!method description(new_value = nil) # Get or set this class's description inherited_value(:description) # @!method deprecation_reason(new_value = nil) # Get or set this class's deprecation_reason inherited_value(:deprecation_reason) # @!method complexity(new_value = nil) # Get or set this class's complexity inherited_value(:complexity) private # Does this function inherit from another function? def parent_function? superclass <= GraphQL::Function end # Arguments defined on this class (not superclasses) def own_arguments @own_arguments ||= {} end end end end graphql-ruby-1.11.10/lib/graphql/id_type.rb000066400000000000000000000001271414121453000204510ustar00rootroot00000000000000# frozen_string_literal: true GraphQL::ID_TYPE = GraphQL::Types::ID.graphql_definition graphql-ruby-1.11.10/lib/graphql/input_object_type.rb000066400000000000000000000100141414121453000225360ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # @api deprecated class InputObjectType < GraphQL::BaseType accepts_definitions( :arguments, :mutation, input_field: GraphQL::Define::AssignArgument, argument: GraphQL::Define::AssignArgument ) attr_accessor :mutation, :arguments, :arguments_class ensure_defined(:mutation, :arguments, :input_fields) alias :input_fields :arguments # @!attribute mutation # @return [GraphQL::Relay::Mutation, nil] The mutation this field was derived from, if it was derived from a mutation # @!attribute arguments # @return [Hash GraphQL::Argument>] Map String argument names to their {GraphQL::Argument} implementations def initialize super @arguments = {} end def initialize_copy(other) super @arguments = other.arguments.dup end def kind GraphQL::TypeKinds::INPUT_OBJECT end def coerce_result(value, ctx = nil) if ctx.nil? warn_deprecated_coerce("coerce_isolated_result") ctx = GraphQL::Query::NullContext end # Allow the application to provide values as :symbols, and convert them to the strings value = value.reduce({}) { |memo, (k, v)| memo[k.to_s] = v; memo } result = {} arguments.each do |input_key, input_field_defn| input_value = value[input_key] if value.key?(input_key) result[input_key] = if input_value.nil? nil else input_field_defn.type.coerce_result(input_value, ctx) end end end result end def get_argument(argument_name) arguments[argument_name] end private def coerce_non_null_input(value, ctx) input_values = {} defaults_used = Set.new arguments.each do |input_key, input_field_defn| field_value = value[input_key] if value.key?(input_key) coerced_value = input_field_defn.type.coerce_input(field_value, ctx) input_values[input_key] = input_field_defn.prepare(coerced_value, ctx) elsif input_field_defn.default_value? coerced_value = input_field_defn.type.coerce_input(input_field_defn.default_value, ctx) input_values[input_key] = coerced_value defaults_used << input_key end end result = arguments_class.new(input_values, context: ctx, defaults_used: defaults_used) result.prepare end # @api private INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key, value object responding to `to_h` or `to_unsafe_h`." def validate_non_null_input(input, ctx) warden = ctx.warden result = GraphQL::Query::InputValidationResult.new if input.is_a?(Array) result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) }) return result end # We're not actually _using_ the coerced result, we're just # using these methods to make sure that the object will # behave like a hash below, when we call `each` on it. begin input.to_h rescue begin # Handle ActionController::Parameters: input.to_unsafe_h rescue # We're not sure it'll act like a hash, so reject it: result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) }) return result end end visible_arguments_map = warden.arguments(self).reduce({}) { |m, f| m[f.name] = f; m} # Items in the input that are unexpected input.each do |name, value| if visible_arguments_map[name].nil? result.add_problem("Field is not defined on #{self.graphql_name}", [name]) end end # Items in the input that are expected, but have invalid values visible_arguments_map.map do |name, field| field_result = field.type.validate_input(input[name], ctx) if !field_result.valid? result.merge_result!(name, field_result) end end result end end end graphql-ruby-1.11.10/lib/graphql/int_type.rb000066400000000000000000000001311414121453000206420ustar00rootroot00000000000000# frozen_string_literal: true GraphQL::INT_TYPE = GraphQL::Types::Int.graphql_definition graphql-ruby-1.11.10/lib/graphql/integer_decoding_error.rb000066400000000000000000000011001414121453000235060ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # This error is raised when `Types::Int` is given an input value outside of 32-bit integer range. # # For really big integer values, consider `GraphQL::Types::BigInt` # # @see GraphQL::Types::Int which raises this error class IntegerDecodingError < GraphQL::RuntimeTypeError # The value which couldn't be decoded attr_reader :integer_value def initialize(value) @integer_value = value super("Integer out of bounds: #{value}. \nConsider using GraphQL::Types::BigInt instead.") end end end graphql-ruby-1.11.10/lib/graphql/integer_encoding_error.rb000066400000000000000000000012541414121453000235320ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # This error is raised when `Types::Int` is asked to return a value outside of 32-bit integer range. # # For values outside that range, consider: # # - `ID` for database primary keys or other identifiers # - `GraphQL::Types::BigInt` for really big integer values # # @see GraphQL::Types::Int which raises this error class IntegerEncodingError < GraphQL::RuntimeTypeError # The value which couldn't be encoded attr_reader :integer_value def initialize(value) @integer_value = value super("Integer out of bounds: #{value}. \nConsider using ID or GraphQL::Types::BigInt instead.") end end end graphql-ruby-1.11.10/lib/graphql/interface_type.rb000066400000000000000000000044021414121453000220150ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # @api deprecated class InterfaceType < GraphQL::BaseType accepts_definitions :fields, :orphan_types, :resolve_type, field: GraphQL::Define::AssignObjectField attr_accessor :fields, :orphan_types, :resolve_type_proc attr_writer :type_membership_class ensure_defined :fields, :orphan_types, :resolve_type_proc, :resolve_type def initialize super @fields = {} @orphan_types = [] @resolve_type_proc = nil end def initialize_copy(other) super @fields = other.fields.dup @orphan_types = other.orphan_types.dup end def kind GraphQL::TypeKinds::INTERFACE end def resolve_type(value, ctx) ctx.query.resolve_type(self, value) end def resolve_type=(resolve_type_callable) @resolve_type_proc = resolve_type_callable end # @return [GraphQL::Field] The defined field for `field_name` def get_field(field_name) fields[field_name] end # These fields don't have instrumenation applied # @see [Schema#get_fields] Get fields with instrumentation # @return [Array] All fields on this type def all_fields fields.values end # Get a possible type of this {InterfaceType} by type name # @param type_name [String] # @param ctx [GraphQL::Query::Context] The context for the current query # @return [GraphQL::ObjectType, nil] The type named `type_name` if it exists and implements this {InterfaceType}, (else `nil`) def get_possible_type(type_name, ctx) type = ctx.query.get_type(type_name) type if type && ctx.query.warden.possible_types(self).include?(type) end # Check if a type is a possible type of this {InterfaceType} # @param type [String, GraphQL::BaseType] Name of the type or a type definition # @param ctx [GraphQL::Query::Context] The context for the current query # @return [Boolean] True if the `type` exists and is a member of this {InterfaceType}, (else `nil`) def possible_type?(type, ctx) type_name = type.is_a?(String) ? type : type.graphql_name !get_possible_type(type_name, ctx).nil? end def type_membership_class @type_membership_class || GraphQL::Schema::TypeMembership end end end graphql-ruby-1.11.10/lib/graphql/internal_representation.rb000066400000000000000000000005021414121453000237470ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/internal_representation/document" require "graphql/internal_representation/node" require "graphql/internal_representation/print" require "graphql/internal_representation/rewrite" require "graphql/internal_representation/scope" require "graphql/internal_representation/visit" graphql-ruby-1.11.10/lib/graphql/internal_representation/000077500000000000000000000000001414121453000234255ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/internal_representation/document.rb000066400000000000000000000013671414121453000255770ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module InternalRepresentation class Document # @return [Hash] Operation Nodes of this query attr_reader :operation_definitions # @return [Hash] Fragment definition Nodes of this query attr_reader :fragment_definitions def initialize @operation_definitions = {} @fragment_definitions = {} end def [](key) warn "#{self.class}#[] is deprecated; use `operation_definitions[]` instead" operation_definitions[key] end def each(&block) warn "#{self.class}#each is deprecated; use `operation_definitions.each` instead" operation_definitions.each(&block) end end end end graphql-ruby-1.11.10/lib/graphql/internal_representation/node.rb000066400000000000000000000151241414121453000247020ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module InternalRepresentation class Node # @api private DEFAULT_TYPED_CHILDREN = Proc.new { |h, k| h[k] = {} } # A specialized, reusable object for leaf nodes. NO_TYPED_CHILDREN = Hash.new({}.freeze) def NO_TYPED_CHILDREN.dup; self; end; NO_TYPED_CHILDREN.freeze # @return [String] the name this node has in the response attr_reader :name # @return [GraphQL::ObjectType] attr_reader :owner_type # Each key is a {GraphQL::ObjectType} which this selection _may_ be made on. # The values for that key are selections which apply to that type. # # This value is derived from {#scoped_children} after the rewrite is finished. # @return [Hash Node>>] def typed_children @typed_children ||= begin if @scoped_children.any? new_tc = Hash.new(&DEFAULT_TYPED_CHILDREN) all_object_types = Set.new scoped_children.each_key { |t| all_object_types.merge(@query.possible_types(t)) } # Remove any scoped children which don't follow this return type # (This can happen with fragment merging where lexical scope is lost) all_object_types &= @query.possible_types(@return_type.unwrap) all_object_types.each do |t| new_tc[t] = get_typed_children(t) end new_tc else NO_TYPED_CHILDREN end end end # These children correspond closely to scopes in the AST. # Keys _may_ be abstract types. They're assumed to be read-only after rewrite is finished # because {#typed_children} is derived from them. # # Using {#scoped_children} during the rewrite step reduces the overhead of reifying # abstract types because they're only reified _after_ the rewrite. # @return [Hash Node>>] attr_reader :scoped_children # @return [Array] AST nodes which are represented by this node attr_reader :ast_nodes # @return [Array] Field definitions for this node (there should only be one!) attr_reader :definitions # @return [GraphQL::BaseType] The expected wrapped type this node must return. attr_reader :return_type # @return [InternalRepresentation::Node, nil] attr_reader :parent def initialize( name:, owner_type:, query:, return_type:, parent:, ast_nodes: [], definitions: [] ) @name = name @query = query @owner_type = owner_type @parent = parent @typed_children = nil @scoped_children = Hash.new { |h1, k1| h1[k1] = {} } @ast_nodes = ast_nodes @definitions = definitions @return_type = return_type end def initialize_copy(other_node) super # Bust some caches: @typed_children = nil @definition = nil @definition_name = nil @ast_node = nil # Shallow-copy some state: @scoped_children = other_node.scoped_children.dup @ast_nodes = other_node.ast_nodes.dup @definitions = other_node.definitions.dup end def ==(other) other.is_a?(self.class) && other.name == name && other.parent == parent && other.return_type == return_type && other.owner_type == owner_type && other.scoped_children == scoped_children && other.definitions == definitions && other.ast_nodes == ast_nodes end def definition_name definition && definition.name end def arguments @query.arguments_for(self, definition) end def definition @definition ||= begin first_def = @definitions.first first_def && @query.get_field(@owner_type, first_def.name) end end def ast_node @ast_node ||= ast_nodes.first end def inspect all_children_names = scoped_children.values.map(&:keys).flatten.uniq.join(", ") all_locations = ast_nodes.map {|n| "#{n.line}:#{n.col}" }.join(", ") "# #{@return_type} {#{all_children_names}} @ [#{all_locations}] #{object_id}>" end # Merge selections from `new_parent` into `self`. # Selections are merged in place, not copied. def deep_merge_node(new_parent, scope: nil, merge_self: true) if merge_self @ast_nodes |= new_parent.ast_nodes @definitions |= new_parent.definitions end new_sc = new_parent.scoped_children if new_sc.any? scope ||= Scope.new(@query, @return_type.unwrap) new_sc.each do |obj_type, new_fields| inner_scope = scope.enter(obj_type) inner_scope.each do |scoped_type| prev_fields = @scoped_children[scoped_type] new_fields.each do |name, new_node| prev_node = prev_fields[name] if prev_node prev_node.deep_merge_node(new_node) else prev_fields[name] = new_node end end end end end end # @return [GraphQL::Query] attr_reader :query def subscription_topic @subscription_topic ||= begin scope = if definition.subscription_scope @query.context[definition.subscription_scope] else nil end Subscriptions::Event.serialize( definition_name, @query.arguments_for(self, definition), definition, scope: scope ) end end protected attr_writer :owner_type, :parent private # Get applicable children from {#scoped_children} # @param obj_type [GraphQL::ObjectType] # @return [Hash Node>] def get_typed_children(obj_type) new_tc = {} @scoped_children.each do |scope_type, scope_nodes| if GraphQL::Execution::Typecast.subtype?(scope_type, obj_type) scope_nodes.each do |name, new_node| prev_node = new_tc[name] if prev_node prev_node.deep_merge_node(new_node) else copied_node = new_node.dup copied_node.owner_type = obj_type copied_node.parent = self new_tc[name] = copied_node end end end end new_tc end end end end graphql-ruby-1.11.10/lib/graphql/internal_representation/print.rb000066400000000000000000000027671414121453000251220ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module InternalRepresentation module Print module_function def print(schema, query_string) query = GraphQL::Query.new(schema, query_string) print_node(query.irep_selection) end def print_node(node, indent: 0) padding = " " * indent typed_children_padding = " " * (indent + 2) query_str = "".dup if !node.definition op_node = node.ast_node name = op_node.name ? " " + op_node.name : "" op_type = op_node.operation_type query_str << "#{op_type}#{name}" else if node.name == node.definition_name query_str << "#{padding}#{node.name}" else query_str << "#{padding}#{node.name}: #{node.definition_name}" end args = node.ast_nodes.map { |n| n.arguments.map(&:to_query_string).join(",") }.uniq query_str << args.map { |a| "(#{a})"}.join("|") end if node.typed_children.any? query_str << " {\n" node.typed_children.each do |type, children| query_str << "#{typed_children_padding}... on #{type.name} {\n" children.each do |name, child| query_str << print_node(child, indent: indent + 4) end query_str << "#{typed_children_padding}}\n" end query_str << "#{padding}}\n" else query_str << "\n" end query_str end end end end graphql-ruby-1.11.10/lib/graphql/internal_representation/rewrite.rb000066400000000000000000000144171414121453000254420ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module InternalRepresentation # While visiting an AST, build a normalized, flattened tree of {InternalRepresentation::Node}s. # # No unions or interfaces are present in this tree, only object types. # # Selections from the AST are attached to the object types they apply to. # # Inline fragments and fragment spreads are preserved in {InternalRepresentation::Node#ast_spreads}, # where they can be used to check for the presence of directives. This might not be sufficient # for future directives, since the selections' grouping is lost. # # The rewritten query tree serves as the basis for the `FieldsWillMerge` validation. # module Rewrite include GraphQL::Language NO_DIRECTIVES = [].freeze # @return InternalRepresentation::Document attr_reader :rewrite_document def initialize(*) super @query = context.query @rewrite_document = InternalRepresentation::Document.new # Hash Set> # A record of fragment spreads and the irep nodes that used them @rewrite_spread_parents = Hash.new { |h, k| h[k] = Set.new } # Hash Scope> @rewrite_spread_scopes = {} # Array> # The current point of the irep_tree during visitation @rewrite_nodes_stack = [] # Array @rewrite_scopes_stack = [] @rewrite_skip_nodes = Set.new # Resolve fragment spreads. # Fragment definitions got their own irep trees during visitation. # Those nodes are spliced in verbatim (not copied), but this is OK # because fragments are resolved from the "bottom up", each fragment # can be shared between its usages. context.on_dependency_resolve do |defn_ast_node, spread_ast_nodes, frag_ast_node| frag_name = frag_ast_node.name fragment_node = @rewrite_document.fragment_definitions[frag_name] if fragment_node spread_ast_nodes.each do |spread_ast_node| parent_nodes = @rewrite_spread_parents[spread_ast_node] parent_scope = @rewrite_spread_scopes[spread_ast_node] parent_nodes.each do |parent_node| parent_node.deep_merge_node(fragment_node, scope: parent_scope, merge_self: false) end end end end end # @return [Hash] Roots of this query def operations warn "#{self.class}#operations is deprecated; use `document.operation_definitions` instead" @document.operation_definitions end def on_operation_definition(ast_node, parent) push_root_node(ast_node, @rewrite_document.operation_definitions) { super } end def on_fragment_definition(ast_node, parent) push_root_node(ast_node, @rewrite_document.fragment_definitions) { super } end def push_root_node(ast_node, definitions) # Either QueryType or the fragment type condition owner_type = context.type_definition defn_name = ast_node.name node = Node.new( parent: nil, name: defn_name, owner_type: owner_type, query: @query, ast_nodes: [ast_node], return_type: owner_type, ) definitions[defn_name] = node @rewrite_scopes_stack.push(Scope.new(@query, owner_type)) @rewrite_nodes_stack.push([node]) yield @rewrite_nodes_stack.pop @rewrite_scopes_stack.pop end def on_inline_fragment(node, parent) # Inline fragments provide two things to the rewritten tree: # - They _may_ narrow the scope by their type condition # - They _may_ apply their directives to their children if skip?(node) @rewrite_skip_nodes.add(node) end if @rewrite_skip_nodes.empty? @rewrite_scopes_stack.push(@rewrite_scopes_stack.last.enter(context.type_definition)) end super if @rewrite_skip_nodes.empty? @rewrite_scopes_stack.pop end if @rewrite_skip_nodes.include?(node) @rewrite_skip_nodes.delete(node) end end def on_field(ast_node, ast_parent) if skip?(ast_node) @rewrite_skip_nodes.add(ast_node) end if @rewrite_skip_nodes.empty? node_name = ast_node.alias || ast_node.name parent_nodes = @rewrite_nodes_stack.last next_nodes = [] field_defn = context.field_definition if field_defn.nil? # It's a non-existent field new_scope = nil else field_return_type = field_defn.type @rewrite_scopes_stack.last.each do |scope_type| parent_nodes.each do |parent_node| node = parent_node.scoped_children[scope_type][node_name] ||= Node.new( parent: parent_node, name: node_name, owner_type: scope_type, query: @query, return_type: field_return_type, ) node.ast_nodes << ast_node node.definitions << field_defn next_nodes << node end end new_scope = Scope.new(@query, field_return_type.unwrap) end @rewrite_nodes_stack.push(next_nodes) @rewrite_scopes_stack.push(new_scope) end super if @rewrite_skip_nodes.empty? @rewrite_nodes_stack.pop @rewrite_scopes_stack.pop end if @rewrite_skip_nodes.include?(ast_node) @rewrite_skip_nodes.delete(ast_node) end end def on_fragment_spread(ast_node, ast_parent) if @rewrite_skip_nodes.empty? && !skip?(ast_node) # Register the irep nodes that depend on this AST node: @rewrite_spread_parents[ast_node].merge(@rewrite_nodes_stack.last) @rewrite_spread_scopes[ast_node] = @rewrite_scopes_stack.last end super end def skip?(ast_node) dir = ast_node.directives dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, @query) end end end end graphql-ruby-1.11.10/lib/graphql/internal_representation/scope.rb000066400000000000000000000052231414121453000250650ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module InternalRepresentation # At a point in the AST, selections may apply to one or more types. # {Scope} represents those types which selections may apply to. # # Scopes can be defined by: # # - A single concrete or abstract type # - An array of types # - `nil` # # The AST may be scoped to an array of types when two abstractly-typed # fragments occur in inside one another. class Scope NO_TYPES = [].freeze # @param query [GraphQL::Query] # @param type_defn [GraphQL::BaseType, Array, nil] def initialize(query, type_defn) @query = query @type = type_defn @abstract_type = false @types = case type_defn when Array type_defn when GraphQL::BaseType @abstract_type = true nil when nil NO_TYPES else raise "Unexpected scope type: #{type_defn}" end end # From a starting point of `self`, create a new scope by condition `other_type_defn`. # @param other_type_defn [GraphQL::BaseType, nil] # @return [Scope] def enter(other_type_defn) case other_type_defn when nil # The type wasn't found, who cares Scope.new(@query, nil) when @type # The condition is the same as current, so reuse self self when GraphQL::UnionType, GraphQL::InterfaceType # Make a new scope of the intersection between the previous & next conditions new_types = @query.possible_types(other_type_defn) & concrete_types Scope.new(@query, new_types) when GraphQL::BaseType # If this type is valid within the current scope, # return a new scope of _exactly_ this type. # Otherwise, this type is out-of-scope so the scope is null. if concrete_types.include?(other_type_defn) Scope.new(@query, other_type_defn) else Scope.new(@query, nil) end else raise "Unexpected scope: #{other_type_defn.inspect}" end end # Call the block for each type in `self`. # This uses the simplest possible expression of `self`, # so if this scope is defined by an abstract type, it gets yielded. def each(&block) if @abstract_type yield(@type) else @types.each(&block) end end private def concrete_types @concrete_types ||= if @abstract_type @query.possible_types(@type) else @types end end end end end graphql-ruby-1.11.10/lib/graphql/internal_representation/visit.rb000066400000000000000000000021701414121453000251100ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module InternalRepresentation # Traverse a re-written query tree, calling handlers for each node module Visit module_function def visit_each_node(operations, handlers) return if handlers.empty? # Post-validation: make some assertions about the rewritten query tree operations.each do |op_name, op_node| # Yield each node to listeners which were attached by validators op_node.typed_children.each do |obj_type, children| children.each do |name, op_child_node| each_node(op_child_node) do |node| for h in handlers h.call(node) end end end end end end # Traverse a node in a rewritten query tree, # visiting the node itself and each of its typed children. def each_node(node, &block) yield(node) node.typed_children.each do |obj_type, children| children.each do |name, node| each_node(node, &block) end end end end end end graphql-ruby-1.11.10/lib/graphql/introspection.rb000066400000000000000000000045341414121453000217220ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection def self.query(include_deprecated_args: false) # The introspection query to end all introspection queries, copied from # https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js <<-QUERY query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } } } fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args#{include_deprecated_args ? '(includeDeprecated: true)' : ''} { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields#{include_deprecated_args ? '(includeDeprecated: true)' : ''} { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue #{include_deprecated_args ? 'isDeprecated' : ''} #{include_deprecated_args ? 'deprecationReason' : ''} } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } } QUERY end end end require "graphql/introspection/base_object" require "graphql/introspection/input_value_type" require "graphql/introspection/enum_value_type" require "graphql/introspection/type_kind_enum" require "graphql/introspection/type_type" require "graphql/introspection/field_type" require "graphql/introspection/directive_location_enum" require "graphql/introspection/directive_type" require "graphql/introspection/schema_type" require "graphql/introspection/introspection_query" require "graphql/introspection/dynamic_fields" require "graphql/introspection/entry_points" graphql-ruby-1.11.10/lib/graphql/introspection/000077500000000000000000000000001414121453000213675ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/introspection/base_object.rb000066400000000000000000000004451414121453000241570ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class BaseObject < GraphQL::Schema::Object introspection(true) def self.field(*args, **kwargs, &block) kwargs[:introspection] = true super(*args, **kwargs, &block) end end end end graphql-ruby-1.11.10/lib/graphql/introspection/directive_location_enum.rb000066400000000000000000000010411414121453000266020ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class DirectiveLocationEnum < GraphQL::Schema::Enum graphql_name "__DirectiveLocation" description "A Directive can be adjacent to many parts of the GraphQL language, "\ "a __DirectiveLocation describes one such possible adjacencies." GraphQL::Directive::LOCATIONS.each do |location| value(location.to_s, GraphQL::Directive::LOCATION_DESCRIPTIONS[location], value: location) end introspection true end end end graphql-ruby-1.11.10/lib/graphql/introspection/directive_type.rb000066400000000000000000000025231414121453000247350ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class DirectiveType < Introspection::BaseObject graphql_name "__Directive" description "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document."\ "\n\n"\ "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." field :name, String, null: false, method: :graphql_name field :description, String, null: true field :locations, [GraphQL::Schema::LateBoundType.new("__DirectiveLocation")], null: false field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false field :on_operation, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_operation? field :on_fragment, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_fragment? field :on_field, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_field? def args @context.warden.arguments(@object) end end end end graphql-ruby-1.11.10/lib/graphql/introspection/dynamic_fields.rb000066400000000000000000000007521414121453000246720ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class DynamicFields < Introspection::BaseObject field :__typename, String, "The name of this type", null: false, extras: [:irep_node] # `irep_node:` will be nil for the interpreter, since there is no such thing def __typename(irep_node: nil) if context.interpreter? object.class.graphql_name else irep_node.owner_type.name end end end end end graphql-ruby-1.11.10/lib/graphql/introspection/entry_points.rb000066400000000000000000000025741414121453000244610ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class EntryPoints < Introspection::BaseObject field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system", null: true do argument :name, String, required: true end def __schema # Apply wrapping manually since this field isn't wrapped by instrumentation schema = @context.query.schema schema_type = schema.introspection_system.types["__Schema"] schema_type.type_class.authorized_new(schema, @context) end def __type(name:) return unless context.warden.reachable_type?(name) type = context.warden.get_type(name) if type && context.interpreter? && !type.is_a?(Module) type = type.type_class || raise("Invariant: interpreter requires class-based type for #{name}") end # The interpreter provides this wrapping, other execution doesnt, so support both. if type && !context.interpreter? # Apply wrapping manually since this field isn't wrapped by instrumentation type_type = context.schema.introspection_system.types["__Type"] type = type_type.type_class.authorized_new(type, context) end type end end end end graphql-ruby-1.11.10/lib/graphql/introspection/enum_value_type.rb000066400000000000000000000013311414121453000251130ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class EnumValueType < Introspection::BaseObject graphql_name "__EnumValue" description "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." field :name, String, null: false field :description, String, null: true field :is_deprecated, Boolean, null: false field :deprecation_reason, String, null: true def name object.graphql_name end def is_deprecated !!@object.deprecation_reason end end end end graphql-ruby-1.11.10/lib/graphql/introspection/field_type.rb000066400000000000000000000020271414121453000240410ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class FieldType < Introspection::BaseObject graphql_name "__Field" description "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." field :name, String, null: false field :description, String, null: true field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false do argument :include_deprecated, Boolean, required: false, default_value: false end field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false field :is_deprecated, Boolean, null: false field :deprecation_reason, String, null: true def is_deprecated !!@object.deprecation_reason end def args(include_deprecated:) args = @context.warden.arguments(@object) args = args.reject(&:deprecation_reason) unless include_deprecated args end end end end graphql-ruby-1.11.10/lib/graphql/introspection/input_value_type.rb000066400000000000000000000037651414121453000253230ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class InputValueType < Introspection::BaseObject graphql_name "__InputValue" description "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." field :name, String, null: false field :description, String, null: true field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false field :default_value, String, "A GraphQL-formatted string representing the default value for this input value.", null: true field :is_deprecated, Boolean, null: false field :deprecation_reason, String, null: true def is_deprecated !!@object.deprecation_reason end def default_value if @object.default_value? value = @object.default_value if value.nil? 'null' else coerced_default_value = @object.type.coerce_result(value, @context) serialize_default_value(coerced_default_value, @object.type) end else nil end end private # Recursively serialize, taking care not to add quotes to enum values def serialize_default_value(value, type) if value.nil? 'null' elsif type.kind.list? inner_type = type.of_type "[" + value.map { |v| serialize_default_value(v, inner_type) }.join(", ") + "]" elsif type.kind.non_null? serialize_default_value(value, type.of_type) elsif type.kind.enum? value elsif type.kind.input_object? "{" + value.map do |k, v| arg_defn = type.arguments[k] "#{k}: #{serialize_default_value(v, arg_defn.type)}" end.join(", ") + "}" else GraphQL::Language.serialize(value) end end end end end graphql-ruby-1.11.10/lib/graphql/introspection/introspection_query.rb000066400000000000000000000005031414121453000260370ustar00rootroot00000000000000# frozen_string_literal: true # This query is used by graphql-client so don't add the includeDeprecated # argument for inputFields since the server may not support it. Two stage # introspection queries will be required to handle this in clients. GraphQL::Introspection::INTROSPECTION_QUERY = GraphQL::Introspection.query graphql-ruby-1.11.10/lib/graphql/introspection/schema_type.rb000066400000000000000000000032641414121453000242220ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class SchemaType < Introspection::BaseObject graphql_name "__Schema" description "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." field :types, [GraphQL::Schema::LateBoundType.new("__Type")], "A list of all types supported by this server.", null: false field :query_type, GraphQL::Schema::LateBoundType.new("__Type"), "The type that query operations will be rooted at.", null: false field :mutation_type, GraphQL::Schema::LateBoundType.new("__Type"), "If this server supports mutation, the type that mutation operations will be rooted at.", null: true field :subscription_type, GraphQL::Schema::LateBoundType.new("__Type"), "If this server support subscription, the type that subscription operations will be rooted at.", null: true field :directives, [GraphQL::Schema::LateBoundType.new("__Directive")], "A list of all directives supported by this server.", null: false def types @context.warden.reachable_types.sort_by(&:graphql_name) end def query_type permitted_root_type("query") end def mutation_type permitted_root_type("mutation") end def subscription_type permitted_root_type("subscription") end def directives context.schema.directives.values end private def permitted_root_type(op_type) @context.warden.root_type_for_operation(op_type) end end end end graphql-ruby-1.11.10/lib/graphql/introspection/type_kind_enum.rb000066400000000000000000000006041414121453000247260ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class TypeKindEnum < GraphQL::Schema::Enum graphql_name "__TypeKind" description "An enum describing what kind of type a given `__Type` is." GraphQL::TypeKinds::TYPE_KINDS.each do |type_kind| value(type_kind.name, type_kind.description) end introspection true end end end graphql-ruby-1.11.10/lib/graphql/introspection/type_type.rb000066400000000000000000000063071414121453000237440ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Introspection class TypeType < Introspection::BaseObject graphql_name "__Type" description "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in "\ "GraphQL as represented by the `__TypeKind` enum.\n\n"\ "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." field :kind, GraphQL::Schema::LateBoundType.new("__TypeKind"), null: false field :name, String, null: true field :description, String, null: true field :fields, [GraphQL::Schema::LateBoundType.new("__Field")], null: true do argument :include_deprecated, Boolean, required: false, default_value: false end field :interfaces, [GraphQL::Schema::LateBoundType.new("__Type")], null: true field :possible_types, [GraphQL::Schema::LateBoundType.new("__Type")], null: true field :enum_values, [GraphQL::Schema::LateBoundType.new("__EnumValue")], null: true do argument :include_deprecated, Boolean, required: false, default_value: false end field :input_fields, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: true do argument :include_deprecated, Boolean, required: false, default_value: false end field :of_type, GraphQL::Schema::LateBoundType.new("__Type"), null: true def name object.graphql_name end def kind @object.kind.name end def enum_values(include_deprecated:) if !@object.kind.enum? nil else enum_values = @context.warden.enum_values(@object) if !include_deprecated enum_values = enum_values.select {|f| !f.deprecation_reason } end enum_values end end def interfaces if @object.kind == GraphQL::TypeKinds::OBJECT @context.warden.interfaces(@object) else nil end end def input_fields(include_deprecated:) if @object.kind.input_object? args = @context.warden.arguments(@object) args = args.reject(&:deprecation_reason) unless include_deprecated args else nil end end def possible_types if @object.kind.abstract? @context.warden.possible_types(@object).sort_by(&:graphql_name) else nil end end def fields(include_deprecated:) if !@object.kind.fields? nil else fields = @context.warden.fields(@object) if !include_deprecated fields = fields.select {|f| !f.deprecation_reason } end fields.sort_by(&:name) end end def of_type @object.kind.wraps? ? @object.of_type : nil end end end end graphql-ruby-1.11.10/lib/graphql/invalid_name_error.rb000066400000000000000000000005001414121453000226460ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class InvalidNameError < GraphQL::ExecutionError attr_reader :name, :valid_regex def initialize(name, valid_regex) @name = name @valid_regex = valid_regex super("Names must match #{@valid_regex.inspect} but '#{@name}' does not") end end end graphql-ruby-1.11.10/lib/graphql/invalid_null_error.rb000066400000000000000000000025661414121453000227160ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Raised automatically when a field's resolve function returns `nil` # for a non-null field. class InvalidNullError < GraphQL::RuntimeTypeError # @return [GraphQL::BaseType] The owner of {#field} attr_reader :parent_type # @return [GraphQL::Field] The field which failed to return a value attr_reader :field # @return [nil, GraphQL::ExecutionError] The invalid value for this field attr_reader :value def initialize(parent_type, field, value) @parent_type = parent_type @field = field @value = value super("Cannot return null for non-nullable field #{@parent_type.graphql_name}.#{@field.graphql_name}") end # @return [Hash] An entry for the response's "errors" key def to_h { "message" => message } end # @deprecated always false def parent_error? false end class << self attr_accessor :parent_class def subclass_for(parent_class) subclass = Class.new(self) subclass.parent_class = parent_class subclass end def inspect if (name.nil? || parent_class.name.nil?) && parent_class.respond_to?(:mutation) && (mutation = parent_class.mutation) "#{mutation.inspect}::#{parent_class.graphql_name}::InvalidNullError" else super end end end end end graphql-ruby-1.11.10/lib/graphql/language.rb000066400000000000000000000016541414121453000206050ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/language/block_string" require "graphql/language/printer" require "graphql/language/sanitized_printer" require "graphql/language/document_from_schema_definition" require "graphql/language/generation" require "graphql/language/lexer" require "graphql/language/nodes" require "graphql/language/parser" require "graphql/language/token" require "graphql/language/visitor" require "graphql/language/definition_slice" module GraphQL module Language # @api private def self.serialize(value) if value.is_a?(Hash) serialized_hash = value.map do |k, v| "#{k}:#{serialize v}" end.join(",") "{#{serialized_hash}}" elsif value.is_a?(Array) serialized_array = value.map do |v| serialize v end.join(",") "[#{serialized_array}]" else JSON.generate(value, quirks_mode: true) end end end end graphql-ruby-1.11.10/lib/graphql/language/000077500000000000000000000000001414121453000202525ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/language/block_string.rb000066400000000000000000000053151414121453000232630ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language module BlockString if !String.method_defined?(:match?) using GraphQL::StringMatchBackport end # Remove leading and trailing whitespace from a block string. # See "Block Strings" in https://github.com/facebook/graphql/blob/master/spec/Section%202%20--%20Language.md def self.trim_whitespace(str) # Early return for the most common cases: if str == "" return "" elsif !(has_newline = str.include?("\n")) && !(str.start_with?(" ")) return str end lines = has_newline ? str.split("\n") : [str] common_indent = nil # find the common whitespace lines.each_with_index do |line, idx| if idx == 0 next end line_length = line.size line_indent = if line.match?(/\A [^ ]/) 2 elsif line.match?(/\A [^ ]/) 4 elsif line.match?(/\A[^ ]/) 0 else line[/\A */].size end if line_indent < line_length && (common_indent.nil? || line_indent < common_indent) common_indent = line_indent end end # Remove the common whitespace if common_indent && common_indent > 0 lines.each_with_index do |line, idx| if idx == 0 next else line.slice!(0, common_indent) end end end # Remove leading & trailing blank lines while lines.size > 0 && lines[0].empty? lines.shift end while lines.size > 0 && lines[-1].empty? lines.pop end # Rebuild the string lines.size > 1 ? lines.join("\n") : (lines.first || "") end def self.print(str, indent: '') lines = str.split("\n") block_str = "#{indent}\"\"\"\n".dup lines.each do |line| if line == '' block_str << "\n" else sublines = break_line(line, 120 - indent.length) sublines.each do |subline| block_str << "#{indent}#{subline}\n" end end end block_str << "#{indent}\"\"\"\n".dup end private def self.break_line(line, length) return [line] if line.length < length + 5 parts = line.split(Regexp.new("((?: |^).{15,#{length - 40}}(?= |$))")) return [line] if parts.length < 4 sublines = [parts.slice!(0, 3).join] parts.each_with_index do |part, i| next if i % 2 == 1 sublines << "#{part[1..-1]}#{parts[i + 1]}" end sublines end end end end graphql-ruby-1.11.10/lib/graphql/language/definition_slice.rb000066400000000000000000000022221414121453000241040ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language module DefinitionSlice extend self def slice(document, name) definitions = {} document.definitions.each { |d| definitions[d.name] = d } names = Set.new DependencyVisitor.find_definition_dependencies(definitions, name, names) definitions = document.definitions.select { |d| names.include?(d.name) } Nodes::Document.new(definitions: definitions) end private class DependencyVisitor < GraphQL::Language::Visitor def initialize(doc, definitions, names) @names = names @definitions = definitions super(doc) end def on_fragment_spread(node, parent) if fragment = @definitions[node.name] self.class.find_definition_dependencies(@definitions, fragment.name, @names) end super end def self.find_definition_dependencies(definitions, name, names) names.add(name) visitor = self.new(definitions[name], definitions, names) visitor.visit nil end end end end end graphql-ruby-1.11.10/lib/graphql/language/document_from_schema_definition.rb000066400000000000000000000260021414121453000271700ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language # @api private # # {GraphQL::Language::DocumentFromSchemaDefinition} is used to convert a {GraphQL::Schema} object # To a {GraphQL::Language::Document} AST node. # # @param context [Hash] # @param only [<#call(member, ctx)>] # @param except [<#call(member, ctx)>] # @param include_introspection_types [Boolean] Whether or not to include introspection types in the AST # @param include_built_in_scalars [Boolean] Whether or not to include built in scalars in the AST # @param include_built_in_directives [Boolean] Whether or not to include built in directives in the AST class DocumentFromSchemaDefinition def initialize( schema, context: nil, only: nil, except: nil, include_introspection_types: false, include_built_in_directives: false, include_built_in_scalars: false, always_include_schema: false ) @schema = schema @always_include_schema = always_include_schema @include_introspection_types = include_introspection_types @include_built_in_scalars = include_built_in_scalars @include_built_in_directives = include_built_in_directives filter = GraphQL::Filter.new(only: only, except: except) if @schema.respond_to?(:visible?) filter = filter.merge(only: @schema.method(:visible?)) end schema_context = schema.context_class.new(query: nil, object: nil, schema: schema, values: context) @warden = GraphQL::Schema::Warden.new( filter, schema: @schema, context: schema_context, ) end def document GraphQL::Language::Nodes::Document.new( definitions: build_definition_nodes ) end def build_schema_node GraphQL::Language::Nodes::SchemaDefinition.new( query: (q = warden.root_type_for_operation("query")) && q.graphql_name, mutation: (m = warden.root_type_for_operation("mutation")) && m.graphql_name, subscription: (s = warden.root_type_for_operation("subscription")) && s.graphql_name, # This only supports directives from parsing, # use a custom printer to add to this list. directives: ast_directives(@schema), ) end def build_object_type_node(object_type) GraphQL::Language::Nodes::ObjectTypeDefinition.new( name: object_type.graphql_name, interfaces: warden.interfaces(object_type).sort_by(&:graphql_name).map { |iface| build_type_name_node(iface) }, fields: build_field_nodes(warden.fields(object_type)), description: object_type.description, directives: ast_directives(object_type), ) end def build_field_node(field) GraphQL::Language::Nodes::FieldDefinition.new( name: field.graphql_name, arguments: build_argument_nodes(warden.arguments(field)), type: build_type_name_node(field.type), description: field.description, directives: ast_directives(field), ) end def build_union_type_node(union_type) GraphQL::Language::Nodes::UnionTypeDefinition.new( name: union_type.graphql_name, description: union_type.description, types: warden.possible_types(union_type).sort_by(&:graphql_name).map { |type| build_type_name_node(type) }, directives: ast_directives(union_type), ) end def build_interface_type_node(interface_type) GraphQL::Language::Nodes::InterfaceTypeDefinition.new( name: interface_type.graphql_name, description: interface_type.description, fields: build_field_nodes(warden.fields(interface_type)), directives: ast_directives(interface_type), ) end def build_enum_type_node(enum_type) GraphQL::Language::Nodes::EnumTypeDefinition.new( name: enum_type.graphql_name, values: warden.enum_values(enum_type).sort_by(&:graphql_name).map do |enum_value| build_enum_value_node(enum_value) end, description: enum_type.description, directives: ast_directives(enum_type), ) end def build_enum_value_node(enum_value) GraphQL::Language::Nodes::EnumValueDefinition.new( name: enum_value.graphql_name, description: enum_value.description, directives: ast_directives(enum_value), ) end def build_scalar_type_node(scalar_type) GraphQL::Language::Nodes::ScalarTypeDefinition.new( name: scalar_type.graphql_name, description: scalar_type.description, directives: ast_directives(scalar_type), ) end def build_argument_node(argument) if argument.default_value? default_value = build_default_value(argument.default_value, argument.type) else default_value = nil end argument_node = GraphQL::Language::Nodes::InputValueDefinition.new( name: argument.graphql_name, description: argument.description, type: build_type_name_node(argument.type), default_value: default_value, directives: ast_directives(argument), ) argument_node end def build_input_object_node(input_object) GraphQL::Language::Nodes::InputObjectTypeDefinition.new( name: input_object.graphql_name, fields: build_argument_nodes(warden.arguments(input_object)), description: input_object.description, directives: ast_directives(input_object), ) end def build_directive_node(directive) GraphQL::Language::Nodes::DirectiveDefinition.new( name: directive.graphql_name, arguments: build_argument_nodes(warden.arguments(directive)), locations: build_directive_location_nodes(directive.locations), description: directive.description, ) end def build_directive_location_nodes(locations) locations.map { |location| build_directive_location_node(location) } end def build_directive_location_node(location) GraphQL::Language::Nodes::DirectiveLocation.new( name: location.to_s ) end def build_type_name_node(type) case type.kind.name when "LIST" GraphQL::Language::Nodes::ListType.new( of_type: build_type_name_node(type.of_type) ) when "NON_NULL" GraphQL::Language::Nodes::NonNullType.new( of_type: build_type_name_node(type.of_type) ) else GraphQL::Language::Nodes::TypeName.new(name: type.graphql_name) end end def build_default_value(default_value, type) if default_value.nil? return GraphQL::Language::Nodes::NullValue.new(name: "null") end case type.kind.name when "SCALAR" type.coerce_isolated_result(default_value) when "ENUM" GraphQL::Language::Nodes::Enum.new(name: type.coerce_isolated_result(default_value)) when "INPUT_OBJECT" GraphQL::Language::Nodes::InputObject.new( arguments: default_value.to_h.map do |arg_name, arg_value| arg_type = type.arguments.fetch(arg_name.to_s).type GraphQL::Language::Nodes::Argument.new( name: arg_name.to_s, value: build_default_value(arg_value, arg_type) ) end ) when "NON_NULL" build_default_value(default_value, type.of_type) when "LIST" default_value.to_a.map { |v| build_default_value(v, type.of_type) } else raise GraphQL::RequiredImplementationMissingError, "Unexpected default value type #{type.inspect}" end end def build_type_definition_node(type) case type.kind.name when "OBJECT" build_object_type_node(type) when "UNION" build_union_type_node(type) when "INTERFACE" build_interface_type_node(type) when "SCALAR" build_scalar_type_node(type) when "ENUM" build_enum_type_node(type) when "INPUT_OBJECT" build_input_object_node(type) else raise TypeError end end def build_argument_nodes(arguments) arguments .map { |arg| build_argument_node(arg) } .sort_by(&:name) end def build_directive_nodes(directives) if !include_built_in_directives directives = directives.reject { |directive| directive.default_directive? } end directives .map { |directive| build_directive_node(directive) } .sort_by(&:name) end def build_definition_nodes definitions = [] definitions << build_schema_node if include_schema_node? definitions += build_directive_nodes(warden.directives) definitions += build_type_definition_nodes(warden.reachable_types) definitions end def build_type_definition_nodes(types) if !include_introspection_types types = types.reject { |type| type.introspection? } end if !include_built_in_scalars types = types.reject { |type| type.kind.scalar? && type.default_scalar? } end types .map { |type| build_type_definition_node(type) } .sort_by(&:name) end def build_field_nodes(fields) fields .map { |field| build_field_node(field) } .sort_by(&:name) end private def include_schema_node? always_include_schema || !schema_respects_root_name_conventions?(schema) end def schema_respects_root_name_conventions?(schema) (schema.query.nil? || schema.query.graphql_name == 'Query') && (schema.mutation.nil? || schema.mutation.graphql_name == 'Mutation') && (schema.subscription.nil? || schema.subscription.graphql_name == 'Subscription') end def ast_directives(member) ast_directives = member.ast_node ? member.ast_node.directives : [] # If this schema was built from IDL, it will already have `@deprecated` in `ast_node.directives` if member.respond_to?(:deprecation_reason) && (reason = member.deprecation_reason) && ast_directives.none? { |d| d.name == "deprecated" } arguments = [] if reason != GraphQL::Schema::Directive::DEFAULT_DEPRECATION_REASON arguments << GraphQL::Language::Nodes::Argument.new( name: "reason", value: reason ) end ast_directives += [ GraphQL::Language::Nodes::Directive.new( name: GraphQL::Directive::DeprecatedDirective.graphql_name, arguments: arguments ) ] end ast_directives end attr_reader :schema, :warden, :always_include_schema, :include_introspection_types, :include_built_in_directives, :include_built_in_scalars end end end graphql-ruby-1.11.10/lib/graphql/language/generation.rb000066400000000000000000000016461414121453000227410ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language # Exposes {.generate}, which turns AST nodes back into query strings. module Generation extend self # Turn an AST node back into a string. # # @example Turning a document into a query # document = GraphQL.parse(query_string) # GraphQL::Language::Generation.generate(document) # # => "{ ... }" # # @param node [GraphQL::Language::Nodes::AbstractNode] an AST node to recursively stringify # @param indent [String] Whitespace to add to each printed node # @param printer [GraphQL::Language::Printer] An optional custom printer for printing AST nodes. Defaults to GraphQL::Language::Printer # @return [String] Valid GraphQL for `node` def generate(node, indent: "", printer: GraphQL::Language::Printer.new) printer.print(node, indent: indent) end end end end graphql-ruby-1.11.10/lib/graphql/language/lexer.rb000066400000000000000000001263231414121453000217250ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language module Lexer if !String.method_defined?(:match?) using GraphQL::StringMatchBackport end def self.tokenize(query_string) run_lexer(query_string) end # Replace any escaped unicode or whitespace with the _actual_ characters # To avoid allocating more strings, this modifies the string passed into it def self.replace_escaped_characters_in_place(raw_string) raw_string.gsub!(ESCAPES, ESCAPES_REPLACE) raw_string.gsub!(UTF_8, &UTF_8_REPLACE) nil end private class << self attr_accessor :_graphql_lexer_trans_keys private :_graphql_lexer_trans_keys, :_graphql_lexer_trans_keys= end self._graphql_lexer_trans_keys = [ 1, 0, 4, 22, 4, 43, 14, 46, 14, 46, 14, 46, 14, 46, 4, 22, 4, 4, 4, 4, 4, 22, 4, 4, 4, 4, 14, 15, 14, 15, 10, 15, 12, 12, 4, 22, 4, 43, 14, 46, 14, 46, 14, 46, 14, 46, 0, 49, 0, 0, 4, 22, 4, 4, 4, 4, 4, 4, 4, 22, 4, 4, 4, 4, 1, 1, 14, 15, 10, 29, 14, 15, 10, 29, 10, 29, 12, 12, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 14, 46, 4, 4, 0 , ] class << self attr_accessor :_graphql_lexer_char_class private :_graphql_lexer_char_class, :_graphql_lexer_char_class= end self._graphql_lexer_char_class = [ 0, 1, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 3, 4, 5, 6, 2, 7, 2, 8, 9, 2, 10, 0, 11, 12, 13, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 2, 2, 17, 2, 2, 18, 19, 19, 19, 19, 20, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 21, 22, 23, 2, 24, 2, 25, 26, 27, 28, 29, 30, 31, 32, 33, 19, 19, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 19, 45, 46, 19, 47, 48, 49, 0 , ] class << self attr_accessor :_graphql_lexer_index_offsets private :_graphql_lexer_index_offsets, :_graphql_lexer_index_offsets= end self._graphql_lexer_index_offsets = [ 0, 0, 19, 59, 92, 125, 158, 191, 210, 211, 212, 231, 232, 233, 235, 237, 243, 244, 263, 303, 336, 369, 402, 435, 485, 486, 505, 506, 507, 508, 527, 528, 529, 530, 532, 552, 554, 574, 594, 595, 628, 661, 694, 727, 760, 793, 826, 859, 892, 925, 958, 991, 1024, 1057, 1090, 1123, 1156, 1189, 1222, 1255, 1288, 1321, 1354, 1387, 1420, 1453, 1486, 1519, 1552, 1585, 1618, 1651, 1684, 1717, 1750, 1783, 1816, 1849, 1882, 1915, 1948, 1981, 2014, 2047, 2080, 2113, 2146, 2179, 2212, 2245, 2278, 2311, 2344, 2377, 2410, 2443, 2476, 2509, 2542, 2575, 2608, 2641, 2674, 2707, 2740, 2773, 2806, 2839, 2872, 2905, 2938, 2971, 3004, 3037, 3070, 3103, 3136, 3169, 3202, 3235, 3268, 3301, 3334, 3367, 3400, 3433, 0 , ] class << self attr_accessor :_graphql_lexer_indicies private :_graphql_lexer_indicies, :_graphql_lexer_indicies= end self._graphql_lexer_indicies = [ 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 4, 5, 5, 0, 0, 0, 5, 5, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 0, 0, 0, 6, 6, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 0, 0, 0, 7, 7, 0, 0, 0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 11, 12, 13, 14, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 11, 15, 16, 17, 17, 19, 19, 20, 20, 8, 8, 17, 17, 21, 23, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 24, 22, 25, 25, 25, 25, 25, 25, 25, 25, 22, 25, 25, 25, 25, 25, 25, 25, 25, 22, 25, 25, 25, 22, 25, 25, 25, 22, 25, 25, 25, 25, 25, 22, 25, 25, 25, 22, 25, 22, 26, 27, 27, 25, 25, 25, 27, 27, 25, 25, 25, 25, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 25, 25, 25, 28, 28, 25, 25, 25, 25, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 25, 25, 25, 29, 29, 25, 25, 25, 25, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 22, 22, 25, 25, 25, 22, 22, 25, 25, 25, 25, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 31, 32, 30, 33, 34, 35, 36, 37, 38, 39, 30, 40, 41, 30, 42, 43, 44, 45, 46, 47, 47, 48, 30, 49, 47, 47, 47, 47, 50, 51, 52, 47, 47, 53, 47, 54, 55, 56, 47, 57, 47, 58, 59, 60, 47, 47, 47, 61, 62, 63, 31, 66, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 9, 69, 70, 71, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 11, 72, 13, 73, 42, 43, 20, 20, 75, 74, 17, 17, 74, 74, 74, 74, 76, 74, 74, 74, 74, 74, 74, 74, 74, 76, 17, 17, 20, 20, 77, 77, 19, 19, 77, 77, 77, 77, 76, 77, 77, 77, 77, 77, 77, 77, 77, 76, 20, 20, 75, 74, 43, 43, 74, 74, 74, 74, 76, 74, 74, 74, 74, 74, 74, 74, 74, 76, 78, 47, 47, 8, 8, 8, 47, 47, 8, 8, 8, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 80, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 81, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 82, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 83, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 84, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 85, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 86, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 87, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 88, 47, 47, 47, 47, 47, 47, 47, 47, 89, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 90, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 91, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 92, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 93, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 94, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 95, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 96, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 97, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 98, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 99, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 100, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 101, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 102, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 103, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 104, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 105, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 106, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 107, 108, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 109, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 110, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 111, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 112, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 113, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 114, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 115, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 116, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 117, 47, 47, 47, 118, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 119, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 120, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 121, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 122, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 123, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 124, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 125, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 126, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 127, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 128, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 129, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 130, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 131, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 132, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 133, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 134, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 135, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 136, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 137, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 138, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 139, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 140, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 141, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 142, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 143, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 144, 47, 47, 47, 47, 47, 47, 145, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 146, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 147, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 148, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 149, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 150, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 151, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 152, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 153, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 154, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 155, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 156, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 157, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 158, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 159, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 160, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 161, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 162, 47, 47, 47, 47, 47, 163, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 164, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 165, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 166, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 167, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 168, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 169, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 170, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 79, 79, 79, 47, 47, 79, 79, 79, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 171, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 22, 0 , ] class << self attr_accessor :_graphql_lexer_index_defaults private :_graphql_lexer_index_defaults, :_graphql_lexer_index_defaults= end self._graphql_lexer_index_defaults = [ 0, 1, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 8, 18, 8, 0, 22, 25, 25, 25, 25, 25, 30, 64, 1, 67, 68, 68, 9, 9, 9, 35, 65, 74, 77, 77, 74, 65, 8, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 25, 0 , ] class << self attr_accessor :_graphql_lexer_trans_cond_spaces private :_graphql_lexer_trans_cond_spaces, :_graphql_lexer_trans_cond_spaces= end self._graphql_lexer_trans_cond_spaces = [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0 , ] class << self attr_accessor :_graphql_lexer_cond_targs private :_graphql_lexer_cond_targs, :_graphql_lexer_cond_targs= end self._graphql_lexer_cond_targs = [ 23, 1, 23, 2, 3, 4, 5, 6, 23, 7, 8, 10, 9, 27, 11, 12, 29, 35, 23, 36, 13, 23, 17, 125, 18, 0, 19, 20, 21, 22, 23, 24, 23, 23, 25, 32, 23, 23, 23, 23, 33, 38, 34, 37, 23, 23, 23, 39, 23, 23, 40, 48, 55, 65, 83, 90, 93, 94, 98, 116, 121, 23, 23, 23, 23, 23, 26, 23, 23, 28, 23, 30, 31, 23, 23, 14, 15, 23, 16, 23, 41, 42, 43, 44, 45, 46, 47, 39, 49, 51, 50, 39, 52, 53, 54, 39, 56, 59, 57, 58, 39, 60, 61, 62, 63, 64, 39, 66, 74, 67, 68, 69, 70, 71, 72, 73, 39, 75, 77, 76, 39, 78, 79, 80, 81, 82, 39, 84, 85, 86, 87, 88, 89, 39, 91, 92, 39, 39, 95, 96, 97, 39, 99, 106, 100, 103, 101, 102, 39, 104, 105, 39, 107, 108, 109, 110, 111, 112, 113, 114, 115, 39, 117, 119, 118, 39, 120, 39, 122, 123, 124, 39, 0 , ] class << self attr_accessor :_graphql_lexer_cond_actions private :_graphql_lexer_cond_actions, :_graphql_lexer_cond_actions= end self._graphql_lexer_cond_actions = [ 1, 0, 2, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 5, 6, 0, 7, 0, 8, 0, 0, 0, 0, 0, 0, 11, 0, 12, 13, 14, 0, 15, 16, 17, 18, 0, 14, 19, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 0, 34, 4, 4, 35, 36, 0, 0, 37, 0, 38, 0, 0, 0, 0, 0, 0, 0, 39, 0, 0, 0, 40, 0, 0, 0, 41, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 45, 0, 0, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 47, 0, 0, 48, 49, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 51, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 53, 0, 0, 0, 54, 0, 55, 0, 0, 0, 56, 0 , ] class << self attr_accessor :_graphql_lexer_to_state_actions private :_graphql_lexer_to_state_actions, :_graphql_lexer_to_state_actions= end self._graphql_lexer_to_state_actions = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0 , ] class << self attr_accessor :_graphql_lexer_from_state_actions private :_graphql_lexer_from_state_actions, :_graphql_lexer_from_state_actions= end self._graphql_lexer_from_state_actions = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0 , ] class << self attr_accessor :_graphql_lexer_eof_trans private :_graphql_lexer_eof_trans, :_graphql_lexer_eof_trans= end self._graphql_lexer_eof_trans = [ 0, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 19, 9, 1, 0, 0, 0, 0, 0, 0, 0, 65, 66, 68, 69, 69, 69, 69, 69, 74, 66, 75, 78, 78, 75, 66, 9, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 0, 0 , ] class << self attr_accessor :_graphql_lexer_nfa_targs private :_graphql_lexer_nfa_targs, :_graphql_lexer_nfa_targs= end self._graphql_lexer_nfa_targs = [ 0, 0 , ] class << self attr_accessor :_graphql_lexer_nfa_offsets private :_graphql_lexer_nfa_offsets, :_graphql_lexer_nfa_offsets= end self._graphql_lexer_nfa_offsets = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 , ] class << self attr_accessor :_graphql_lexer_nfa_push_actions private :_graphql_lexer_nfa_push_actions, :_graphql_lexer_nfa_push_actions= end self._graphql_lexer_nfa_push_actions = [ 0, 0 , ] class << self attr_accessor :_graphql_lexer_nfa_pop_trans private :_graphql_lexer_nfa_pop_trans, :_graphql_lexer_nfa_pop_trans= end self._graphql_lexer_nfa_pop_trans = [ 0, 0 , ] class << self attr_accessor :graphql_lexer_start end self.graphql_lexer_start = 23; class << self attr_accessor :graphql_lexer_first_final end self.graphql_lexer_first_final = 23; class << self attr_accessor :graphql_lexer_error end self.graphql_lexer_error = 0; class << self attr_accessor :graphql_lexer_en_str end self.graphql_lexer_en_str = 125; class << self attr_accessor :graphql_lexer_en_main end self.graphql_lexer_en_main = 23; def self.run_lexer(query_string) data = query_string.unpack(PACK_DIRECTIVE) eof = data.length # Since `Lexer` is a module, store all lexer state # in this local variable: meta = { line: 1, col: 1, data: data, tokens: [], previous_token: nil, } p ||= 0 pe ||= data.length begin cs = graphql_lexer_start; ts = 0; te = 0; act = 0; end begin _trans = 0; _have = 0; _cont = 1; _keys = 0; _inds = 0; while ( _cont == 1 ) begin if ( cs == 0 ) _cont = 0; end _have = 0; if ( p == pe ) begin if ( p == eof ) begin if ( _graphql_lexer_eof_trans[cs] > 0 ) begin _trans = _graphql_lexer_eof_trans[cs] - 1; _have = 1; end end if ( _have == 0 ) begin end end end end if ( _have == 0 ) _cont = 0; end end end if ( _cont == 1 ) begin if ( _have == 0 ) begin case _graphql_lexer_from_state_actions[cs] when -2 then begin end when 10 then begin begin begin ts = p; end end end end _keys = (cs<<1) ; _inds = _graphql_lexer_index_offsets[cs] ; if ( ( data[p ].ord) <= 125 && ( data[p ].ord) >= 9 ) begin _ic = _graphql_lexer_char_class[( data[p ].ord) - 9]; if ( _ic <= _graphql_lexer_trans_keys[_keys+1 ]&& _ic >= _graphql_lexer_trans_keys[_keys ] ) _trans = _graphql_lexer_indicies[_inds + ( _ic - _graphql_lexer_trans_keys[_keys ]) ]; else _trans = _graphql_lexer_index_defaults[cs]; end end else begin _trans = _graphql_lexer_index_defaults[cs]; end end end end if ( _cont == 1 ) begin cs = _graphql_lexer_cond_targs[_trans]; case _graphql_lexer_cond_actions[_trans] when -2 then begin end when 14 then begin begin begin te = p+1; end end end when 8 then begin begin begin te = p+1; begin emit_string(ts, te, meta, block: false) end end end end when 28 then begin begin begin te = p+1; begin emit(:RCURLY, ts, te, meta, "}") end end end end when 26 then begin begin begin te = p+1; begin emit(:LCURLY, ts, te, meta, "{") end end end end when 18 then begin begin begin te = p+1; begin emit(:RPAREN, ts, te, meta, ")") end end end end when 17 then begin begin begin te = p+1; begin emit(:LPAREN, ts, te, meta, "(") end end end end when 25 then begin begin begin te = p+1; begin emit(:RBRACKET, ts, te, meta, "]") end end end end when 24 then begin begin begin te = p+1; begin emit(:LBRACKET, ts, te, meta, "[") end end end end when 20 then begin begin begin te = p+1; begin emit(:COLON, ts, te, meta, ":") end end end end when 2 then begin begin begin te = p+1; begin emit_string(ts, te, meta, block: false) end end end end when 34 then begin begin begin te = p+1; begin emit_string(ts, te, meta, block: true) end end end end when 15 then begin begin begin te = p+1; begin emit(:VAR_SIGN, ts, te, meta, "$") end end end end when 22 then begin begin begin te = p+1; begin emit(:DIR_SIGN, ts, te, meta, "@") end end end end when 7 then begin begin begin te = p+1; begin emit(:ELLIPSIS, ts, te, meta, "...") end end end end when 21 then begin begin begin te = p+1; begin emit(:EQUALS, ts, te, meta, "=") end end end end when 13 then begin begin begin te = p+1; begin emit(:BANG, ts, te, meta, "!") end end end end when 27 then begin begin begin te = p+1; begin emit(:PIPE, ts, te, meta, "|") end end end end when 16 then begin begin begin te = p+1; begin emit(:AMP, ts, te, meta, "&") end end end end when 12 then begin begin begin te = p+1; begin meta[:line] += 1 meta[:col] = 1 end end end end when 11 then begin begin begin te = p+1; begin emit(:UNKNOWN_CHAR, ts, te, meta) end end end end when 36 then begin begin begin te = p; p = p - 1; begin emit(:INT, ts, te, meta) end end end end when 37 then begin begin begin te = p; p = p - 1; begin emit(:FLOAT, ts, te, meta) end end end end when 32 then begin begin begin te = p; p = p - 1; begin emit_string(ts, te, meta, block: false) end end end end when 33 then begin begin begin te = p; p = p - 1; begin emit_string(ts, te, meta, block: true) end end end end when 38 then begin begin begin te = p; p = p - 1; begin emit(:IDENTIFIER, ts, te, meta) end end end end when 35 then begin begin begin te = p; p = p - 1; begin record_comment(ts, te, meta) end end end end when 29 then begin begin begin te = p; p = p - 1; begin meta[:col] += te - ts end end end end when 30 then begin begin begin te = p; p = p - 1; begin emit(:UNKNOWN_CHAR, ts, te, meta) end end end end when 5 then begin begin begin p = ((te))-1; begin emit(:INT, ts, te, meta) end end end end when 1 then begin begin begin p = ((te))-1; begin emit(:UNKNOWN_CHAR, ts, te, meta) end end end end when 3 then begin begin begin case act when -2 then begin end when 2 then begin p = ((te))-1; begin emit(:INT, ts, te, meta) end end when 3 then begin p = ((te))-1; begin emit(:FLOAT, ts, te, meta) end end when 4 then begin p = ((te))-1; begin emit(:ON, ts, te, meta, "on") end end when 5 then begin p = ((te))-1; begin emit(:FRAGMENT, ts, te, meta, "fragment") end end when 6 then begin p = ((te))-1; begin emit(:TRUE, ts, te, meta, "true") end end when 7 then begin p = ((te))-1; begin emit(:FALSE, ts, te, meta, "false") end end when 8 then begin p = ((te))-1; begin emit(:NULL, ts, te, meta, "null") end end when 9 then begin p = ((te))-1; begin emit(:QUERY, ts, te, meta, "query") end end when 10 then begin p = ((te))-1; begin emit(:MUTATION, ts, te, meta, "mutation") end end when 11 then begin p = ((te))-1; begin emit(:SUBSCRIPTION, ts, te, meta, "subscription") end end when 12 then begin p = ((te))-1; begin emit(:SCHEMA, ts, te, meta) end end when 13 then begin p = ((te))-1; begin emit(:SCALAR, ts, te, meta) end end when 14 then begin p = ((te))-1; begin emit(:TYPE, ts, te, meta) end end when 15 then begin p = ((te))-1; begin emit(:EXTEND, ts, te, meta) end end when 16 then begin p = ((te))-1; begin emit(:IMPLEMENTS, ts, te, meta) end end when 17 then begin p = ((te))-1; begin emit(:INTERFACE, ts, te, meta) end end when 18 then begin p = ((te))-1; begin emit(:UNION, ts, te, meta) end end when 19 then begin p = ((te))-1; begin emit(:ENUM, ts, te, meta) end end when 20 then begin p = ((te))-1; begin emit(:INPUT, ts, te, meta) end end when 21 then begin p = ((te))-1; begin emit(:DIRECTIVE, ts, te, meta) end end when 29 then begin p = ((te))-1; begin emit_string(ts, te, meta, block: false) end end when 30 then begin p = ((te))-1; begin emit_string(ts, te, meta, block: true) end end when 38 then begin p = ((te))-1; begin emit(:IDENTIFIER, ts, te, meta) end end end end end end when 19 then begin begin begin te = p+1; end end begin begin act = 2; end end end when 6 then begin begin begin te = p+1; end end begin begin act = 3; end end end when 49 then begin begin begin te = p+1; end end begin begin act = 4; end end end when 43 then begin begin begin te = p+1; end end begin begin act = 5; end end end when 54 then begin begin begin te = p+1; end end begin begin act = 6; end end end when 42 then begin begin begin te = p+1; end end begin begin act = 7; end end end when 48 then begin begin begin te = p+1; end end begin begin act = 8; end end end when 50 then begin begin begin te = p+1; end end begin begin act = 9; end end end when 47 then begin begin begin te = p+1; end end begin begin act = 10; end end end when 53 then begin begin begin te = p+1; end end begin begin act = 11; end end end when 52 then begin begin begin te = p+1; end end begin begin act = 12; end end end when 51 then begin begin begin te = p+1; end end begin begin act = 13; end end end when 55 then begin begin begin te = p+1; end end begin begin act = 14; end end end when 41 then begin begin begin te = p+1; end end begin begin act = 15; end end end when 44 then begin begin begin te = p+1; end end begin begin act = 16; end end end when 46 then begin begin begin te = p+1; end end begin begin act = 17; end end end when 56 then begin begin begin te = p+1; end end begin begin act = 18; end end end when 40 then begin begin begin te = p+1; end end begin begin act = 19; end end end when 45 then begin begin begin te = p+1; end end begin begin act = 20; end end end when 39 then begin begin begin te = p+1; end end begin begin act = 21; end end end when 31 then begin begin begin te = p+1; end end begin begin act = 29; end end end when 4 then begin begin begin te = p+1; end end begin begin act = 30; end end end when 23 then begin begin begin te = p+1; end end begin begin act = 38; end end end end case _graphql_lexer_to_state_actions[cs] when -2 then begin end when 9 then begin begin begin ts = 0; end end end end if ( cs == 0 ) _cont = 0; end if ( _cont == 1 ) p += 1; end end end end end end end end meta[:tokens] end def self.record_comment(ts, te, meta) token = GraphQL::Language::Token.new( :COMMENT, meta[:data][ts, te - ts].pack(PACK_DIRECTIVE).force_encoding(UTF_8_ENCODING), meta[:line], meta[:col], meta[:previous_token], ) meta[:previous_token] = token meta[:col] += te - ts end def self.emit(token_name, ts, te, meta, token_value = nil) token_value ||= meta[:data][ts, te - ts].pack(PACK_DIRECTIVE).force_encoding(UTF_8_ENCODING) meta[:tokens] << token = GraphQL::Language::Token.new( token_name, token_value, meta[:line], meta[:col], meta[:previous_token], ) meta[:previous_token] = token # Bump the column counter for the next token meta[:col] += te - ts end ESCAPES = /\\["\\\/bfnrt]/ ESCAPES_REPLACE = { '\\"' => '"', "\\\\" => "\\", "\\/" => '/', "\\b" => "\b", "\\f" => "\f", "\\n" => "\n", "\\r" => "\r", "\\t" => "\t", } UTF_8 = /\\u[\dAa-f]{4}/i UTF_8_REPLACE = ->(m) { [m[-4..-1].to_i(16)].pack('U'.freeze) } VALID_STRING = /\A(?:[^\\]|#{ESCAPES}|#{UTF_8})*\z/o PACK_DIRECTIVE = "c*" UTF_8_ENCODING = "UTF-8" def self.emit_string(ts, te, meta, block:) quotes_length = block ? 3 : 1 value = meta[:data][ts + quotes_length, te - ts - 2 * quotes_length].pack(PACK_DIRECTIVE).force_encoding(UTF_8_ENCODING) || '' line_incr = 0 if block && !value.empty? line_incr = value.count("\n") value = GraphQL::Language::BlockString.trim_whitespace(value) end # TODO: replace with `String#match?` when we support only Ruby 2.4+ # (It's faster: https://bugs.ruby-lang.org/issues/8110) if !value.valid_encoding? || !value.match?(VALID_STRING) meta[:tokens] << token = GraphQL::Language::Token.new( :BAD_UNICODE_ESCAPE, value, meta[:line], meta[:col], meta[:previous_token], ) else replace_escaped_characters_in_place(value) if !value.valid_encoding? meta[:tokens] << token = GraphQL::Language::Token.new( :BAD_UNICODE_ESCAPE, value, meta[:line], meta[:col], meta[:previous_token], ) else meta[:tokens] << token = GraphQL::Language::Token.new( :STRING, value, meta[:line], meta[:col], meta[:previous_token], ) end end meta[:previous_token] = token meta[:col] += te - ts meta[:line] += line_incr end end end end graphql-ruby-1.11.10/lib/graphql/language/lexer.rl000066400000000000000000000204621414121453000217340ustar00rootroot00000000000000%%{ machine graphql_lexer; IDENTIFIER = [_A-Za-z][_0-9A-Za-z]*; NEWLINE = [\c\r\n]; BLANK = [, \t]+; COMMENT = '#' [^\n\r]*; INT = '-'? ('0'|[1-9][0-9]*); FLOAT_DECIMAL = '.'[0-9]+; FLOAT_EXP = ('e' | 'E')?('+' | '-')?[0-9]+; FLOAT = INT FLOAT_DECIMAL? FLOAT_EXP?; ON = 'on'; FRAGMENT = 'fragment'; TRUE = 'true'; FALSE = 'false'; NULL = 'null'; QUERY = 'query'; MUTATION = 'mutation'; SUBSCRIPTION = 'subscription'; SCHEMA = 'schema'; SCALAR = 'scalar'; TYPE = 'type'; EXTEND = 'extend'; IMPLEMENTS = 'implements'; INTERFACE = 'interface'; UNION = 'union'; ENUM = 'enum'; INPUT = 'input'; DIRECTIVE = 'directive'; LCURLY = '{'; RCURLY = '}'; LPAREN = '('; RPAREN = ')'; LBRACKET = '['; RBRACKET = ']'; COLON = ':'; QUOTE = '"'; BACKSLASH = '\\'; # Could limit to hex here, but “bad unicode escape” on 0XXF is probably a # more helpful error than “unknown char” UNICODE_ESCAPE = '\\u' [0-9A-Za-z]{4}; # https://graphql.github.io/graphql-spec/June2018/#sec-String-Value STRING_ESCAPE = '\\' [\\/bfnrt]; BLOCK_QUOTE = '"""'; ESCAPED_BLOCK_QUOTE = '\\"""'; BLOCK_STRING_CHAR = (ESCAPED_BLOCK_QUOTE | ^QUOTE | QUOTE{1,2} ^QUOTE); ESCAPED_QUOTE = '\\"'; STRING_CHAR = ((ESCAPED_QUOTE | ^QUOTE) - BACKSLASH) | UNICODE_ESCAPE | STRING_ESCAPE; VAR_SIGN = '$'; DIR_SIGN = '@'; ELLIPSIS = '...'; EQUALS = '='; BANG = '!'; PIPE = '|'; AMP = '&'; QUOTED_STRING = QUOTE STRING_CHAR* QUOTE; BLOCK_STRING = BLOCK_QUOTE BLOCK_STRING_CHAR* QUOTE{0,2} BLOCK_QUOTE; # catch-all for anything else. must be at the bottom for precedence. UNKNOWN_CHAR = /./; # Used with ragel -V for graphviz visualization str := |* QUOTED_STRING => { emit_string(ts, te, meta, block: false) }; *|; main := |* INT => { emit(:INT, ts, te, meta) }; FLOAT => { emit(:FLOAT, ts, te, meta) }; ON => { emit(:ON, ts, te, meta, "on") }; FRAGMENT => { emit(:FRAGMENT, ts, te, meta, "fragment") }; TRUE => { emit(:TRUE, ts, te, meta, "true") }; FALSE => { emit(:FALSE, ts, te, meta, "false") }; NULL => { emit(:NULL, ts, te, meta, "null") }; QUERY => { emit(:QUERY, ts, te, meta, "query") }; MUTATION => { emit(:MUTATION, ts, te, meta, "mutation") }; SUBSCRIPTION => { emit(:SUBSCRIPTION, ts, te, meta, "subscription") }; SCHEMA => { emit(:SCHEMA, ts, te, meta) }; SCALAR => { emit(:SCALAR, ts, te, meta) }; TYPE => { emit(:TYPE, ts, te, meta) }; EXTEND => { emit(:EXTEND, ts, te, meta) }; IMPLEMENTS => { emit(:IMPLEMENTS, ts, te, meta) }; INTERFACE => { emit(:INTERFACE, ts, te, meta) }; UNION => { emit(:UNION, ts, te, meta) }; ENUM => { emit(:ENUM, ts, te, meta) }; INPUT => { emit(:INPUT, ts, te, meta) }; DIRECTIVE => { emit(:DIRECTIVE, ts, te, meta) }; RCURLY => { emit(:RCURLY, ts, te, meta, "}") }; LCURLY => { emit(:LCURLY, ts, te, meta, "{") }; RPAREN => { emit(:RPAREN, ts, te, meta, ")") }; LPAREN => { emit(:LPAREN, ts, te, meta, "(")}; RBRACKET => { emit(:RBRACKET, ts, te, meta, "]") }; LBRACKET => { emit(:LBRACKET, ts, te, meta, "[") }; COLON => { emit(:COLON, ts, te, meta, ":") }; QUOTED_STRING => { emit_string(ts, te, meta, block: false) }; BLOCK_STRING => { emit_string(ts, te, meta, block: true) }; VAR_SIGN => { emit(:VAR_SIGN, ts, te, meta, "$") }; DIR_SIGN => { emit(:DIR_SIGN, ts, te, meta, "@") }; ELLIPSIS => { emit(:ELLIPSIS, ts, te, meta, "...") }; EQUALS => { emit(:EQUALS, ts, te, meta, "=") }; BANG => { emit(:BANG, ts, te, meta, "!") }; PIPE => { emit(:PIPE, ts, te, meta, "|") }; AMP => { emit(:AMP, ts, te, meta, "&") }; IDENTIFIER => { emit(:IDENTIFIER, ts, te, meta) }; COMMENT => { record_comment(ts, te, meta) }; NEWLINE => { meta[:line] += 1 meta[:col] = 1 }; BLANK => { meta[:col] += te - ts }; UNKNOWN_CHAR => { emit(:UNKNOWN_CHAR, ts, te, meta) }; *|; }%% # frozen_string_literal: true module GraphQL module Language module Lexer if !String.method_defined?(:match?) using GraphQL::StringMatchBackport end def self.tokenize(query_string) run_lexer(query_string) end # Replace any escaped unicode or whitespace with the _actual_ characters # To avoid allocating more strings, this modifies the string passed into it def self.replace_escaped_characters_in_place(raw_string) raw_string.gsub!(ESCAPES, ESCAPES_REPLACE) raw_string.gsub!(UTF_8, &UTF_8_REPLACE) nil end private %% write data; def self.run_lexer(query_string) data = query_string.unpack(PACK_DIRECTIVE) eof = data.length # Since `Lexer` is a module, store all lexer state # in this local variable: meta = { line: 1, col: 1, data: data, tokens: [], previous_token: nil, } p ||= 0 pe ||= data.length %% write init; %% write exec; meta[:tokens] end def self.record_comment(ts, te, meta) token = GraphQL::Language::Token.new( :COMMENT, meta[:data][ts, te - ts].pack(PACK_DIRECTIVE).force_encoding(UTF_8_ENCODING), meta[:line], meta[:col], meta[:previous_token], ) meta[:previous_token] = token meta[:col] += te - ts end def self.emit(token_name, ts, te, meta, token_value = nil) token_value ||= meta[:data][ts, te - ts].pack(PACK_DIRECTIVE).force_encoding(UTF_8_ENCODING) meta[:tokens] << token = GraphQL::Language::Token.new( token_name, token_value, meta[:line], meta[:col], meta[:previous_token], ) meta[:previous_token] = token # Bump the column counter for the next token meta[:col] += te - ts end ESCAPES = /\\["\\\/bfnrt]/ ESCAPES_REPLACE = { '\\"' => '"', "\\\\" => "\\", "\\/" => '/', "\\b" => "\b", "\\f" => "\f", "\\n" => "\n", "\\r" => "\r", "\\t" => "\t", } UTF_8 = /\\u[\dAa-f]{4}/i UTF_8_REPLACE = ->(m) { [m[-4..-1].to_i(16)].pack('U'.freeze) } VALID_STRING = /\A(?:[^\\]|#{ESCAPES}|#{UTF_8})*\z/o PACK_DIRECTIVE = "c*" UTF_8_ENCODING = "UTF-8" def self.emit_string(ts, te, meta, block:) quotes_length = block ? 3 : 1 value = meta[:data][ts + quotes_length, te - ts - 2 * quotes_length].pack(PACK_DIRECTIVE).force_encoding(UTF_8_ENCODING) || '' line_incr = 0 if block && !value.empty? line_incr = value.count("\n") value = GraphQL::Language::BlockString.trim_whitespace(value) end # TODO: replace with `String#match?` when we support only Ruby 2.4+ # (It's faster: https://bugs.ruby-lang.org/issues/8110) if !value.valid_encoding? || !value.match?(VALID_STRING) meta[:tokens] << token = GraphQL::Language::Token.new( :BAD_UNICODE_ESCAPE, value, meta[:line], meta[:col], meta[:previous_token], ) else replace_escaped_characters_in_place(value) if !value.valid_encoding? meta[:tokens] << token = GraphQL::Language::Token.new( :BAD_UNICODE_ESCAPE, value, meta[:line], meta[:col], meta[:previous_token], ) else meta[:tokens] << token = GraphQL::Language::Token.new( :STRING, value, meta[:line], meta[:col], meta[:previous_token], ) end end meta[:previous_token] = token meta[:col] += te - ts meta[:line] += line_incr end end end end graphql-ruby-1.11.10/lib/graphql/language/nodes.rb000066400000000000000000000563661414121453000217270ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language module Nodes # {AbstractNode} is the base class for all nodes in a GraphQL AST. # # It provides some APIs for working with ASTs: # - `children` returns all AST nodes attached to this one. Used for tree traversal. # - `scalars` returns all scalar (Ruby) values attached to this one. Used for comparing nodes. # - `to_query_string` turns an AST node into a GraphQL string class AbstractNode module DefinitionNode # This AST node's {#line} returns the first line, which may be the description. # @return [Integer] The first line of the definition (not the description) attr_reader :definition_line def initialize(options = {}) @definition_line = options.delete(:definition_line) super(options) end end attr_reader :line, :col, :filename # Initialize a node by extracting its position, # then calling the class's `initialize_node` method. # @param options [Hash] Initial attributes for this node def initialize(options = {}) if options.key?(:position_source) position_source = options.delete(:position_source) @line = position_source.line @col = position_source.col end @filename = options.delete(:filename) initialize_node(**options) end # Value equality # @return [Boolean] True if `self` is equivalent to `other` def ==(other) return true if equal?(other) other.kind_of?(self.class) && other.scalars == self.scalars && other.children == self.children end NO_CHILDREN = [].freeze # @return [Array] all nodes in the tree below this one def children NO_CHILDREN end # @return [Array] Scalar values attached to this node def scalars NO_CHILDREN end # This might be unnecessary, but its easiest to add it here. def initialize_copy(other) @children = nil @scalars = nil @query_string = nil end def children_method_name self.class.children_method_name end def position [line, col] end def to_query_string(printer: GraphQL::Language::Printer.new) if printer.is_a?(GraphQL::Language::Printer) @query_string ||= printer.print(self) else printer.print(self) end end # This creates a copy of `self`, with `new_options` applied. # @param new_options [Hash] # @return [AbstractNode] a shallow copy of `self` def merge(new_options) dup.merge!(new_options) end # Copy `self`, but modify the copy so that `previous_child` is replaced by `new_child` def replace_child(previous_child, new_child) # Figure out which list `previous_child` may be found in method_name = previous_child.children_method_name # Get the value from this (original) node prev_children = public_send(method_name) if prev_children.is_a?(Array) # Copy that list, and replace `previous_child` with `new_child` # in the list. new_children = prev_children.dup prev_idx = new_children.index(previous_child) new_children[prev_idx] = new_child else # Use the new value for the given attribute new_children = new_child end # Copy this node, but with the new child value copy_of_self = merge(method_name => new_children) # Return the copy: copy_of_self end # TODO DRY with `replace_child` def delete_child(previous_child) # Figure out which list `previous_child` may be found in method_name = previous_child.children_method_name # Copy that list, and delete previous_child new_children = public_send(method_name).dup new_children.delete(previous_child) # Copy this node, but with the new list of children: copy_of_self = merge(method_name => new_children) # Return the copy: copy_of_self end protected def merge!(new_options) new_options.each do |key, value| instance_variable_set(:"@#{key}", value) end self end class << self # Add a default `#visit_method` and `#children_method_name` using the class name def inherited(child_class) super name_underscored = child_class.name .split("::").last .gsub(/([a-z])([A-Z])/,'\1_\2') # insert underscores .downcase # remove caps child_class.module_eval <<-RUBY def visit_method :on_#{name_underscored} end class << self attr_accessor :children_method_name end self.children_method_name = :#{name_underscored}s RUBY end private # Name accessors which return lists of nodes, # along with the kind of node they return, if possible. # - Add a reader for these children # - Add a persistent update method to add a child # - Generate a `#children` method def children_methods(children_of_type) if defined?(@children_methods) raise "Can't re-call .children_methods for #{self} (already have: #{@children_methods})" else @children_methods = children_of_type end if children_of_type == false @children_methods = {} # skip else children_of_type.each do |method_name, node_type| module_eval <<-RUBY, __FILE__, __LINE__ # A reader for these children attr_reader :#{method_name} RUBY if node_type # Only generate a method if we know what kind of node to make module_eval <<-RUBY, __FILE__, __LINE__ # Singular method: create a node with these options # and return a new `self` which includes that node in this list. def merge_#{method_name.to_s.sub(/s$/, "")}(node_opts) merge(#{method_name}: #{method_name} + [#{node_type.name}.new(node_opts)]) end RUBY end end if children_of_type.size == 1 module_eval <<-RUBY, __FILE__, __LINE__ alias :children #{children_of_type.keys.first} RUBY else module_eval <<-RUBY, __FILE__, __LINE__ def children @children ||= (#{children_of_type.keys.map { |k| "@#{k}" }.join(" + ")}).freeze end RUBY end end if defined?(@scalar_methods) if !method_defined?(:initialize_node) generate_initialize_node else # This method was defined manually end else raise "Can't generate_initialize_node because scalar_methods wasn't called; call it before children_methods" end end # These methods return a plain Ruby value, not another node # - Add reader methods # - Add a `#scalars` method def scalar_methods(*method_names) if defined?(@scalar_methods) raise "Can't re-call .scalar_methods for #{self} (already have: #{@scalar_methods})" else @scalar_methods = method_names end if method_names == [false] @scalar_methods = [] # skip it else module_eval <<-RUBY, __FILE__, __LINE__ # add readers for each scalar attr_reader #{method_names.map { |m| ":#{m}"}.join(", ")} def scalars @scalars ||= [#{method_names.map { |k| "@#{k}" }.join(", ")}].freeze end RUBY end end def generate_initialize_node scalar_method_names = @scalar_methods # TODO: These probably should be scalar methods, but `types` returns an array [:types, :description].each do |extra_method| if method_defined?(extra_method) scalar_method_names += [extra_method] end end all_method_names = scalar_method_names + @children_methods.keys if all_method_names.include?(:alias) # Rather than complicating this special case, # let it be overridden (in field) return else arguments = scalar_method_names.map { |m| "#{m}: nil"} + @children_methods.keys.map { |m| "#{m}: NO_CHILDREN" } assignments = scalar_method_names.map { |m| "@#{m} = #{m}"} + @children_methods.keys.map { |m| "@#{m} = #{m}.freeze" } module_eval <<-RUBY, __FILE__, __LINE__ def initialize_node #{arguments.join(", ")} #{assignments.join("\n")} end RUBY end end end end # Base class for non-null type names and list type names class WrapperType < AbstractNode scalar_methods :of_type children_methods(false) end # Base class for nodes whose only value is a name (no child nodes or other scalars) class NameOnlyNode < AbstractNode scalar_methods :name children_methods(false) end # A key-value pair for a field's inputs class Argument < AbstractNode scalar_methods :name, :value children_methods(false) # @!attribute name # @return [String] the key for this argument # @!attribute value # @return [String, Float, Integer, Boolean, Array, InputObject] The value passed for this key def children @children ||= Array(value).flatten.select { |v| v.is_a?(AbstractNode) } end end class Directive < AbstractNode scalar_methods :name children_methods(arguments: GraphQL::Language::Nodes::Argument) end class DirectiveLocation < NameOnlyNode end class DirectiveDefinition < AbstractNode include DefinitionNode attr_reader :description scalar_methods :name children_methods( locations: Nodes::DirectiveLocation, arguments: Nodes::Argument, ) end # This is the AST root for normal queries # # @example Deriving a document by parsing a string # document = GraphQL.parse(query_string) # # @example Creating a string from a document # document.to_query_string # # { ... } # # @example Creating a custom string from a document # class VariableScrubber < GraphQL::Language::Printer # def print_argument(arg) # "#{arg.name}: " # end # end # # document.to_query_string(printer: VariableSrubber.new) # class Document < AbstractNode scalar_methods false children_methods(definitions: nil) # @!attribute definitions # @return [Array] top-level GraphQL units: operations or fragments def slice_definition(name) GraphQL::Language::DefinitionSlice.slice(self, name) end end # An enum value. The string is available as {#name}. class Enum < NameOnlyNode end # A null value literal. class NullValue < NameOnlyNode end # A single selection in a GraphQL query. class Field < AbstractNode NONE = [].freeze scalar_methods :name, :alias children_methods({ arguments: GraphQL::Language::Nodes::Argument, selections: GraphQL::Language::Nodes::Field, directives: GraphQL::Language::Nodes::Directive, }) # @!attribute selections # @return [Array] Selections on this object (or empty array if this is a scalar field) def initialize_node(attributes) @name = attributes[:name] @arguments = attributes[:arguments] || NONE @directives = attributes[:directives] || NONE @selections = attributes[:selections] || NONE # oops, alias is a keyword: @alias = attributes[:alias] end # Override this because default is `:fields` self.children_method_name = :selections end # A reusable fragment, defined at document-level. class FragmentDefinition < AbstractNode # @!attribute name # @return [String] the identifier for this fragment, which may be applied with `...#{name}` # @!attribute type # @return [String] the type condition for this fragment (name of type which it may apply to) def initialize_node(name: nil, type: nil, directives: [], selections: []) @name = name @type = type @directives = directives @selections = selections end scalar_methods :name, :type children_methods({ selections: GraphQL::Language::Nodes::Field, directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end # Application of a named fragment in a selection class FragmentSpread < AbstractNode scalar_methods :name children_methods(directives: GraphQL::Language::Nodes::Directive) self.children_method_name = :selections # @!attribute name # @return [String] The identifier of the fragment to apply, corresponds with {FragmentDefinition#name} end # An unnamed fragment, defined directly in the query with `... { }` class InlineFragment < AbstractNode scalar_methods :type children_methods({ selections: GraphQL::Language::Nodes::Field, directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :selections # @!attribute type # @return [String, nil] Name of the type this fragment applies to, or `nil` if this fragment applies to any type end # A collection of key-value inputs which may be a field argument class InputObject < AbstractNode scalar_methods(false) children_methods(arguments: GraphQL::Language::Nodes::Argument) # @!attribute arguments # @return [Array] A list of key-value pairs inside this input object # @return [Hash] Recursively turn this input object into a Ruby Hash def to_h(options={}) arguments.inject({}) do |memo, pair| v = pair.value memo[pair.name] = serialize_value_for_hash v memo end end self.children_method_name = :value private def serialize_value_for_hash(value) case value when InputObject value.to_h when Array value.map do |v| serialize_value_for_hash v end when Enum value.name when NullValue nil else value end end end # A list type definition, denoted with `[...]` (used for variable type definitions) class ListType < WrapperType end # A non-null type definition, denoted with `...!` (used for variable type definitions) class NonNullType < WrapperType end # An operation-level query variable class VariableDefinition < AbstractNode scalar_methods :name, :type, :default_value children_methods false # @!attribute default_value # @return [String, Integer, Float, Boolean, Array, NullValue] A Ruby value to use if no other value is provided # @!attribute type # @return [TypeName, NonNullType, ListType] The expected type of this value # @!attribute name # @return [String] The identifier for this variable, _without_ `$` self.children_method_name = :variables end # A query, mutation or subscription. # May be anonymous or named. # May be explicitly typed (eg `mutation { ... }`) or implicitly a query (eg `{ ... }`). class OperationDefinition < AbstractNode scalar_methods :operation_type, :name children_methods({ variables: GraphQL::Language::Nodes::VariableDefinition, selections: GraphQL::Language::Nodes::Field, directives: GraphQL::Language::Nodes::Directive, }) # @!attribute variables # @return [Array] Variable $definitions for this operation # @!attribute selections # @return [Array] Root-level fields on this operation # @!attribute operation_type # @return [String, nil] The root type for this operation, or `nil` for implicit `"query"` # @!attribute name # @return [String, nil] The name for this operation, or `nil` if unnamed self.children_method_name = :definitions end # A type name, used for variable definitions class TypeName < NameOnlyNode end # Usage of a variable in a query. Name does _not_ include `$`. class VariableIdentifier < NameOnlyNode self.children_method_name = :value end class SchemaDefinition < AbstractNode include DefinitionNode scalar_methods :query, :mutation, :subscription children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end class SchemaExtension < AbstractNode scalar_methods :query, :mutation, :subscription children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end class ScalarTypeDefinition < AbstractNode include DefinitionNode attr_reader :description scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end class ScalarTypeExtension < AbstractNode scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end class InputValueDefinition < AbstractNode include DefinitionNode attr_reader :description scalar_methods :name, :type, :default_value children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :fields end class FieldDefinition < AbstractNode include DefinitionNode attr_reader :description scalar_methods :name, :type children_methods({ directives: GraphQL::Language::Nodes::Directive, arguments: GraphQL::Language::Nodes::InputValueDefinition, }) self.children_method_name = :fields # this is so that `children_method_name` of `InputValueDefinition` works properly # with `#replace_child` alias :fields :arguments def merge(new_options) if (f = new_options.delete(:fields)) new_options[:arguments] = f end super end end class ObjectTypeDefinition < AbstractNode include DefinitionNode attr_reader :description scalar_methods :name, :interfaces children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::FieldDefinition, }) self.children_method_name = :definitions end class ObjectTypeExtension < AbstractNode scalar_methods :name, :interfaces children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::FieldDefinition, }) self.children_method_name = :definitions end class InterfaceTypeDefinition < AbstractNode include DefinitionNode attr_reader :description scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::FieldDefinition, }) self.children_method_name = :definitions end class InterfaceTypeExtension < AbstractNode scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::FieldDefinition, }) self.children_method_name = :definitions end class UnionTypeDefinition < AbstractNode include DefinitionNode attr_reader :description, :types scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end class UnionTypeExtension < AbstractNode attr_reader :types scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end class EnumValueDefinition < AbstractNode include DefinitionNode attr_reader :description scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :values end class EnumTypeDefinition < AbstractNode include DefinitionNode attr_reader :description scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, values: GraphQL::Language::Nodes::EnumValueDefinition, }) self.children_method_name = :definitions end class EnumTypeExtension < AbstractNode scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, values: GraphQL::Language::Nodes::EnumValueDefinition, }) self.children_method_name = :definitions end class InputObjectTypeDefinition < AbstractNode include DefinitionNode attr_reader :description scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::InputValueDefinition, }) self.children_method_name = :definitions end class InputObjectTypeExtension < AbstractNode scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::InputValueDefinition, }) self.children_method_name = :definitions end end end end graphql-ruby-1.11.10/lib/graphql/language/parser.rb000066400000000000000000002005631414121453000221010ustar00rootroot00000000000000# # DO NOT MODIFY!!!! # This file is automatically generated by Racc 1.4.16 # from Racc grammar file "". # require 'racc/parser.rb' module GraphQL module Language class Parser < Racc::Parser module_eval(<<'...end parser.y/module_eval...', 'parser.y', 437) EMPTY_ARRAY = [].freeze def initialize(query_string, filename:, tracer: Tracing::NullTracer) raise GraphQL::ParseError.new("No query string was present", nil, nil, query_string) if query_string.nil? @query_string = query_string @filename = filename @tracer = tracer @reused_next_token = [nil, nil] end def parse_document @document ||= begin # Break the string into tokens @tracer.trace("lex", {query_string: @query_string}) do @tokens ||= GraphQL.scan(@query_string) end # From the tokens, build an AST @tracer.trace("parse", {query_string: @query_string}) do if @tokens.empty? raise GraphQL::ParseError.new("Unexpected end of document", nil, nil, @query_string) else do_parse end end end end def self.parse(query_string, filename: nil, tracer: GraphQL::Tracing::NullTracer) self.new(query_string, filename: filename, tracer: tracer).parse_document end def self.parse_file(filename, tracer: GraphQL::Tracing::NullTracer) self.parse(File.read(filename), filename: filename, tracer: tracer) end private def next_token lexer_token = @tokens.shift if lexer_token.nil? nil else @reused_next_token[0] = lexer_token.name @reused_next_token[1] = lexer_token @reused_next_token end end def get_description(token) comments = [] loop do prev_token = token token = token.prev_token break if token.nil? break if token.name != :COMMENT break if prev_token.line != token.line + 1 comments.unshift(token.to_s.sub(/^#\s*/, "")) end return nil if comments.empty? comments.join("\n") end def on_error(parser_token_id, lexer_token, vstack) if lexer_token == "$" || lexer_token == nil raise GraphQL::ParseError.new("Unexpected end of document", nil, nil, @query_string, filename: @filename) else parser_token_name = token_to_str(parser_token_id) if parser_token_name.nil? raise GraphQL::ParseError.new("Parse Error on unknown token: {token_id: #{parser_token_id}, lexer_token: #{lexer_token}} from #{@query_string}", nil, nil, @query_string, filename: @filename) else line, col = lexer_token.line_and_column if lexer_token.name == :BAD_UNICODE_ESCAPE raise GraphQL::ParseError.new("Parse error on bad Unicode escape sequence: #{lexer_token.to_s.inspect} (#{parser_token_name}) at [#{line}, #{col}]", line, col, @query_string, filename: @filename) else raise GraphQL::ParseError.new("Parse error on #{lexer_token.to_s.inspect} (#{parser_token_name}) at [#{line}, #{col}]", line, col, @query_string, filename: @filename) end end end end def make_node(node_name, assigns) assigns.each do |key, value| if key != :position_source && value.is_a?(GraphQL::Language::Token) assigns[key] = value.to_s end end assigns[:filename] = @filename GraphQL::Language::Nodes.const_get(node_name).new(assigns) end ...end parser.y/module_eval... ##### State transition tables begin ### racc_action_table = [ -2, 279, 11, -99, 12, 13, 14, 280, 11, -99, 12, 13, 14, 178, -99, -99, 19, -99, 94, -165, 194, 93, 19, 12, 13, 14, 15, 196, 71, 35, 35, 35, 15, 71, 71, 28, -99, 35, 12, 13, 14, 28, 71, 263, -151, 71, 71, 35, 71, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 60, 12, 13, 14, 71, -165, -165, 181, 35, -165, 160, 120, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 90, 12, 13, 14, 299, 66, 295, 35, 35, -165, 221, 35, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 223, 12, 13, 14, 273, 66, 35, 272, 35, 131, 268, 35, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 219, 71, 12, 13, 14, 66, 35, 217, 278, 35, 218, 285, 35, 200, 44, 45, 46, 47, 48, 49, 50, 51, 52, 198, 199, 207, 208, 204, 205, 206, 216, 302, 131, 12, 13, 14, 35, 300, 71, 277, 71, 218, 230, 87, 200, 44, 45, 46, 47, 48, 49, 50, 51, 52, 198, 199, 207, 208, 204, 205, 206, 216, 219, 233, 12, 13, 14, 35, 303, 217, 71, 35, 218, 35, 244, 200, 44, 45, 46, 47, 48, 49, 50, 51, 52, 198, 199, 207, 208, 204, 205, 206, 216, 302, 142, 12, 13, 14, 226, 12, 13, 14, 306, 218, 35, 35, 200, 44, 45, 46, 47, 48, 49, 50, 51, 52, 198, 199, 207, 208, 204, 205, 206, 216, 219, 35, 12, 13, 14, 250, 307, 217, 131, 284, 218, 254, 71, 200, 44, 45, 46, 47, 48, 49, 50, 51, 52, 198, 199, 207, 208, 204, 205, 206, 216, 219, 138, 12, 13, 14, 71, 94, 217, 233, 131, 218, 269, 71, 200, 44, 45, 46, 47, 48, 49, 50, 51, 52, 198, 199, 207, 208, 204, 205, 206, 216, 219, 71, 12, 13, 14, 71, 120, 217, 71, 269, 218, 116, 71, 200, 44, 45, 46, 47, 48, 49, 50, 51, 52, 198, 199, 207, 208, 204, 205, 206, 216, 12, 13, 14, 80, 81, 38, 82, 83, 84, 85, 86, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, 284, 101, 66, 174, 12, 13, 14, 71, 96, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 291, 12, 13, 14, 71, 12, 13, 14, 290, 98, 194, 71, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 287, 12, 13, 14, 73, 74, 75, 71, 76, 77, 78, 79, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 310, 12, 13, 14, 131, 94, 298, 168, 169, 89, 71, 131, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 257, 12, 13, 14, 71, 176, 71, 71, 71, 71, 71, 185, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, 186, 71, 187, 142, 188, 129, 71, 190, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, 191, 192, 193, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, nil, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, 129, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, 166, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, 129, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, 129, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, 129, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, 129, nil, nil, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 12, 13, 14, nil, nil, nil, nil, nil, nil, nil, nil, 200, 44, 45, 46, 47, 48, 49, 50, 51, 52, 198, 199, -157, nil, nil, nil, -157, nil, nil, nil, nil, nil, -157, nil, -157, -157 ] racc_action_check = [ 3, 248, 3, 143, 3, 3, 3, 249, 0, 145, 0, 0, 0, 140, 102, 179, 3, 149, 65, 230, 158, 65, 0, 138, 138, 138, 3, 162, 145, 248, 249, 3, 0, 165, 143, 3, 147, 0, 142, 142, 142, 0, 110, 227, 140, 102, 179, 230, 149, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 11, 11, 11, 11, 147, 190, 185, 142, 227, 178, 118, 118, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 59, 59, 59, 59, 276, 11, 266, 190, 185, 244, 170, 178, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 172, 172, 172, 172, 241, 59, 276, 240, 266, 171, 235, 244, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 255, 109, 255, 255, 255, 172, 241, 255, 247, 240, 255, 255, 235, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 306, 173, 306, 306, 306, 247, 281, 108, 245, 107, 306, 180, 38, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 290, 182, 290, 290, 290, 245, 289, 290, 106, 187, 290, 188, 189, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 284, 105, 284, 284, 284, 177, 177, 177, 177, 293, 284, 192, 193, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 218, 194, 218, 218, 218, 195, 294, 218, 197, 308, 218, 218, 104, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 218, 303, 103, 303, 303, 303, 312, 100, 303, 231, 97, 303, 236, 95, 303, 303, 303, 303, 303, 303, 303, 303, 303, 303, 303, 303, 303, 303, 303, 303, 303, 303, 168, 92, 168, 168, 168, 19, 89, 168, 88, 246, 168, 86, 73, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 131, 131, 131, 37, 37, 1, 37, 37, 37, 37, 37, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 66, 66, 66, 251, 72, 131, 135, 135, 135, 135, 69, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 261, 261, 261, 261, 66, 101, 101, 101, 260, 67, 265, 313, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 261, 258, 258, 258, 258, 28, 28, 28, 270, 28, 28, 28, 28, 258, 258, 258, 258, 258, 258, 258, 258, 258, 258, 258, 258, 258, 258, 302, 302, 302, 302, 121, 122, 275, 125, 127, 40, 130, 117, 302, 302, 302, 302, 302, 302, 302, 302, 302, 302, 302, 302, 302, 302, 219, 219, 219, 219, 133, 137, 139, 115, 141, 113, 114, 144, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 98, 98, 98, 146, 112, 148, 111, 150, 98, 152, 154, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 10, 10, 10, 155, 156, 157, nil, nil, nil, nil, nil, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 15, 15, 15, nil, nil, nil, nil, nil, nil, nil, nil, nil, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 71, 71, 71, nil, nil, nil, nil, nil, nil, nil, nil, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 74, 74, 74, nil, nil, nil, nil, nil, nil, nil, nil, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 75, 75, 75, nil, nil, nil, nil, nil, nil, nil, nil, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 76, 76, 76, nil, nil, nil, nil, nil, nil, nil, nil, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 77, 77, 77, nil, nil, nil, nil, nil, nil, nil, nil, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 78, 78, 78, nil, nil, nil, nil, nil, nil, nil, nil, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 79, 79, 79, nil, nil, nil, nil, nil, nil, nil, nil, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 80, 80, 80, nil, nil, nil, nil, nil, nil, nil, nil, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 81, 81, 81, nil, nil, nil, nil, nil, nil, nil, nil, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 82, 82, 82, nil, nil, nil, nil, nil, nil, nil, nil, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 83, 83, 83, nil, nil, nil, nil, nil, nil, nil, nil, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 84, 84, 84, nil, nil, nil, nil, nil, nil, nil, nil, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 85, 85, 85, nil, nil, nil, nil, nil, nil, nil, nil, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 93, 93, 93, nil, nil, nil, nil, nil, nil, nil, nil, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 94, 94, 94, nil, nil, nil, nil, nil, nil, nil, nil, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 96, 96, 96, nil, nil, nil, nil, nil, 96, nil, nil, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 116, 116, 116, nil, nil, nil, nil, nil, nil, nil, nil, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 120, 120, 120, nil, nil, nil, nil, nil, nil, nil, nil, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 123, 123, 123, nil, 123, nil, nil, nil, nil, nil, nil, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 129, 129, 129, nil, nil, nil, nil, nil, 129, nil, nil, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 176, 176, 176, nil, nil, nil, nil, nil, nil, nil, nil, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 181, 181, 181, nil, nil, nil, nil, nil, nil, nil, nil, 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, 183, 183, 183, nil, nil, nil, nil, nil, nil, nil, nil, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 183, 186, 186, 186, nil, nil, nil, nil, nil, nil, nil, nil, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 191, 191, 191, nil, nil, nil, nil, nil, nil, nil, nil, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 191, 196, 196, 196, nil, nil, nil, nil, nil, 196, nil, nil, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 196, 217, 217, 217, nil, nil, nil, nil, nil, nil, nil, nil, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 217, 228, 228, 228, nil, nil, nil, nil, nil, nil, nil, nil, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 233, 233, 233, nil, nil, nil, nil, nil, nil, nil, nil, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 242, 242, 242, nil, nil, nil, nil, nil, nil, nil, nil, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 250, 250, 250, nil, nil, nil, nil, nil, nil, nil, nil, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 269, 269, 269, nil, nil, nil, nil, nil, nil, nil, nil, 269, 269, 269, 269, 269, 269, 269, 269, 269, 269, 269, 269, 269, 269, 298, 298, 298, nil, nil, nil, nil, nil, 298, nil, nil, 298, 298, 298, 298, 298, 298, 298, 298, 298, 298, 298, 298, 298, 298, 300, 300, 300, nil, nil, nil, nil, nil, nil, nil, nil, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 307, 307, 307, nil, nil, nil, nil, nil, 307, nil, nil, 307, 307, 307, 307, 307, 307, 307, 307, 307, 307, 307, 307, 307, 307, 238, 238, 238, nil, nil, nil, nil, nil, nil, nil, nil, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 184, nil, nil, nil, 184, nil, nil, nil, nil, nil, 184, nil, 184, 184 ] racc_action_pointer = [ 6, 363, nil, 0, nil, nil, nil, nil, nil, nil, 533, 60, nil, nil, nil, 558, nil, nil, nil, 299, nil, nil, nil, nil, nil, nil, nil, nil, 422, nil, nil, nil, nil, nil, nil, nil, nil, 344, 184, nil, 462, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 86, nil, nil, nil, nil, nil, 11, 379, 402, nil, 360, nil, 583, 385, 306, 608, 633, 658, 683, 708, 733, 758, 783, 808, 833, 858, 883, 305, nil, 302, 324, nil, nil, 295, 908, 933, 275, 958, 303, 508, nil, 295, 409, 12, 295, 244, 216, 178, 148, 146, 109, 9, 499, 483, 462, 463, 460, 983, 469, 65, nil, 1008, 462, 458, 1033, nil, 457, nil, 457, nil, 1058, 437, 354, nil, 457, nil, 386, nil, 481, 19, 459, 11, 461, 34, 1, 495, -5, 501, 34, 515, 15, 517, nil, 488, nil, 520, 526, 539, 540, 13, nil, nil, nil, 17, nil, nil, 0, nil, nil, 325, nil, 86, 122, 112, 171, nil, nil, 1083, 236, 69, 13, 181, 1108, 168, 1133, 1470, 66, 1158, 181, 183, 213, 65, 1183, 214, 215, 235, 255, 1208, 271, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1233, 263, 483, nil, nil, nil, nil, nil, nil, nil, 40, 1258, nil, 16, 268, nil, 1283, nil, 122, 270, nil, 1458, nil, 119, 116, 1308, nil, 95, 177, 299, 146, -2, -1, 1333, 372, nil, nil, nil, 139, nil, nil, 431, nil, 406, 405, nil, nil, nil, 411, 92, nil, nil, 1358, 408, nil, nil, nil, nil, 456, 90, nil, nil, nil, nil, 141, nil, nil, 232, nil, nil, nil, nil, 199, 201, nil, nil, 233, 261, nil, nil, nil, 1383, nil, 1408, nil, 457, 294, nil, nil, 170, 1433, 260, nil, nil, nil, 268, 386, nil, nil ] racc_action_default = [ -146, -177, -1, -146, -3, -5, -6, -7, -8, -9, -16, -177, -13, -14, -15, -107, -109, -110, -111, -98, -116, -117, -118, -119, -120, -121, -122, -123, -177, -126, -127, -128, -129, -130, -131, -145, -147, -177, -177, -4, -18, -17, -39, -40, -41, -42, -43, -44, -45, -46, -47, -48, -49, -50, -51, -52, -53, -54, -55, -177, -12, -32, -34, -35, -36, -64, -98, -177, -108, -99, -100, -177, -177, -98, -177, -177, -177, -177, -177, -177, -177, -177, -177, -177, -177, -177, -177, 316, -98, -177, -11, -33, -98, -177, -177, -98, -177, -177, -177, -101, -64, -177, -125, -177, -177, -150, -98, -98, -98, -98, -98, -150, -98, -98, -98, -98, -177, -177, -177, -20, -177, -30, -64, -177, -66, -177, -103, -23, -25, -177, -98, -177, -105, -98, -102, -177, -113, -177, -177, -132, -136, -98, -177, -138, -177, -140, -177, -142, -177, -144, -177, -148, -98, -151, -177, -177, -177, -177, -162, -10, -19, -21, -177, -31, -37, -98, -65, -67, -177, -24, -177, -177, -177, -177, -112, -114, -177, -177, -146, -135, -177, -177, -153, -154, -155, -146, -177, -146, -146, -177, -146, -177, -146, -146, -146, -177, -177, -30, -56, -57, -58, -59, -60, -68, -69, -70, -71, -72, -73, -74, -75, -76, -77, -78, -79, -80, -81, -177, -177, -177, -97, -26, -104, -29, -106, -115, -124, -146, -177, -166, -146, -152, -155, -177, -158, -146, -139, -169, -177, -62, -146, -146, -177, -160, -146, -146, -171, -146, -146, -146, -177, -27, -38, -82, -83, -177, -85, -87, -177, -89, -177, -177, -94, -133, -167, -162, -146, -156, -137, -177, -98, -63, -141, -143, -161, -177, -146, -168, -172, -173, -163, -174, -175, -22, -177, -84, -86, -88, -90, -177, -177, -93, -95, -177, -177, -134, -170, -61, -177, -149, -177, -28, -177, -177, -78, -91, -177, -177, -27, -176, -92, -96, -98, -98, -164, -159 ] racc_goto_table = [ 10, 72, 59, 10, 91, 132, 130, 195, 133, 68, 164, 203, 283, 270, 227, 202, 102, 175, 92, 37, 135, 235, 37, 274, 264, 159, 245, 124, 259, 99, 274, 274, 264, 182, 271, 119, 118, 2, 241, 170, 140, 271, 264, 248, 249, 88, 153, 139, 97, 143, 145, 147, 149, 134, 281, 103, 167, 177, 1, 175, 95, 256, 99, 264, 161, 202, 266, 288, 4, 313, 117, 39, 231, 264, 121, 165, 141, 126, 236, 222, 276, 224, 152, 246, 179, 202, 252, 40, 144, 146, 148, 150, 151, 183, 154, 155, 156, 157, 286, 99, 3, 137, 202, 99, 258, 99, 251, 99, 240, 99, 301, 292, 171, 247, 294, 173, 304, 91, 255, 123, 67, nil, 172, 180, nil, nil, 41, 65, nil, nil, nil, 202, 311, nil, 189, 137, nil, 202, 137, 99, nil, nil, nil, nil, nil, nil, nil, 197, nil, nil, 202, nil, nil, 202, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 201, nil, nil, nil, nil, nil, nil, 65, nil, 137, nil, nil, nil, nil, nil, nil, nil, nil, nil, 100, nil, nil, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, nil, nil, nil, nil, 238, 242, 308, 122, 125, 238, 242, 242, nil, nil, nil, 312, 201, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 158, nil, nil, nil, 162, nil, 201, 125, nil, nil, nil, nil, nil, nil, nil, 65, nil, nil, nil, nil, 297, nil, nil, 201, nil, nil, 184, 238, 242, nil, nil, nil, nil, nil, 238, 242, 242, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 201, nil, nil, nil, 65, nil, 201, nil, 225, nil, 314, 315, nil, 232, nil, 234, nil, nil, 237, 201, nil, nil, 201, 237, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 253, nil, 260, nil, nil, nil, nil, nil, nil, nil, nil, 265, nil, nil, nil, nil, 267, nil, nil, nil, nil, nil, nil, nil, nil, 275, nil, nil, nil, nil, nil, nil, nil, 282, nil, nil, nil, nil, nil, nil, nil, 289, nil, nil, 293, nil, nil, nil, nil, nil, nil, nil, 296, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 309, nil, 293 ] racc_goto_check = [ 10, 13, 15, 10, 24, 14, 19, 80, 19, 29, 23, 37, 20, 31, 72, 30, 49, 56, 28, 33, 55, 72, 33, 79, 81, 14, 72, 36, 46, 50, 79, 79, 81, 77, 32, 18, 17, 2, 75, 19, 71, 32, 81, 75, 75, 12, 71, 49, 13, 49, 49, 49, 49, 28, 82, 13, 36, 55, 1, 56, 29, 37, 50, 81, 18, 30, 72, 46, 4, 20, 13, 4, 77, 81, 13, 28, 73, 13, 74, 14, 72, 14, 73, 74, 49, 30, 23, 11, 13, 13, 13, 13, 13, 78, 13, 13, 13, 13, 37, 50, 3, 10, 30, 50, 45, 50, 19, 50, 34, 50, 22, 48, 13, 34, 80, 13, 22, 24, 44, 35, 51, nil, 15, 13, nil, nil, 16, 16, nil, nil, nil, 30, 22, nil, 13, 10, nil, 30, 10, 50, nil, nil, nil, nil, nil, nil, nil, 13, nil, nil, 30, nil, nil, 30, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, nil, nil, 16, nil, 10, nil, nil, nil, nil, nil, nil, nil, nil, nil, 16, nil, nil, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, nil, nil, nil, nil, 33, 33, 19, 16, 16, 33, 33, 33, nil, nil, nil, 19, 10, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 16, nil, nil, nil, 16, nil, 10, 16, nil, nil, nil, nil, nil, nil, nil, 16, nil, nil, nil, nil, 13, nil, nil, 10, nil, nil, 16, 33, 33, nil, nil, nil, nil, nil, 33, 33, 33, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, 16, nil, 10, nil, 16, nil, 13, 13, nil, 16, nil, 16, nil, nil, 16, 10, nil, nil, 10, 16, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 16, nil, 16, nil, nil, nil, nil, nil, nil, nil, nil, 16, nil, nil, nil, nil, 16, nil, nil, nil, nil, nil, nil, nil, nil, 16, nil, nil, nil, nil, nil, nil, nil, 16, nil, nil, nil, nil, nil, nil, nil, 16, nil, nil, 16, nil, nil, nil, nil, nil, nil, nil, 16, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 16, nil, 16 ] racc_goto_pointer = [ nil, 58, 37, 100, 68, nil, nil, nil, nil, nil, 0, 77, 5, -18, -92, -9, 116, -53, -54, -90, -239, nil, -174, -111, -55, nil, nil, nil, -47, -6, -153, -225, -206, 19, -79, 25, -67, -157, nil, nil, nil, nil, nil, nil, -100, -115, -191, nil, -150, -57, -40, 105, nil, nil, nil, -81, -118, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, -65, -164, -29, -108, -150, nil, -109, -49, -218, -151, -203, -196 ] racc_goto_default = [ nil, nil, nil, nil, nil, 5, 6, 7, 8, 9, 57, nil, nil, nil, 163, nil, 128, nil, nil, nil, nil, 127, 213, nil, 61, 62, 63, 64, nil, 42, 58, 220, 239, 228, nil, nil, nil, 305, 209, 210, 211, 212, 214, 215, nil, nil, nil, 261, 262, 69, 70, nil, 16, 17, 18, nil, 136, 20, 21, 22, 23, 24, 25, 26, 27, 29, 30, 31, 32, 33, 34, nil, nil, nil, nil, nil, 36, nil, nil, 243, nil, 229, nil ] racc_reduce_table = [ 0, 0, :racc_error, 1, 39, :_reduce_none, 1, 40, :_reduce_2, 1, 41, :_reduce_3, 2, 41, :_reduce_4, 1, 42, :_reduce_none, 1, 42, :_reduce_none, 1, 42, :_reduce_none, 1, 43, :_reduce_none, 1, 43, :_reduce_none, 5, 46, :_reduce_10, 3, 46, :_reduce_11, 2, 46, :_reduce_12, 1, 48, :_reduce_none, 1, 48, :_reduce_none, 1, 48, :_reduce_none, 0, 49, :_reduce_16, 1, 49, :_reduce_none, 0, 50, :_reduce_18, 3, 50, :_reduce_19, 1, 55, :_reduce_20, 2, 55, :_reduce_21, 5, 56, :_reduce_22, 1, 57, :_reduce_23, 2, 57, :_reduce_24, 1, 59, :_reduce_25, 3, 59, :_reduce_26, 0, 58, :_reduce_27, 2, 58, :_reduce_28, 3, 52, :_reduce_29, 0, 61, :_reduce_30, 1, 61, :_reduce_31, 1, 53, :_reduce_32, 2, 53, :_reduce_33, 1, 62, :_reduce_none, 1, 62, :_reduce_none, 1, 62, :_reduce_none, 4, 63, :_reduce_37, 6, 63, :_reduce_38, 1, 54, :_reduce_none, 1, 54, :_reduce_none, 1, 68, :_reduce_none, 1, 68, :_reduce_none, 1, 68, :_reduce_none, 1, 68, :_reduce_none, 1, 68, :_reduce_none, 1, 68, :_reduce_none, 1, 68, :_reduce_none, 1, 68, :_reduce_none, 1, 68, :_reduce_none, 1, 67, :_reduce_none, 1, 67, :_reduce_none, 1, 67, :_reduce_none, 1, 67, :_reduce_none, 1, 67, :_reduce_none, 1, 67, :_reduce_none, 1, 69, :_reduce_none, 1, 69, :_reduce_none, 1, 69, :_reduce_none, 1, 69, :_reduce_none, 1, 69, :_reduce_none, 3, 70, :_reduce_61, 1, 72, :_reduce_62, 2, 72, :_reduce_63, 0, 66, :_reduce_64, 3, 66, :_reduce_65, 1, 73, :_reduce_66, 2, 73, :_reduce_67, 3, 74, :_reduce_68, 1, 60, :_reduce_69, 1, 60, :_reduce_70, 1, 60, :_reduce_71, 1, 60, :_reduce_72, 1, 60, :_reduce_73, 1, 60, :_reduce_none, 1, 60, :_reduce_none, 1, 60, :_reduce_none, 1, 60, :_reduce_none, 1, 75, :_reduce_none, 1, 75, :_reduce_none, 1, 75, :_reduce_none, 1, 76, :_reduce_81, 2, 80, :_reduce_82, 2, 78, :_reduce_83, 3, 78, :_reduce_84, 1, 82, :_reduce_85, 2, 82, :_reduce_86, 2, 81, :_reduce_87, 3, 81, :_reduce_88, 1, 83, :_reduce_89, 2, 83, :_reduce_90, 3, 84, :_reduce_91, 2, 79, :_reduce_92, 3, 79, :_reduce_93, 1, 85, :_reduce_94, 2, 85, :_reduce_95, 3, 86, :_reduce_96, 1, 77, :_reduce_97, 0, 51, :_reduce_98, 1, 51, :_reduce_none, 1, 87, :_reduce_100, 2, 87, :_reduce_101, 3, 88, :_reduce_102, 3, 64, :_reduce_103, 5, 65, :_reduce_104, 3, 65, :_reduce_105, 6, 47, :_reduce_106, 0, 89, :_reduce_107, 1, 89, :_reduce_none, 1, 44, :_reduce_none, 1, 44, :_reduce_none, 1, 44, :_reduce_none, 5, 90, :_reduce_112, 1, 93, :_reduce_none, 2, 93, :_reduce_114, 3, 94, :_reduce_115, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 91, :_reduce_none, 1, 45, :_reduce_none, 1, 45, :_reduce_none, 6, 101, :_reduce_124, 3, 101, :_reduce_125, 1, 102, :_reduce_none, 1, 102, :_reduce_none, 1, 102, :_reduce_none, 1, 102, :_reduce_none, 1, 102, :_reduce_none, 1, 102, :_reduce_none, 4, 103, :_reduce_132, 7, 104, :_reduce_133, 8, 104, :_reduce_134, 5, 104, :_reduce_135, 4, 104, :_reduce_136, 7, 105, :_reduce_137, 4, 105, :_reduce_138, 6, 106, :_reduce_139, 4, 106, :_reduce_140, 7, 107, :_reduce_141, 4, 107, :_reduce_142, 7, 108, :_reduce_143, 4, 108, :_reduce_144, 1, 114, :_reduce_none, 0, 71, :_reduce_none, 1, 71, :_reduce_none, 4, 95, :_reduce_148, 8, 96, :_reduce_149, 0, 111, :_reduce_150, 1, 111, :_reduce_none, 3, 109, :_reduce_152, 2, 109, :_reduce_153, 2, 109, :_reduce_154, 1, 115, :_reduce_155, 3, 115, :_reduce_156, 1, 116, :_reduce_157, 2, 116, :_reduce_158, 6, 117, :_reduce_159, 1, 113, :_reduce_160, 2, 113, :_reduce_161, 0, 118, :_reduce_162, 3, 118, :_reduce_163, 6, 119, :_reduce_164, 0, 110, :_reduce_165, 1, 110, :_reduce_166, 2, 110, :_reduce_167, 7, 97, :_reduce_168, 1, 112, :_reduce_169, 3, 112, :_reduce_170, 6, 98, :_reduce_171, 7, 99, :_reduce_172, 7, 100, :_reduce_173, 7, 92, :_reduce_174, 1, 120, :_reduce_175, 3, 120, :_reduce_176 ] racc_reduce_n = 177 racc_shift_n = 316 racc_token_table = { false => 0, :error => 1, :LCURLY => 2, :RCURLY => 3, :QUERY => 4, :MUTATION => 5, :SUBSCRIPTION => 6, :LPAREN => 7, :RPAREN => 8, :VAR_SIGN => 9, :COLON => 10, :BANG => 11, :LBRACKET => 12, :RBRACKET => 13, :EQUALS => 14, :ON => 15, :SCHEMA => 16, :SCALAR => 17, :TYPE => 18, :IMPLEMENTS => 19, :INTERFACE => 20, :UNION => 21, :ENUM => 22, :INPUT => 23, :DIRECTIVE => 24, :IDENTIFIER => 25, :FRAGMENT => 26, :TRUE => 27, :FALSE => 28, :FLOAT => 29, :INT => 30, :STRING => 31, :NULL => 32, :DIR_SIGN => 33, :ELLIPSIS => 34, :EXTEND => 35, :AMP => 36, :PIPE => 37 } racc_nt_base = 38 racc_use_result_var = true Racc_arg = [ racc_action_table, racc_action_check, racc_action_default, racc_action_pointer, racc_goto_table, racc_goto_check, racc_goto_default, racc_goto_pointer, racc_nt_base, racc_reduce_table, racc_token_table, racc_shift_n, racc_reduce_n, racc_use_result_var ] Racc_token_to_s_table = [ "$end", "error", "LCURLY", "RCURLY", "QUERY", "MUTATION", "SUBSCRIPTION", "LPAREN", "RPAREN", "VAR_SIGN", "COLON", "BANG", "LBRACKET", "RBRACKET", "EQUALS", "ON", "SCHEMA", "SCALAR", "TYPE", "IMPLEMENTS", "INTERFACE", "UNION", "ENUM", "INPUT", "DIRECTIVE", "IDENTIFIER", "FRAGMENT", "TRUE", "FALSE", "FLOAT", "INT", "STRING", "NULL", "DIR_SIGN", "ELLIPSIS", "EXTEND", "AMP", "PIPE", "$start", "target", "document", "definitions_list", "definition", "executable_definition", "type_system_definition", "type_system_extension", "operation_definition", "fragment_definition", "operation_type", "operation_name_opt", "variable_definitions_opt", "directives_list_opt", "selection_set", "selection_list", "name", "variable_definitions_list", "variable_definition", "type", "default_value_opt", "nullable_type", "literal_value", "selection_set_opt", "selection", "field", "fragment_spread", "inline_fragment", "arguments_opt", "name_without_on", "schema_keyword", "enum_name", "enum_value_definition", "description_opt", "enum_value_definitions", "arguments_list", "argument", "input_value", "null_value", "enum_value", "list_value", "object_literal_value", "variable", "object_value", "list_value_list", "object_value_list", "object_value_field", "object_literal_value_list", "object_literal_value_field", "directives_list", "directive", "fragment_name_opt", "schema_definition", "type_definition", "directive_definition", "operation_type_definition_list", "operation_type_definition", "scalar_type_definition", "object_type_definition", "interface_type_definition", "union_type_definition", "enum_type_definition", "input_object_type_definition", "schema_extension", "type_extension", "scalar_type_extension", "object_type_extension", "interface_type_extension", "union_type_extension", "enum_type_extension", "input_object_type_extension", "implements", "field_definition_list", "implements_opt", "union_members", "input_value_definition_list", "description", "interfaces_list", "legacy_interfaces_list", "input_value_definition", "arguments_definitions_opt", "field_definition", "directive_locations" ] Racc_debug_parser = false ##### State transition tables end ##### # reduce 0 omitted # reduce 1 omitted module_eval(<<'.,.,', 'parser.y', 4) def _reduce_2(val, _values, result) result = make_node(:Document, definitions: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 7) def _reduce_3(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 8) def _reduce_4(val, _values, result) val[0] << val[1] result end .,., # reduce 5 omitted # reduce 6 omitted # reduce 7 omitted # reduce 8 omitted # reduce 9 omitted module_eval(<<'.,.,', 'parser.y', 21) def _reduce_10(val, _values, result) result = make_node( :OperationDefinition, { operation_type: val[0], name: val[1], variables: val[2], directives: val[3], selections: val[4], position_source: val[0], } ) result end .,., module_eval(<<'.,.,', 'parser.y', 33) def _reduce_11(val, _values, result) result = make_node( :OperationDefinition, { operation_type: "query", selections: val[1], position_source: val[0], } ) result end .,., module_eval(<<'.,.,', 'parser.y', 42) def _reduce_12(val, _values, result) result = make_node( :OperationDefinition, { operation_type: "query", selections: [], position_source: val[0], } ) result end .,., # reduce 13 omitted # reduce 14 omitted # reduce 15 omitted module_eval(<<'.,.,', 'parser.y', 57) def _reduce_16(val, _values, result) result = nil result end .,., # reduce 17 omitted module_eval(<<'.,.,', 'parser.y', 61) def _reduce_18(val, _values, result) result = EMPTY_ARRAY result end .,., module_eval(<<'.,.,', 'parser.y', 62) def _reduce_19(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 65) def _reduce_20(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 66) def _reduce_21(val, _values, result) val[0] << val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 70) def _reduce_22(val, _values, result) result = make_node(:VariableDefinition, { name: val[1], type: val[3], default_value: val[4], position_source: val[0], }) result end .,., module_eval(<<'.,.,', 'parser.y', 79) def _reduce_23(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'parser.y', 80) def _reduce_24(val, _values, result) result = make_node(:NonNullType, of_type: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 83) def _reduce_25(val, _values, result) result = make_node(:TypeName, name: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 84) def _reduce_26(val, _values, result) result = make_node(:ListType, of_type: val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 87) def _reduce_27(val, _values, result) result = nil result end .,., module_eval(<<'.,.,', 'parser.y', 88) def _reduce_28(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 91) def _reduce_29(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 94) def _reduce_30(val, _values, result) result = EMPTY_ARRAY result end .,., module_eval(<<'.,.,', 'parser.y', 95) def _reduce_31(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'parser.y', 98) def _reduce_32(val, _values, result) result = [result] result end .,., module_eval(<<'.,.,', 'parser.y', 99) def _reduce_33(val, _values, result) val[0] << val[1] result end .,., # reduce 34 omitted # reduce 35 omitted # reduce 36 omitted module_eval(<<'.,.,', 'parser.y', 108) def _reduce_37(val, _values, result) result = make_node( :Field, { name: val[0], arguments: val[1], directives: val[2], selections: val[3], position_source: val[0], } ) result end .,., module_eval(<<'.,.,', 'parser.y', 119) def _reduce_38(val, _values, result) result = make_node( :Field, { alias: val[0], name: val[2], arguments: val[3], directives: val[4], selections: val[5], position_source: val[0], } ) result end .,., # reduce 39 omitted # reduce 40 omitted # reduce 41 omitted # reduce 42 omitted # reduce 43 omitted # reduce 44 omitted # reduce 45 omitted # reduce 46 omitted # reduce 47 omitted # reduce 48 omitted # reduce 49 omitted # reduce 50 omitted # reduce 51 omitted # reduce 52 omitted # reduce 53 omitted # reduce 54 omitted # reduce 55 omitted # reduce 56 omitted # reduce 57 omitted # reduce 58 omitted # reduce 59 omitted # reduce 60 omitted module_eval(<<'.,.,', 'parser.y', 162) def _reduce_61(val, _values, result) result = make_node(:EnumValueDefinition, name: val[1], directives: val[2], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 165) def _reduce_62(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 166) def _reduce_63(val, _values, result) result = val[0] << val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 169) def _reduce_64(val, _values, result) result = EMPTY_ARRAY result end .,., module_eval(<<'.,.,', 'parser.y', 170) def _reduce_65(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 173) def _reduce_66(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 174) def _reduce_67(val, _values, result) val[0] << val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 177) def _reduce_68(val, _values, result) result = make_node(:Argument, name: val[0], value: val[2], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 180) def _reduce_69(val, _values, result) result = val[0].to_f result end .,., module_eval(<<'.,.,', 'parser.y', 181) def _reduce_70(val, _values, result) result = val[0].to_i result end .,., module_eval(<<'.,.,', 'parser.y', 182) def _reduce_71(val, _values, result) result = val[0].to_s result end .,., module_eval(<<'.,.,', 'parser.y', 183) def _reduce_72(val, _values, result) result = true result end .,., module_eval(<<'.,.,', 'parser.y', 184) def _reduce_73(val, _values, result) result = false result end .,., # reduce 74 omitted # reduce 75 omitted # reduce 76 omitted # reduce 77 omitted # reduce 78 omitted # reduce 79 omitted # reduce 80 omitted module_eval(<<'.,.,', 'parser.y', 195) def _reduce_81(val, _values, result) result = make_node(:NullValue, name: val[0], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 196) def _reduce_82(val, _values, result) result = make_node(:VariableIdentifier, name: val[1], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 199) def _reduce_83(val, _values, result) result = EMPTY_ARRAY result end .,., module_eval(<<'.,.,', 'parser.y', 200) def _reduce_84(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 203) def _reduce_85(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 204) def _reduce_86(val, _values, result) val[0] << val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 207) def _reduce_87(val, _values, result) result = make_node(:InputObject, arguments: [], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 208) def _reduce_88(val, _values, result) result = make_node(:InputObject, arguments: val[1], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 211) def _reduce_89(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 212) def _reduce_90(val, _values, result) val[0] << val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 215) def _reduce_91(val, _values, result) result = make_node(:Argument, name: val[0], value: val[2], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 219) def _reduce_92(val, _values, result) result = make_node(:InputObject, arguments: [], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 220) def _reduce_93(val, _values, result) result = make_node(:InputObject, arguments: val[1], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 223) def _reduce_94(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 224) def _reduce_95(val, _values, result) val[0] << val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 227) def _reduce_96(val, _values, result) result = make_node(:Argument, name: val[0], value: val[2], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 229) def _reduce_97(val, _values, result) result = make_node(:Enum, name: val[0], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 232) def _reduce_98(val, _values, result) result = EMPTY_ARRAY result end .,., # reduce 99 omitted module_eval(<<'.,.,', 'parser.y', 236) def _reduce_100(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 237) def _reduce_101(val, _values, result) val[0] << val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 239) def _reduce_102(val, _values, result) result = make_node(:Directive, name: val[1], arguments: val[2], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 242) def _reduce_103(val, _values, result) result = make_node(:FragmentSpread, name: val[1], directives: val[2], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 246) def _reduce_104(val, _values, result) result = make_node(:InlineFragment, { type: val[2], directives: val[3], selections: val[4], position_source: val[0] }) result end .,., module_eval(<<'.,.,', 'parser.y', 254) def _reduce_105(val, _values, result) result = make_node(:InlineFragment, { type: nil, directives: val[1], selections: val[2], position_source: val[0] }) result end .,., module_eval(<<'.,.,', 'parser.y', 264) def _reduce_106(val, _values, result) result = make_node(:FragmentDefinition, { name: val[1], type: val[3], directives: val[4], selections: val[5], position_source: val[0], } ) result end .,., module_eval(<<'.,.,', 'parser.y', 275) def _reduce_107(val, _values, result) result = nil result end .,., # reduce 108 omitted # reduce 109 omitted # reduce 110 omitted # reduce 111 omitted module_eval(<<'.,.,', 'parser.y', 284) def _reduce_112(val, _values, result) result = make_node(:SchemaDefinition, position_source: val[0], definition_line: val[0].line, directives: val[1], **val[3]) result end .,., # reduce 113 omitted module_eval(<<'.,.,', 'parser.y', 288) def _reduce_114(val, _values, result) result = val[0].merge(val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 291) def _reduce_115(val, _values, result) result = { val[0].to_s.to_sym => val[2] } result end .,., # reduce 116 omitted # reduce 117 omitted # reduce 118 omitted # reduce 119 omitted # reduce 120 omitted # reduce 121 omitted # reduce 122 omitted # reduce 123 omitted module_eval(<<'.,.,', 'parser.y', 306) def _reduce_124(val, _values, result) result = make_node(:SchemaExtension, position_source: val[0], directives: val[2], **val[4]) result end .,., module_eval(<<'.,.,', 'parser.y', 307) def _reduce_125(val, _values, result) result = make_node(:SchemaExtension, position_source: val[0], directives: val[2]) result end .,., # reduce 126 omitted # reduce 127 omitted # reduce 128 omitted # reduce 129 omitted # reduce 130 omitted # reduce 131 omitted module_eval(<<'.,.,', 'parser.y', 317) def _reduce_132(val, _values, result) result = make_node(:ScalarTypeExtension, name: val[2], directives: val[3], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 321) def _reduce_133(val, _values, result) result = make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: [], fields: val[5], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 322) def _reduce_134(val, _values, result) result = make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: val[4], fields: val[6], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 323) def _reduce_135(val, _values, result) result = make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: val[4], fields: [], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 324) def _reduce_136(val, _values, result) result = make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: [], fields: [], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 327) def _reduce_137(val, _values, result) result = make_node(:InterfaceTypeExtension, name: val[2], directives: val[3], fields: val[5], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 328) def _reduce_138(val, _values, result) result = make_node(:InterfaceTypeExtension, name: val[2], directives: val[3], fields: [], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 331) def _reduce_139(val, _values, result) result = make_node(:UnionTypeExtension, name: val[2], directives: val[3], types: val[5], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 332) def _reduce_140(val, _values, result) result = make_node(:UnionTypeExtension, name: val[2], directives: val[3], types: [], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 335) def _reduce_141(val, _values, result) result = make_node(:EnumTypeExtension, name: val[2], directives: val[3], values: val[5], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 336) def _reduce_142(val, _values, result) result = make_node(:EnumTypeExtension, name: val[2], directives: val[3], values: [], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 339) def _reduce_143(val, _values, result) result = make_node(:InputObjectTypeExtension, name: val[2], directives: val[3], fields: val[5], position_source: val[0]) result end .,., module_eval(<<'.,.,', 'parser.y', 340) def _reduce_144(val, _values, result) result = make_node(:InputObjectTypeExtension, name: val[2], directives: val[3], fields: [], position_source: val[0]) result end .,., # reduce 145 omitted # reduce 146 omitted # reduce 147 omitted module_eval(<<'.,.,', 'parser.y', 350) def _reduce_148(val, _values, result) result = make_node(:ScalarTypeDefinition, name: val[2], directives: val[3], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 355) def _reduce_149(val, _values, result) result = make_node(:ObjectTypeDefinition, name: val[2], interfaces: val[3], directives: val[4], fields: val[6], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 359) def _reduce_150(val, _values, result) result = EMPTY_ARRAY result end .,., # reduce 151 omitted module_eval(<<'.,.,', 'parser.y', 363) def _reduce_152(val, _values, result) result = val[2] result end .,., module_eval(<<'.,.,', 'parser.y', 364) def _reduce_153(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 365) def _reduce_154(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 368) def _reduce_155(val, _values, result) result = [make_node(:TypeName, name: val[0], position_source: val[0])] result end .,., module_eval(<<'.,.,', 'parser.y', 369) def _reduce_156(val, _values, result) val[0] << make_node(:TypeName, name: val[2], position_source: val[2]) result end .,., module_eval(<<'.,.,', 'parser.y', 372) def _reduce_157(val, _values, result) result = [make_node(:TypeName, name: val[0], position_source: val[0])] result end .,., module_eval(<<'.,.,', 'parser.y', 373) def _reduce_158(val, _values, result) val[0] << make_node(:TypeName, name: val[1], position_source: val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 377) def _reduce_159(val, _values, result) result = make_node(:InputValueDefinition, name: val[1], type: val[3], default_value: val[4], directives: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 381) def _reduce_160(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 382) def _reduce_161(val, _values, result) val[0] << val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 385) def _reduce_162(val, _values, result) result = EMPTY_ARRAY result end .,., module_eval(<<'.,.,', 'parser.y', 386) def _reduce_163(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 390) def _reduce_164(val, _values, result) result = make_node(:FieldDefinition, name: val[1], arguments: val[2], type: val[4], directives: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 394) def _reduce_165(val, _values, result) result = EMPTY_ARRAY result end .,., module_eval(<<'.,.,', 'parser.y', 395) def _reduce_166(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'parser.y', 396) def _reduce_167(val, _values, result) val[0] << val[1] result end .,., module_eval(<<'.,.,', 'parser.y', 400) def _reduce_168(val, _values, result) result = make_node(:InterfaceTypeDefinition, name: val[2], directives: val[3], fields: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 404) def _reduce_169(val, _values, result) result = [make_node(:TypeName, name: val[0], position_source: val[0])] result end .,., module_eval(<<'.,.,', 'parser.y', 405) def _reduce_170(val, _values, result) val[0] << make_node(:TypeName, name: val[2], position_source: val[2]) result end .,., module_eval(<<'.,.,', 'parser.y', 409) def _reduce_171(val, _values, result) result = make_node(:UnionTypeDefinition, name: val[2], directives: val[3], types: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 414) def _reduce_172(val, _values, result) result = make_node(:EnumTypeDefinition, name: val[2], directives: val[3], values: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 419) def _reduce_173(val, _values, result) result = make_node(:InputObjectTypeDefinition, name: val[2], directives: val[3], fields: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 424) def _reduce_174(val, _values, result) result = make_node(:DirectiveDefinition, name: val[3], arguments: val[4], locations: val[6], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) result end .,., module_eval(<<'.,.,', 'parser.y', 428) def _reduce_175(val, _values, result) result = [make_node(:DirectiveLocation, name: val[0].to_s, position_source: val[0])] result end .,., module_eval(<<'.,.,', 'parser.y', 429) def _reduce_176(val, _values, result) val[0] << make_node(:DirectiveLocation, name: val[2].to_s, position_source: val[2]) result end .,., def _reduce_none(val, _values, result) val[0] end end # class Parser end # module Language end # module GraphQL graphql-ruby-1.11.10/lib/graphql/language/parser.y000066400000000000000000000476121414121453000217520ustar00rootroot00000000000000class GraphQL::Language::Parser rule target: document document: definitions_list { result = make_node(:Document, definitions: val[0])} definitions_list: definition { result = [val[0]]} | definitions_list definition { val[0] << val[1] } definition: executable_definition | type_system_definition | type_system_extension executable_definition: operation_definition | fragment_definition operation_definition: operation_type operation_name_opt variable_definitions_opt directives_list_opt selection_set { result = make_node( :OperationDefinition, { operation_type: val[0], name: val[1], variables: val[2], directives: val[3], selections: val[4], position_source: val[0], } ) } | LCURLY selection_list RCURLY { result = make_node( :OperationDefinition, { operation_type: "query", selections: val[1], position_source: val[0], } ) } | LCURLY RCURLY { result = make_node( :OperationDefinition, { operation_type: "query", selections: [], position_source: val[0], } ) } operation_type: QUERY | MUTATION | SUBSCRIPTION operation_name_opt: /* none */ { result = nil } | name variable_definitions_opt: /* none */ { result = EMPTY_ARRAY } | LPAREN variable_definitions_list RPAREN { result = val[1] } variable_definitions_list: variable_definition { result = [val[0]] } | variable_definitions_list variable_definition { val[0] << val[1] } variable_definition: VAR_SIGN name COLON type default_value_opt { result = make_node(:VariableDefinition, { name: val[1], type: val[3], default_value: val[4], position_source: val[0], }) } type: nullable_type { result = val[0] } | nullable_type BANG { result = make_node(:NonNullType, of_type: val[0]) } nullable_type: name { result = make_node(:TypeName, name: val[0])} | LBRACKET type RBRACKET { result = make_node(:ListType, of_type: val[1]) } default_value_opt: /* none */ { result = nil } | EQUALS literal_value { result = val[1] } selection_set: LCURLY selection_list RCURLY { result = val[1] } selection_set_opt: /* none */ { result = EMPTY_ARRAY } | selection_set { result = val[0] } selection_list: selection { result = [result] } | selection_list selection { val[0] << val[1] } selection: field | fragment_spread | inline_fragment field: name arguments_opt directives_list_opt selection_set_opt { result = make_node( :Field, { name: val[0], arguments: val[1], directives: val[2], selections: val[3], position_source: val[0], } ) } | name COLON name arguments_opt directives_list_opt selection_set_opt { result = make_node( :Field, { alias: val[0], name: val[2], arguments: val[3], directives: val[4], selections: val[5], position_source: val[0], } ) } name: name_without_on | ON schema_keyword: SCHEMA | SCALAR | TYPE | IMPLEMENTS | INTERFACE | UNION | ENUM | INPUT | DIRECTIVE name_without_on: IDENTIFIER | FRAGMENT | TRUE | FALSE | operation_type | schema_keyword enum_name: /* any identifier, but not "true", "false" or "null" */ IDENTIFIER | FRAGMENT | ON | operation_type | schema_keyword enum_value_definition: description_opt enum_name directives_list_opt { result = make_node(:EnumValueDefinition, name: val[1], directives: val[2], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } enum_value_definitions: enum_value_definition { result = [val[0]] } | enum_value_definitions enum_value_definition { result = val[0] << val[1] } arguments_opt: /* none */ { result = EMPTY_ARRAY } | LPAREN arguments_list RPAREN { result = val[1] } arguments_list: argument { result = [val[0]] } | arguments_list argument { val[0] << val[1] } argument: name COLON input_value { result = make_node(:Argument, name: val[0], value: val[2], position_source: val[0])} literal_value: FLOAT { result = val[0].to_f } | INT { result = val[0].to_i } | STRING { result = val[0].to_s } | TRUE { result = true } | FALSE { result = false } | null_value | enum_value | list_value | object_literal_value input_value: literal_value | variable | object_value null_value: NULL { result = make_node(:NullValue, name: val[0], position_source: val[0]) } variable: VAR_SIGN name { result = make_node(:VariableIdentifier, name: val[1], position_source: val[0]) } list_value: LBRACKET RBRACKET { result = EMPTY_ARRAY } | LBRACKET list_value_list RBRACKET { result = val[1] } list_value_list: input_value { result = [val[0]] } | list_value_list input_value { val[0] << val[1] } object_value: LCURLY RCURLY { result = make_node(:InputObject, arguments: [], position_source: val[0])} | LCURLY object_value_list RCURLY { result = make_node(:InputObject, arguments: val[1], position_source: val[0])} object_value_list: object_value_field { result = [val[0]] } | object_value_list object_value_field { val[0] << val[1] } object_value_field: name COLON input_value { result = make_node(:Argument, name: val[0], value: val[2], position_source: val[0])} /* like the previous, but with literals only: */ object_literal_value: LCURLY RCURLY { result = make_node(:InputObject, arguments: [], position_source: val[0])} | LCURLY object_literal_value_list RCURLY { result = make_node(:InputObject, arguments: val[1], position_source: val[0])} object_literal_value_list: object_literal_value_field { result = [val[0]] } | object_literal_value_list object_literal_value_field { val[0] << val[1] } object_literal_value_field: name COLON literal_value { result = make_node(:Argument, name: val[0], value: val[2], position_source: val[0])} enum_value: enum_name { result = make_node(:Enum, name: val[0], position_source: val[0]) } directives_list_opt: /* none */ { result = EMPTY_ARRAY } | directives_list directives_list: directive { result = [val[0]] } | directives_list directive { val[0] << val[1] } directive: DIR_SIGN name arguments_opt { result = make_node(:Directive, name: val[1], arguments: val[2], position_source: val[0]) } fragment_spread: ELLIPSIS name_without_on directives_list_opt { result = make_node(:FragmentSpread, name: val[1], directives: val[2], position_source: val[0]) } inline_fragment: ELLIPSIS ON type directives_list_opt selection_set { result = make_node(:InlineFragment, { type: val[2], directives: val[3], selections: val[4], position_source: val[0] }) } | ELLIPSIS directives_list_opt selection_set { result = make_node(:InlineFragment, { type: nil, directives: val[1], selections: val[2], position_source: val[0] }) } fragment_definition: FRAGMENT fragment_name_opt ON type directives_list_opt selection_set { result = make_node(:FragmentDefinition, { name: val[1], type: val[3], directives: val[4], selections: val[5], position_source: val[0], } ) } fragment_name_opt: /* none */ { result = nil } | name_without_on type_system_definition: schema_definition | type_definition | directive_definition schema_definition: SCHEMA directives_list_opt LCURLY operation_type_definition_list RCURLY { result = make_node(:SchemaDefinition, position_source: val[0], definition_line: val[0].line, directives: val[1], **val[3]) } operation_type_definition_list: operation_type_definition | operation_type_definition_list operation_type_definition { result = val[0].merge(val[1]) } operation_type_definition: operation_type COLON name { result = { val[0].to_s.to_sym => val[2] } } type_definition: scalar_type_definition | object_type_definition | interface_type_definition | union_type_definition | enum_type_definition | input_object_type_definition type_system_extension: schema_extension | type_extension schema_extension: EXTEND SCHEMA directives_list_opt LCURLY operation_type_definition_list RCURLY { result = make_node(:SchemaExtension, position_source: val[0], directives: val[2], **val[4]) } | EXTEND SCHEMA directives_list { result = make_node(:SchemaExtension, position_source: val[0], directives: val[2]) } type_extension: scalar_type_extension | object_type_extension | interface_type_extension | union_type_extension | enum_type_extension | input_object_type_extension scalar_type_extension: EXTEND SCALAR name directives_list { result = make_node(:ScalarTypeExtension, name: val[2], directives: val[3], position_source: val[0]) } object_type_extension: /* TODO - This first one shouldn't be necessary but parser is getting confused */ EXTEND TYPE name implements LCURLY field_definition_list RCURLY { result = make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: [], fields: val[5], position_source: val[0]) } | EXTEND TYPE name implements_opt directives_list_opt LCURLY field_definition_list RCURLY { result = make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: val[4], fields: val[6], position_source: val[0]) } | EXTEND TYPE name implements_opt directives_list { result = make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: val[4], fields: [], position_source: val[0]) } | EXTEND TYPE name implements { result = make_node(:ObjectTypeExtension, name: val[2], interfaces: val[3], directives: [], fields: [], position_source: val[0]) } interface_type_extension: EXTEND INTERFACE name directives_list_opt LCURLY field_definition_list RCURLY { result = make_node(:InterfaceTypeExtension, name: val[2], directives: val[3], fields: val[5], position_source: val[0]) } | EXTEND INTERFACE name directives_list { result = make_node(:InterfaceTypeExtension, name: val[2], directives: val[3], fields: [], position_source: val[0]) } union_type_extension: EXTEND UNION name directives_list_opt EQUALS union_members { result = make_node(:UnionTypeExtension, name: val[2], directives: val[3], types: val[5], position_source: val[0]) } | EXTEND UNION name directives_list { result = make_node(:UnionTypeExtension, name: val[2], directives: val[3], types: [], position_source: val[0]) } enum_type_extension: EXTEND ENUM name directives_list_opt LCURLY enum_value_definitions RCURLY { result = make_node(:EnumTypeExtension, name: val[2], directives: val[3], values: val[5], position_source: val[0]) } | EXTEND ENUM name directives_list { result = make_node(:EnumTypeExtension, name: val[2], directives: val[3], values: [], position_source: val[0]) } input_object_type_extension: EXTEND INPUT name directives_list_opt LCURLY input_value_definition_list RCURLY { result = make_node(:InputObjectTypeExtension, name: val[2], directives: val[3], fields: val[5], position_source: val[0]) } | EXTEND INPUT name directives_list { result = make_node(:InputObjectTypeExtension, name: val[2], directives: val[3], fields: [], position_source: val[0]) } description: STRING description_opt: /* none */ | description scalar_type_definition: description_opt SCALAR name directives_list_opt { result = make_node(:ScalarTypeDefinition, name: val[2], directives: val[3], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } object_type_definition: description_opt TYPE name implements_opt directives_list_opt LCURLY field_definition_list RCURLY { result = make_node(:ObjectTypeDefinition, name: val[2], interfaces: val[3], directives: val[4], fields: val[6], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } implements_opt: /* none */ { result = EMPTY_ARRAY } | implements implements: IMPLEMENTS AMP interfaces_list { result = val[2] } | IMPLEMENTS interfaces_list { result = val[1] } | IMPLEMENTS legacy_interfaces_list { result = val[1] } interfaces_list: name { result = [make_node(:TypeName, name: val[0], position_source: val[0])] } | interfaces_list AMP name { val[0] << make_node(:TypeName, name: val[2], position_source: val[2]) } legacy_interfaces_list: name { result = [make_node(:TypeName, name: val[0], position_source: val[0])] } | legacy_interfaces_list name { val[0] << make_node(:TypeName, name: val[1], position_source: val[1]) } input_value_definition: description_opt name COLON type default_value_opt directives_list_opt { result = make_node(:InputValueDefinition, name: val[1], type: val[3], default_value: val[4], directives: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } input_value_definition_list: input_value_definition { result = [val[0]] } | input_value_definition_list input_value_definition { val[0] << val[1] } arguments_definitions_opt: /* none */ { result = EMPTY_ARRAY } | LPAREN input_value_definition_list RPAREN { result = val[1] } field_definition: description_opt name arguments_definitions_opt COLON type directives_list_opt { result = make_node(:FieldDefinition, name: val[1], arguments: val[2], type: val[4], directives: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } field_definition_list: /* none */ { result = EMPTY_ARRAY } | field_definition { result = [val[0]] } | field_definition_list field_definition { val[0] << val[1] } interface_type_definition: description_opt INTERFACE name directives_list_opt LCURLY field_definition_list RCURLY { result = make_node(:InterfaceTypeDefinition, name: val[2], directives: val[3], fields: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } union_members: name { result = [make_node(:TypeName, name: val[0], position_source: val[0])]} | union_members PIPE name { val[0] << make_node(:TypeName, name: val[2], position_source: val[2]) } union_type_definition: description_opt UNION name directives_list_opt EQUALS union_members { result = make_node(:UnionTypeDefinition, name: val[2], directives: val[3], types: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } enum_type_definition: description_opt ENUM name directives_list_opt LCURLY enum_value_definitions RCURLY { result = make_node(:EnumTypeDefinition, name: val[2], directives: val[3], values: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } input_object_type_definition: description_opt INPUT name directives_list_opt LCURLY input_value_definition_list RCURLY { result = make_node(:InputObjectTypeDefinition, name: val[2], directives: val[3], fields: val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } directive_definition: description_opt DIRECTIVE DIR_SIGN name arguments_definitions_opt ON directive_locations { result = make_node(:DirectiveDefinition, name: val[3], arguments: val[4], locations: val[6], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1]) } directive_locations: name { result = [make_node(:DirectiveLocation, name: val[0].to_s, position_source: val[0])] } | directive_locations PIPE name { val[0] << make_node(:DirectiveLocation, name: val[2].to_s, position_source: val[2]) } end ---- header ---- ---- inner ---- EMPTY_ARRAY = [].freeze def initialize(query_string, filename:, tracer: Tracing::NullTracer) raise GraphQL::ParseError.new("No query string was present", nil, nil, query_string) if query_string.nil? @query_string = query_string @filename = filename @tracer = tracer @reused_next_token = [nil, nil] end def parse_document @document ||= begin # Break the string into tokens @tracer.trace("lex", {query_string: @query_string}) do @tokens ||= GraphQL.scan(@query_string) end # From the tokens, build an AST @tracer.trace("parse", {query_string: @query_string}) do if @tokens.empty? raise GraphQL::ParseError.new("Unexpected end of document", nil, nil, @query_string) else do_parse end end end end def self.parse(query_string, filename: nil, tracer: GraphQL::Tracing::NullTracer) self.new(query_string, filename: filename, tracer: tracer).parse_document end def self.parse_file(filename, tracer: GraphQL::Tracing::NullTracer) self.parse(File.read(filename), filename: filename, tracer: tracer) end private def next_token lexer_token = @tokens.shift if lexer_token.nil? nil else @reused_next_token[0] = lexer_token.name @reused_next_token[1] = lexer_token @reused_next_token end end def get_description(token) comments = [] loop do prev_token = token token = token.prev_token break if token.nil? break if token.name != :COMMENT break if prev_token.line != token.line + 1 comments.unshift(token.to_s.sub(/^#\s*/, "")) end return nil if comments.empty? comments.join("\n") end def on_error(parser_token_id, lexer_token, vstack) if lexer_token == "$" || lexer_token == nil raise GraphQL::ParseError.new("Unexpected end of document", nil, nil, @query_string, filename: @filename) else parser_token_name = token_to_str(parser_token_id) if parser_token_name.nil? raise GraphQL::ParseError.new("Parse Error on unknown token: {token_id: #{parser_token_id}, lexer_token: #{lexer_token}} from #{@query_string}", nil, nil, @query_string, filename: @filename) else line, col = lexer_token.line_and_column if lexer_token.name == :BAD_UNICODE_ESCAPE raise GraphQL::ParseError.new("Parse error on bad Unicode escape sequence: #{lexer_token.to_s.inspect} (#{parser_token_name}) at [#{line}, #{col}]", line, col, @query_string, filename: @filename) else raise GraphQL::ParseError.new("Parse error on #{lexer_token.to_s.inspect} (#{parser_token_name}) at [#{line}, #{col}]", line, col, @query_string, filename: @filename) end end end end def make_node(node_name, assigns) assigns.each do |key, value| if key != :position_source && value.is_a?(GraphQL::Language::Token) assigns[key] = value.to_s end end assigns[:filename] = @filename GraphQL::Language::Nodes.const_get(node_name).new(assigns) end graphql-ruby-1.11.10/lib/graphql/language/printer.rb000066400000000000000000000277311414121453000222740ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language class Printer # Turn an arbitrary AST node back into a string. # # @example Turning a document into a query string # document = GraphQL.parse(query_string) # GraphQL::Language::Printer.new.print(document) # # => "{ ... }" # # # @example Building a custom printer # # class MyPrinter < GraphQL::Language::Printer # def print_argument(arg) # "#{arg.name}: " # end # end # # MyPrinter.new.print(document) # # => "mutation { pay(creditCard: ) { success } }" # # # @param indent [String] Whitespace to add to the printed node # @return [String] Valid GraphQL for `node` def print(node, indent: "") print_node(node, indent: indent) end protected def print_document(document) document.definitions.map { |d| print_node(d) }.join("\n\n") end def print_argument(argument) "#{argument.name}: #{print_node(argument.value)}".dup end def print_directive(directive) out = "@#{directive.name}".dup if directive.arguments.any? out << "(#{directive.arguments.map { |a| print_argument(a) }.join(", ")})" end out end def print_enum(enum) "#{enum.name}".dup end def print_null_value "null".dup end def print_field(field, indent: "") out = "#{indent}".dup out << "#{field.alias}: " if field.alias out << "#{field.name}" out << "(#{field.arguments.map { |a| print_argument(a) }.join(", ")})" if field.arguments.any? out << print_directives(field.directives) out << print_selections(field.selections, indent: indent) out end def print_fragment_definition(fragment_def, indent: "") out = "#{indent}fragment #{fragment_def.name}".dup if fragment_def.type out << " on #{print_node(fragment_def.type)}" end out << print_directives(fragment_def.directives) out << print_selections(fragment_def.selections, indent: indent) out end def print_fragment_spread(fragment_spread, indent: "") out = "#{indent}...#{fragment_spread.name}".dup out << print_directives(fragment_spread.directives) out end def print_inline_fragment(inline_fragment, indent: "") out = "#{indent}...".dup if inline_fragment.type out << " on #{print_node(inline_fragment.type)}" end out << print_directives(inline_fragment.directives) out << print_selections(inline_fragment.selections, indent: indent) out end def print_input_object(input_object) "{#{input_object.arguments.map { |a| print_argument(a) }.join(", ")}}" end def print_list_type(list_type) "[#{print_node(list_type.of_type)}]".dup end def print_non_null_type(non_null_type) "#{print_node(non_null_type.of_type)}!".dup end def print_operation_definition(operation_definition, indent: "") out = "#{indent}#{operation_definition.operation_type}".dup out << " #{operation_definition.name}" if operation_definition.name if operation_definition.variables.any? out << "(#{operation_definition.variables.map { |v| print_variable_definition(v) }.join(", ")})" end out << print_directives(operation_definition.directives) out << print_selections(operation_definition.selections, indent: indent) out end def print_type_name(type_name) "#{type_name.name}".dup end def print_variable_definition(variable_definition) out = "$#{variable_definition.name}: #{print_node(variable_definition.type)}".dup out << " = #{print_node(variable_definition.default_value)}" unless variable_definition.default_value.nil? out end def print_variable_identifier(variable_identifier) "$#{variable_identifier.name}".dup end def print_schema_definition(schema) if (schema.query.nil? || schema.query == 'Query') && (schema.mutation.nil? || schema.mutation == 'Mutation') && (schema.subscription.nil? || schema.subscription == 'Subscription') && (schema.directives.empty?) return end out = "schema".dup if schema.directives.any? schema.directives.each do |dir| out << "\n " out << print_node(dir) end out << "\n{" else out << " {\n" end out << " query: #{schema.query}\n" if schema.query out << " mutation: #{schema.mutation}\n" if schema.mutation out << " subscription: #{schema.subscription}\n" if schema.subscription out << "}" end def print_scalar_type_definition(scalar_type) out = print_description(scalar_type) out << "scalar #{scalar_type.name}" out << print_directives(scalar_type.directives) end def print_object_type_definition(object_type) out = print_description(object_type) out << "type #{object_type.name}" out << " implements " << object_type.interfaces.map(&:name).join(" & ") unless object_type.interfaces.empty? out << print_directives(object_type.directives) out << print_field_definitions(object_type.fields) end def print_input_value_definition(input_value) out = "#{input_value.name}: #{print_node(input_value.type)}".dup out << " = #{print_node(input_value.default_value)}" unless input_value.default_value.nil? out << print_directives(input_value.directives) end def print_arguments(arguments, indent: "") if arguments.all?{ |arg| !arg.description } return "(#{arguments.map{ |arg| print_input_value_definition(arg) }.join(", ")})" end out = "(\n".dup out << arguments.map.with_index{ |arg, i| "#{print_description(arg, indent: " " + indent, first_in_block: i == 0)} #{indent}"\ "#{print_input_value_definition(arg)}" }.join("\n") out << "\n#{indent})" end def print_field_definition(field) out = field.name.dup unless field.arguments.empty? out << print_arguments(field.arguments, indent: " ") end out << ": #{print_node(field.type)}" out << print_directives(field.directives) end def print_interface_type_definition(interface_type) out = print_description(interface_type) out << "interface #{interface_type.name}" out << print_directives(interface_type.directives) out << print_field_definitions(interface_type.fields) end def print_union_type_definition(union_type) out = print_description(union_type) out << "union #{union_type.name}" out << print_directives(union_type.directives) out << " = " + union_type.types.map(&:name).join(" | ") end def print_enum_type_definition(enum_type) out = print_description(enum_type) out << "enum #{enum_type.name}#{print_directives(enum_type.directives)} {\n" enum_type.values.each.with_index do |value, i| out << print_description(value, indent: ' ', first_in_block: i == 0) out << print_enum_value_definition(value) end out << "}" end def print_enum_value_definition(enum_value) out = " #{enum_value.name}".dup out << print_directives(enum_value.directives) out << "\n" end def print_input_object_type_definition(input_object_type) out = print_description(input_object_type) out << "input #{input_object_type.name}" out << print_directives(input_object_type.directives) out << " {\n" input_object_type.fields.each.with_index do |field, i| out << print_description(field, indent: ' ', first_in_block: i == 0) out << " #{print_input_value_definition(field)}\n" end out << "}" end def print_directive_definition(directive) out = print_description(directive) out << "directive @#{directive.name}" if directive.arguments.any? out << print_arguments(directive.arguments) end out << " on #{directive.locations.map(&:name).join(' | ')}" end def print_description(node, indent: "", first_in_block: true) return ''.dup unless node.description description = indent != '' && !first_in_block ? "\n".dup : "".dup description << GraphQL::Language::BlockString.print(node.description, indent: indent) end def print_field_definitions(fields) out = " {\n".dup fields.each.with_index do |field, i| out << print_description(field, indent: ' ', first_in_block: i == 0) out << " #{print_field_definition(field)}\n" end out << "}" end def print_directives(directives) if directives.any? directives.map { |d| " #{print_directive(d)}" }.join else "" end end def print_selections(selections, indent: "") if selections.any? out = " {\n".dup selections.each do |selection| out << print_node(selection, indent: indent + " ") << "\n" end out << "#{indent}}" else "" end end def print_node(node, indent: "") case node when Nodes::Document print_document(node) when Nodes::Argument print_argument(node) when Nodes::Directive print_directive(node) when Nodes::Enum print_enum(node) when Nodes::NullValue print_null_value when Nodes::Field print_field(node, indent: indent) when Nodes::FragmentDefinition print_fragment_definition(node, indent: indent) when Nodes::FragmentSpread print_fragment_spread(node, indent: indent) when Nodes::InlineFragment print_inline_fragment(node, indent: indent) when Nodes::InputObject print_input_object(node) when Nodes::ListType print_list_type(node) when Nodes::NonNullType print_non_null_type(node) when Nodes::OperationDefinition print_operation_definition(node, indent: indent) when Nodes::TypeName print_type_name(node) when Nodes::VariableDefinition print_variable_definition(node) when Nodes::VariableIdentifier print_variable_identifier(node) when Nodes::SchemaDefinition print_schema_definition(node) when Nodes::ScalarTypeDefinition print_scalar_type_definition(node) when Nodes::ObjectTypeDefinition print_object_type_definition(node) when Nodes::InputValueDefinition print_input_value_definition(node) when Nodes::FieldDefinition print_field_definition(node) when Nodes::InterfaceTypeDefinition print_interface_type_definition(node) when Nodes::UnionTypeDefinition print_union_type_definition(node) when Nodes::EnumTypeDefinition print_enum_type_definition(node) when Nodes::EnumValueDefinition print_enum_value_definition(node) when Nodes::InputObjectTypeDefinition print_input_object_type_definition(node) when Nodes::DirectiveDefinition print_directive_definition(node) when FalseClass, Float, Integer, NilClass, String, TrueClass, Symbol GraphQL::Language.serialize(node) when Array "[#{node.map { |v| print_node(v) }.join(", ")}]".dup when Hash "{#{node.map { |k, v| "#{k}: #{print_node(v)}" }.join(", ")}}".dup else GraphQL::Language.serialize(node.to_s) end end private attr_reader :node end end end graphql-ruby-1.11.10/lib/graphql/language/sanitized_printer.rb000066400000000000000000000154161414121453000243430ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language # A custom printer used to print sanitized queries. It inlines provided variables # within the query for facilitate logging and analysis of queries. # # The printer returns `nil` if the query is invalid. # # Since the GraphQL Ruby AST for a GraphQL query doesnt contain any reference # on the type of fields or arguments, we have to track the current object, field # and input type while printing the query. # # @example Printing a scrubbed string # printer = QueryPrinter.new(query) # puts printer.sanitized_query_string # # @see {Query#sanitized_query_string} class SanitizedPrinter < GraphQL::Language::Printer REDACTED = "\"\"" def initialize(query, inline_variables: true) @query = query @current_type = nil @current_field = nil @current_input_type = nil @inline_variables = inline_variables end # @return [String, nil] A scrubbed query string, if the query was valid. def sanitized_query_string if query.valid? print(query.document) else nil end end def print_node(node, indent: "") case node when FalseClass, Float, Integer, String, TrueClass if @current_argument && redact_argument_value?(@current_argument, node) redacted_argument_value(@current_argument) else super end when Array old_input_type = @current_input_type if @current_input_type && @current_input_type.list? @current_input_type = @current_input_type.of_type @current_input_type = @current_input_type.of_type if @current_input_type.non_null? end res = super @current_input_type = old_input_type res else super end end # Indicates whether or not to redact non-null values for the given argument. Defaults to redacting all strings # arguments but this can be customized by subclasses. def redact_argument_value?(argument, value) # Default to redacting any strings or custom scalars encoded as strings type = argument.type.unwrap value.is_a?(String) && type.kind.scalar? && (type.graphql_name == "String" || !type.default_scalar?) end # Returns the value to use for redacted versions of the given argument. Defaults to the # string "". def redacted_argument_value(argument) REDACTED end def print_argument(argument) # We won't have type information if we're recursing into a custom scalar return super if @current_input_type && @current_input_type.kind.scalar? arg_owner = @current_input_type || @current_directive || @current_field old_current_argument = @current_argument @current_argument = arg_owner.arguments[argument.name] old_input_type = @current_input_type @current_input_type = @current_argument.type.non_null? ? @current_argument.type.of_type : @current_argument.type argument_value = if coerce_argument_value_to_list?(@current_input_type, argument.value) [argument.value] else argument.value end res = "#{argument.name}: #{print_node(argument_value)}".dup @current_input_type = old_input_type @current_argument = old_current_argument res end def coerce_argument_value_to_list?(type, value) type.list? && !value.is_a?(Array) && !value.nil? && !value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) end def print_variable_identifier(variable_id) if @inline_variables variable_value = query.variables[variable_id.name] print_node(value_to_ast(variable_value, @current_input_type)) else super end end def print_field(field, indent: "") @current_field = query.schema.get_field(@current_type, field.name) old_type = @current_type @current_type = @current_field.type.unwrap res = super @current_type = old_type res end def print_inline_fragment(inline_fragment, indent: "") old_type = @current_type if inline_fragment.type @current_type = query.schema.types[inline_fragment.type.name] end res = super @current_type = old_type res end def print_fragment_definition(fragment_def, indent: "") old_type = @current_type @current_type = query.schema.types[fragment_def.type.name] res = super @current_type = old_type res end def print_directive(directive) @current_directive = query.schema.directives[directive.name] res = super @current_directive = nil res end # Print the operation definition but do not include the variable # definitions since we will inline them within the query def print_operation_definition(operation_definition, indent: "") old_type = @current_type @current_type = query.schema.public_send(operation_definition.operation_type) if @inline_variables out = "#{indent}#{operation_definition.operation_type}".dup out << " #{operation_definition.name}" if operation_definition.name out << print_directives(operation_definition.directives) out << print_selections(operation_definition.selections, indent: indent) else out = super end @current_type = old_type out end private def value_to_ast(value, type) type = type.of_type if type.non_null? if value.nil? return GraphQL::Language::Nodes::NullValue.new(name: "null") end case type.kind.name when "INPUT_OBJECT" value = if value.respond_to?(:to_unsafe_h) # for ActionController::Parameters value.to_unsafe_h else value.to_h end arguments = value.map do |key, val| sub_type = type.arguments[key.to_s].type GraphQL::Language::Nodes::Argument.new( name: key.to_s, value: value_to_ast(val, sub_type) ) end GraphQL::Language::Nodes::InputObject.new( arguments: arguments ) when "LIST" if value.is_a?(Array) value.map { |v| value_to_ast(v, type.of_type) } else [value].map { |v| value_to_ast(v, type.of_type) } end when "ENUM" GraphQL::Language::Nodes::Enum.new(name: value) else value end end attr_reader :query end end end graphql-ruby-1.11.10/lib/graphql/language/token.rb000066400000000000000000000016051414121453000217210ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language # Emitted by the lexer and passed to the parser. # Contains type, value and position data. class Token if !String.method_defined?(:-@) using GraphQL::StringDedupBackport end # @return [Symbol] The kind of token this is attr_reader :name # @return [String] The text of this token attr_reader :value attr_reader :prev_token, :line, :col def initialize(name, value, line, col, prev_token) @name = name @value = -value @line = line @col = col @prev_token = prev_token end alias to_s value def to_i; @value.to_i; end def to_f; @value.to_f; end def line_and_column [@line, @col] end def inspect "(#{@name} #{@value.inspect} [#{@line}:#{@col}])" end end end end graphql-ruby-1.11.10/lib/graphql/language/visitor.rb000066400000000000000000000214641414121453000223050ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Language # Depth-first traversal through the tree, calling hooks at each stop. # # @example Create a visitor counting certain field names # class NameCounter < GraphQL::Language::Visitor # def initialize(document, field_name) # super(document) # @field_name = field_name # @count = 0 # end # # attr_reader :count # # def on_field(node, parent) # # if this field matches our search, increment the counter # if node.name == @field_name # @count += 1 # end # # Continue visiting subfields: # super # end # end # # # Initialize a visitor # visitor = NameCounter.new(document, "name") # # Run it # visitor.visit # # Check the result # visitor.count # # => 3 class Visitor # If any hook returns this value, the {Visitor} stops visiting this # node right away # @deprecated Use `super` to continue the visit; or don't call it to halt. SKIP = :_skip class DeleteNode; end # When this is returned from a visitor method, # Then the `node` passed into the method is removed from `parent`'s children. DELETE_NODE = DeleteNode.new def initialize(document) @document = document @visitors = {} @result = nil end # @return [GraphQL::Language::Nodes::Document] The document with any modifications applied attr_reader :result # Get a {NodeVisitor} for `node_class` # @param node_class [Class] The node class that you want to listen to # @return [NodeVisitor] # # @example Run a hook whenever you enter a new Field # visitor[GraphQL::Language::Nodes::Field] << ->(node, parent) { p "Here's a field" } # @deprecated see `on_` methods, like {#on_field} def [](node_class) @visitors[node_class] ||= NodeVisitor.new end # Visit `document` and all children, applying hooks as you go # @return [void] def visit result = on_node_with_modifications(@document, nil) @result = if result.is_a?(Array) result.first else # The node wasn't modified @document end end # Call the user-defined handler for `node`. def visit_node(node, parent) public_send(node.visit_method, node, parent) end # The default implementation for visiting an AST node. # It doesn't _do_ anything, but it continues to visiting the node's children. # To customize this hook, override one of its make_visit_methodes (or the base method?) # in your subclasses. # # For compatibility, it calls hook procs, too. # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node. # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`. def on_abstract_node(node, parent) if node.equal?(DELETE_NODE) # This might be passed to `super(DELETE_NODE, ...)` # by a user hook, don't want to keep visiting in that case. nil else # Run hooks if there are any new_node = node no_hooks = !@visitors.key?(node.class) if no_hooks || begin_visit(new_node, parent) node.children.each do |child_node| new_child_and_node = on_node_with_modifications(child_node, new_node) # Reassign `node` in case the child hook makes a modification if new_child_and_node.is_a?(Array) new_node = new_child_and_node[1] end end end end_visit(new_node, parent) unless no_hooks if new_node.equal?(node) nil else [new_node, parent] end end end # We don't use `alias` here because it breaks `super` def self.make_visit_method(node_method) class_eval(<<-RUBY, __FILE__, __LINE__ + 1) def #{node_method}(node, parent) child_mod = on_abstract_node(node, parent) # If visiting the children returned changes, continue passing those. child_mod || [node, parent] end RUBY end make_visit_method :on_argument make_visit_method :on_directive make_visit_method :on_directive_definition make_visit_method :on_directive_location make_visit_method :on_document make_visit_method :on_enum make_visit_method :on_enum_type_definition make_visit_method :on_enum_type_extension make_visit_method :on_enum_value_definition make_visit_method :on_field make_visit_method :on_field_definition make_visit_method :on_fragment_definition make_visit_method :on_fragment_spread make_visit_method :on_inline_fragment make_visit_method :on_input_object make_visit_method :on_input_object_type_definition make_visit_method :on_input_object_type_extension make_visit_method :on_input_value_definition make_visit_method :on_interface_type_definition make_visit_method :on_interface_type_extension make_visit_method :on_list_type make_visit_method :on_non_null_type make_visit_method :on_null_value make_visit_method :on_object_type_definition make_visit_method :on_object_type_extension make_visit_method :on_operation_definition make_visit_method :on_scalar_type_definition make_visit_method :on_scalar_type_extension make_visit_method :on_schema_definition make_visit_method :on_schema_extension make_visit_method :on_type_name make_visit_method :on_union_type_definition make_visit_method :on_union_type_extension make_visit_method :on_variable_definition make_visit_method :on_variable_identifier private # Run the hooks for `node`, and if the hooks return a copy of `node`, # copy `parent` so that it contains the copy of that node as a child, # then return the copies # If a non-array value is returned, consuming functions should ignore # said value def on_node_with_modifications(node, parent) new_node_and_new_parent = visit_node(node, parent) if new_node_and_new_parent.is_a?(Array) new_node = new_node_and_new_parent[0] new_parent = new_node_and_new_parent[1] if new_node.is_a?(Nodes::AbstractNode) && !node.equal?(new_node) # The user-provided hook returned a new node. new_parent = new_parent && new_parent.replace_child(node, new_node) return new_node, new_parent elsif new_node.equal?(DELETE_NODE) # The user-provided hook requested to remove this node new_parent = new_parent && new_parent.delete_child(node) return nil, new_parent elsif new_node_and_new_parent.none? { |n| n == nil || n.class < Nodes::AbstractNode } # The user-provided hook returned an array of who-knows-what # return nil here to signify that no changes should be made nil else new_node_and_new_parent end else # The user-provided hook didn't make any modifications. # In fact, the hook might have returned who-knows-what, so # ignore the return value and use the original values. new_node_and_new_parent end end def begin_visit(node, parent) node_visitor = self[node.class] self.class.apply_hooks(node_visitor.enter, node, parent) end # Should global `leave` visitors come first or last? def end_visit(node, parent) node_visitor = self[node.class] self.class.apply_hooks(node_visitor.leave, node, parent) end # If one of the visitors returns SKIP, stop visiting this node def self.apply_hooks(hooks, node, parent) hooks.each do |proc| return false if proc.call(node, parent) == SKIP end true end # Collect `enter` and `leave` hooks for classes in {GraphQL::Language::Nodes} # # Access {NodeVisitor}s via {GraphQL::Language::Visitor#[]} class NodeVisitor # @return [Array] Hooks to call when entering a node of this type attr_reader :enter # @return [Array] Hooks to call when leaving a node of this type attr_reader :leave def initialize @enter = [] @leave = [] end # Shorthand to add a hook to the {#enter} array # @param hook [Proc] A hook to add def <<(hook) enter << hook end end end end end graphql-ruby-1.11.10/lib/graphql/list_type.rb000066400000000000000000000040401414121453000210260ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # A list type modifies another type. # # List types can be created with the type helper (`types[InnerType]`) # or {BaseType#to_list_type} (`InnerType.to_list_type`) # # For return types, it says that the returned value will be a list of the modified. # # @example A field which returns a list of items # field :items, types[ItemType] # # or # field :items, ItemType.to_list_type # # For input types, it says that the incoming value will be a list of the modified type. # # @example A field which accepts a list of strings # field :newNames do # # ... # argument :values, types[types.String] # # or # argument :values, types.String.to_list_type # end # # Given a list type, you can always get the underlying type with {#unwrap}. # class ListType < GraphQL::BaseType include GraphQL::BaseType::ModifiesAnotherType attr_reader :of_type def initialize(of_type:) super() @of_type = of_type end def kind GraphQL::TypeKinds::LIST end def to_s "[#{of_type.to_s}]" end alias_method :inspect, :to_s alias :to_type_signature :to_s def coerce_result(value, ctx = nil) if ctx.nil? warn_deprecated_coerce("coerce_isolated_result") ctx = GraphQL::Query::NullContext end ensure_array(value).map { |item| item.nil? ? nil : of_type.coerce_result(item, ctx) } end def list? true end private def coerce_non_null_input(value, ctx) ensure_array(value).map { |item| of_type.coerce_input(item, ctx) } end def validate_non_null_input(value, ctx) result = GraphQL::Query::InputValidationResult.new ensure_array(value).each_with_index do |item, index| item_result = of_type.validate_input(item, ctx) if !item_result.valid? result.merge_result!(index, item_result) end end result end def ensure_array(value) value.is_a?(Array) ? value : [value] end end end graphql-ruby-1.11.10/lib/graphql/load_application_object_failed_error.rb000066400000000000000000000016051414121453000263630ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Raised when a argument is configured with `loads:` and the client provides an `ID`, # but no object is loaded for that ID. # # @see GraphQL::Schema::Member::HasArguments::ArgumentObjectLoader#load_application_object_failed, A hook which you can override in resolvers, mutations and input objects. class LoadApplicationObjectFailedError < GraphQL::ExecutionError # @return [GraphQL::Schema::Argument] the argument definition for the argument that was looked up attr_reader :argument # @return [String] The ID provided by the client attr_reader :id # @return [Object] The value found with this ID attr_reader :object def initialize(argument:, id:, object:) @id = id @argument = argument @object = object super("No object found for `#{argument.graphql_name}: #{id.inspect}`") end end end graphql-ruby-1.11.10/lib/graphql/name_validator.rb000066400000000000000000000006311414121453000220010ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class NameValidator if !String.method_defined?(:match?) using GraphQL::StringMatchBackport end VALID_NAME_REGEX = /^[_a-zA-Z][_a-zA-Z0-9]*$/ def self.validate!(name) name = name.is_a?(String) ? name : name.to_s raise GraphQL::InvalidNameError.new(name, VALID_NAME_REGEX) unless name.match?(VALID_NAME_REGEX) end end end graphql-ruby-1.11.10/lib/graphql/non_null_type.rb000066400000000000000000000035361414121453000217100ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # A non-null type modifies another type. # # Non-null types can be created with `!` (`InnerType!`) # or {BaseType#to_non_null_type} (`InnerType.to_non_null_type`) # # For return types, it says that the returned value will _always_ be present. # # @example A field which _always_ returns an error # field :items, !ItemType # # or # field :items, ItemType.to_non_null_type # # (If the application fails to return a value, {InvalidNullError} will be passed to {Schema#type_error}.) # # For input types, it says that the incoming value _must_ be provided by the query. # # @example A field which _requires_ a string input # field :newNames do # # ... # argument :values, !types.String # # or # argument :values, types.String.to_non_null_type # end # # (If a value isn't provided, {Query::VariableValidationError} will be raised). # # Given a non-null type, you can always get the underlying type with {#unwrap}. # class NonNullType < GraphQL::BaseType include GraphQL::BaseType::ModifiesAnotherType extend Forwardable attr_reader :of_type def initialize(of_type:) super() @of_type = of_type end def valid_input?(value, ctx) validate_input(value, ctx).valid? end def validate_input(value, ctx) if value.nil? result = GraphQL::Query::InputValidationResult.new result.add_problem("Expected value to not be null") result else of_type.validate_input(value, ctx) end end def_delegators :@of_type, :coerce_input, :coerce_result, :list? def kind GraphQL::TypeKinds::NON_NULL end def to_s "#{of_type.to_s}!" end alias_method :inspect, :to_s alias :to_type_signature :to_s def non_null? true end end end graphql-ruby-1.11.10/lib/graphql/object_type.rb000066400000000000000000000113661414121453000213320ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # @api deprecated class ObjectType < GraphQL::BaseType accepts_definitions :interfaces, :fields, :mutation, :relay_node_type, field: GraphQL::Define::AssignObjectField accepts_definitions implements: ->(type, *interfaces, inherit: false) { type.implements(interfaces, inherit: inherit) } attr_accessor :fields, :mutation, :relay_node_type ensure_defined(:fields, :mutation, :interfaces, :relay_node_type) # @!attribute fields # @return [Hash GraphQL::Field>] Map String fieldnames to their {GraphQL::Field} implementations # @!attribute mutation # @return [GraphQL::Relay::Mutation, nil] The mutation this object type was derived from, if it is an auto-generated payload type. def initialize super @fields = {} @clean_inherited_fields = nil @structural_interface_type_memberships = [] @inherited_interface_type_memberships = [] end def initialize_copy(other) super @structural_interface_type_memberships = other.structural_interface_type_memberships.dup @inherited_interface_type_memberships = other.inherited_interface_type_memberships.dup @fields = other.fields.dup end # This method declares interfaces for this type AND inherits any field definitions # @param new_interfaces [Array] interfaces that this type implements # @deprecated Use `implements` instead of `interfaces`. def interfaces=(new_interfaces) @structural_interface_type_memberships = [] @inherited_interface_type_memberships = [] @clean_inherited_fields = nil implements(new_interfaces, inherit: true) end def interfaces(ctx = GraphQL::Query::NullContext) ensure_defined visible_ifaces = [] unfiltered = ctx == GraphQL::Query::NullContext [@structural_interface_type_memberships, @inherited_interface_type_memberships].each do |tms| tms.each do |type_membership| if unfiltered || type_membership.visible?(ctx) # if this is derived from a class-based object, we have to # get the `.graphql_definition` of the attached interface. visible_ifaces << GraphQL::BaseType.resolve_related_type(type_membership.abstract_type) end end end visible_ifaces end def kind GraphQL::TypeKinds::OBJECT end # This fields doesnt have instrumenation applied # @see [Schema#get_field] Get field with instrumentation # @return [GraphQL::Field] The field definition for `field_name` (may be inherited from interfaces) def get_field(field_name) fields[field_name] || interface_fields[field_name] end # These fields don't have instrumenation applied # @see [Schema#get_fields] Get fields with instrumentation # @return [Array] All fields, including ones inherited from interfaces def all_fields interface_fields.merge(self.fields).values end # Declare that this object implements this interface. # This declaration will be validated when the schema is defined. # @param interfaces [Array] add a new interface that this type implements # @param inherits [Boolean] If true, copy the interfaces' field definitions to this type def implements(interfaces, inherit: false, **options) if !interfaces.is_a?(Array) raise ArgumentError, "`implements(interfaces)` must be an array, not #{interfaces.class} (#{interfaces})" end @clean_inherited_fields = nil type_memberships = inherit ? @inherited_interface_type_memberships : @structural_interface_type_memberships interfaces.each do |iface| iface = BaseType.resolve_related_type(iface) if iface.is_a?(GraphQL::InterfaceType) type_memberships << iface.type_membership_class.new(iface, self, **options) end end end def resolve_type_proc nil end attr_writer :structural_interface_type_memberships protected attr_reader :structural_interface_type_memberships, :inherited_interface_type_memberships private def normalize_interfaces(ifaces) ifaces.map { |i_type| GraphQL::BaseType.resolve_related_type(i_type) } end def interface_fields if @clean_inherited_fields @clean_inherited_fields else ensure_defined @clean_inherited_fields = {} @inherited_interface_type_memberships.each do |type_membership| iface = GraphQL::BaseType.resolve_related_type(type_membership.abstract_type) if iface.is_a?(GraphQL::InterfaceType) @clean_inherited_fields.merge!(iface.fields) else pp iface end end @clean_inherited_fields end end end end graphql-ruby-1.11.10/lib/graphql/pagination.rb000066400000000000000000000004441414121453000211470ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/pagination/array_connection" require "graphql/pagination/active_record_relation_connection" require "graphql/pagination/connections" require "graphql/pagination/mongoid_relation_connection" require "graphql/pagination/sequel_dataset_connection" graphql-ruby-1.11.10/lib/graphql/pagination/000077500000000000000000000000001414121453000206205ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/pagination/active_record_relation_connection.rb000066400000000000000000000017651414121453000301030ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/pagination/relation_connection" module GraphQL module Pagination # Customizes `RelationConnection` to work with `ActiveRecord::Relation`s. class ActiveRecordRelationConnection < Pagination::RelationConnection def relation_count(relation) int_or_hash = if relation.respond_to?(:unscope) relation.unscope(:order).count(:all) else # Rails 3 relation.count end if int_or_hash.is_a?(Integer) int_or_hash else # Grouped relations return count-by-group hashes int_or_hash.length end end def relation_limit(relation) relation.limit_value end def relation_offset(relation) relation.offset_value end def null_relation(relation) if relation.respond_to?(:none) relation.none else # Rails 3 relation.where("1=2") end end end end end graphql-ruby-1.11.10/lib/graphql/pagination/array_connection.rb000066400000000000000000000036051414121453000245060ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/pagination/connection" module GraphQL module Pagination class ArrayConnection < Pagination::Connection def nodes load_nodes @nodes end def has_previous_page load_nodes @has_previous_page end def has_next_page load_nodes @has_next_page end def cursor_for(item) idx = items.find_index(item) + 1 encode(idx.to_s) end private def index_from_cursor(cursor) decode(cursor).to_i end # Populate all the pagination info _once_, # It doesn't do anything on subsequent calls. def load_nodes @nodes ||= begin sliced_nodes = if before && after items[index_from_cursor(after)..index_from_cursor(before)-1] || [] elsif before items[0..index_from_cursor(before)-2] || [] elsif after items[index_from_cursor(after)..-1] || [] else items end @has_previous_page = if last # There are items preceding the ones in this result sliced_nodes.count > last elsif after # We've paginated into the Array a bit, there are some behind us index_from_cursor(after) > 0 else false end @has_next_page = if first # There are more items after these items sliced_nodes.count > first elsif before # The original array is longer than the `before` index index_from_cursor(before) < items.length + 1 else false end limited_nodes = sliced_nodes limited_nodes = limited_nodes.first(first) if first limited_nodes = limited_nodes.last(last) if last limited_nodes end end end end end graphql-ruby-1.11.10/lib/graphql/pagination/connection.rb000066400000000000000000000162401414121453000233070ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Pagination # A Connection wraps a list of items and provides cursor-based pagination over it. # # Connections were introduced by Facebook's `Relay` front-end framework, but # proved to be generally useful for GraphQL APIs. When in doubt, use connections # to serve lists (like Arrays, ActiveRecord::Relations) via GraphQL. # # Unlike the previous connection implementation, these default to bidirectional pagination. # # Pagination arguments and context may be provided at initialization or assigned later (see {Schema::Field::ConnectionExtension}). class Connection class PaginationImplementationMissingError < GraphQL::Error end # @return [Object] A list object, from the application. This is the unpaginated value passed into the connection. attr_reader :items # @return [GraphQL::Query::Context] attr_accessor :context # @return [Object] the object this collection belongs to attr_accessor :parent # Raw access to client-provided values. (`max_page_size` not applied to first or last.) attr_accessor :before_value, :after_value, :first_value, :last_value # @return [String, nil] the client-provided cursor. `""` is treated as `nil`. def before if defined?(@before) @before else @before = @before_value == "" ? nil : @before_value end end # @return [String, nil] the client-provided cursor. `""` is treated as `nil`. def after if defined?(@after) @after else @after = @after_value == "" ? nil : @after_value end end # @param items [Object] some unpaginated collection item, like an `Array` or `ActiveRecord::Relation` # @param context [Query::Context] # @param parent [Object] The object this collection belongs to # @param first [Integer, nil] The limit parameter from the client, if it provided one # @param after [String, nil] A cursor for pagination, if the client provided one # @param last [Integer, nil] Limit parameter from the client, if provided # @param before [String, nil] A cursor for pagination, if the client provided one. # @param max_page_size [Integer, nil] A configured value to cap the result size. Applied as `first` if neither first or last are given. def initialize(items, parent: nil, context: nil, first: nil, after: nil, max_page_size: :not_given, last: nil, before: nil, edge_class: nil) @items = items @parent = parent @context = context @first_value = first @after_value = after @last_value = last @before_value = before @edge_class = edge_class || self.class::Edge # This is only true if the object was _initialized_ with an override # or if one is assigned later. @has_max_page_size_override = max_page_size != :not_given @max_page_size = if max_page_size == :not_given nil else max_page_size end end def max_page_size=(new_value) @has_max_page_size_override = true @max_page_size = new_value end def max_page_size if @has_max_page_size_override @max_page_size else context.schema.default_max_page_size end end def has_max_page_size_override? @has_max_page_size_override end attr_writer :first # @return [Integer, nil] # A clamped `first` value. # (The underlying instance variable doesn't have limits on it.) # If neither `first` nor `last` is given, but `max_page_size` is present, max_page_size is used for first. def first @first ||= begin capped = limit_pagination_argument(@first_value, max_page_size) if capped.nil? && last.nil? capped = max_page_size end capped end end attr_writer :last # @return [Integer, nil] A clamped `last` value. (The underlying instance variable doesn't have limits on it) def last @last ||= limit_pagination_argument(@last_value, max_page_size) end # @return [Array] {nodes}, but wrapped with Edge instances def edges @edges ||= nodes.map { |n| @edge_class.new(n, self) } end # @return [Class] A wrapper class for edges of this connection attr_accessor :edge_class # @return [Array] A slice of {items}, constrained by {@first_value}/{@after_value}/{@last_value}/{@before_value} def nodes raise PaginationImplementationMissingError, "Implement #{self.class}#nodes to paginate `@items`" end # A dynamic alias for compatibility with {Relay::BaseConnection}. # @deprecated use {#nodes} instead def edge_nodes nodes end # The connection object itself implements `PageInfo` fields def page_info self end # @return [Boolean] True if there are more items after this page def has_next_page raise PaginationImplementationMissingError, "Implement #{self.class}#has_next_page to return the next-page check" end # @return [Boolean] True if there were items before these items def has_previous_page raise PaginationImplementationMissingError, "Implement #{self.class}#has_previous_page to return the previous-page check" end # @return [String] The cursor of the first item in {nodes} def start_cursor nodes.first && cursor_for(nodes.first) end # @return [String] The cursor of the last item in {nodes} def end_cursor nodes.last && cursor_for(nodes.last) end # Return a cursor for this item. # @param item [Object] one of the passed in {items}, taken from {nodes} # @return [String] def cursor_for(item) raise PaginationImplementationMissingError, "Implement #{self.class}#cursor_for(item) to return the cursor for #{item.inspect}" end private # @param argument [nil, Integer] `first` or `last`, as provided by the client # @param max_page_size [nil, Integer] # @return [nil, Integer] `nil` if the input was `nil`, otherwise a value between `0` and `max_page_size` def limit_pagination_argument(argument, max_page_size) if argument if argument < 0 argument = 0 elsif max_page_size && argument > max_page_size argument = max_page_size end end argument end def decode(cursor) context.schema.cursor_encoder.decode(cursor, nonce: true) end def encode(cursor) context.schema.cursor_encoder.encode(cursor, nonce: true) end # A wrapper around paginated items. It includes a {cursor} for pagination # and could be extended with custom relationship-level data. class Edge attr_reader :node def initialize(node, connection) @connection = connection @node = node end def parent @connection.parent end def cursor @cursor ||= @connection.cursor_for(@node) end end end end end graphql-ruby-1.11.10/lib/graphql/pagination/connections.rb000066400000000000000000000102601414121453000234660ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Pagination # A schema-level connection wrapper manager. # # Attach as a plugin. # # @example Using new default connections # class MySchema < GraphQL::Schema # use GraphQL::Pagination::Connections # end # # @example Adding a custom wrapper # class MySchema < GraphQL::Schema # use GraphQL::Pagination::Connections # connections.add(MyApp::SearchResults, MyApp::SearchResultsConnection) # end # # @example Removing default connection support for arrays (they can still be manually wrapped) # class MySchema < GraphQL::Schema # use GraphQL::Pagination::Connections # connections.delete(Array) # end # # @see {Schema.connections} class Connections class ImplementationMissingError < GraphQL::Error end def self.use(schema_defn) if schema_defn.is_a?(Class) schema_defn.connections = self.new(schema: schema_defn) else # Unwrap a `.define` object schema_defn = schema_defn.target schema_defn.connections = self.new(schema: schema_defn) schema_defn.class.connections = schema_defn.connections end end def initialize(schema:) @schema = schema @wrappers = {} add_default end def add(nodes_class, implementation) @wrappers[nodes_class] = implementation end def delete(nodes_class) @wrappers.delete(nodes_class) end def all_wrappers all_wrappers = {} @schema.ancestors.reverse_each do |schema_class| if schema_class.respond_to?(:connections) && (c = schema_class.connections) all_wrappers.merge!(c.wrappers) end end all_wrappers end def wrapper_for(items, wrappers: all_wrappers) impl = nil items.class.ancestors.each { |cls| impl = wrappers[cls] break if impl } impl end # Used by the runtime to wrap values in connection wrappers. # @api Private def wrap(field, parent, items, arguments, context, wrappers: all_wrappers) return items if GraphQL::Execution::Interpreter::RawValue === items impl = wrapper_for(items, wrappers: wrappers) if impl.nil? raise ImplementationMissingError, "Couldn't find a connection wrapper for #{items.class} during #{field.path} (#{items.inspect})" end impl.new( items, context: context, parent: parent, max_page_size: field.max_page_size || context.schema.default_max_page_size, first: arguments[:first], after: arguments[:after], last: arguments[:last], before: arguments[:before], edge_class: edge_class_for_field(field), ) end # use an override if there is one # @api private def edge_class_for_field(field) conn_type = field.type.unwrap conn_type_edge_type = conn_type.respond_to?(:edge_class) && conn_type.edge_class if conn_type_edge_type && conn_type_edge_type != Relay::Edge conn_type_edge_type else nil end end protected attr_reader :wrappers private def add_default add(Array, Pagination::ArrayConnection) if defined?(ActiveRecord::Relation) add(ActiveRecord::Relation, Pagination::ActiveRecordRelationConnection) end if defined?(Sequel::Dataset) add(Sequel::Dataset, Pagination::SequelDatasetConnection) end if defined?(Mongoid::Criteria) add(Mongoid::Criteria, Pagination::MongoidRelationConnection) end # Mongoid 5 and 6 if defined?(Mongoid::Relations::Targets::Enumerable) add(Mongoid::Relations::Targets::Enumerable, Pagination::MongoidRelationConnection) end # Mongoid 7 if defined?(Mongoid::Association::Referenced::HasMany::Targets::Enumerable) add(Mongoid::Association::Referenced::HasMany::Targets::Enumerable, Pagination::MongoidRelationConnection) end end end end end graphql-ruby-1.11.10/lib/graphql/pagination/mongoid_relation_connection.rb000066400000000000000000000011501414121453000267120ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/pagination/relation_connection" module GraphQL module Pagination class MongoidRelationConnection < Pagination::RelationConnection def relation_offset(relation) relation.options.skip end def relation_limit(relation) relation.options.limit end def relation_count(relation) # Mongo's `.count` doesn't apply limit or skip, which we need. So we have to load _everything_! relation.to_a.count end def null_relation(relation) relation.without_options.none end end end end graphql-ruby-1.11.10/lib/graphql/pagination/relation_connection.rb000066400000000000000000000147071414121453000252120ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/pagination/connection" module GraphQL module Pagination # A generic class for working with database query objects. class RelationConnection < Pagination::Connection def nodes load_nodes @nodes end def has_previous_page if @has_previous_page.nil? @has_previous_page = if after_offset && after_offset > 0 true elsif last # See whether there are any nodes _before_ the current offset. # If there _is no_ current offset, then there can't be any nodes before it. # Assume that if the offset is positive, there are nodes before the offset. limited_nodes !(@paged_nodes_offset.nil? || @paged_nodes_offset == 0) else false end end @has_previous_page end def has_next_page if @has_next_page.nil? @has_next_page = if before_offset && before_offset > 0 true elsif first relation_count(set_limit(sliced_nodes, first + 1)) == first + 1 else false end end @has_next_page end def cursor_for(item) load_nodes # index in nodes + existing offset + 1 (because it's offset, not index) offset = nodes.index(item) + 1 + (@paged_nodes_offset || 0) + (relation_offset(items) || 0) encode(offset.to_s) end private # @param relation [Object] A database query object # @return [Integer, nil] The offset value, or nil if there isn't one def relation_offset(relation) raise "#{self.class}#relation_offset(relation) must return the offset value for a #{relation.class} (#{relation.inspect})" end # @param relation [Object] A database query object # @return [Integer, nil] The limit value, or nil if there isn't one def relation_limit(relation) raise "#{self.class}#relation_limit(relation) must return the limit value for a #{relation.class} (#{relation.inspect})" end # @param relation [Object] A database query object # @return [Integer, nil] The number of items in this relation (hopefully determined without loading all records into memory!) def relation_count(relation) raise "#{self.class}#relation_count(relation) must return the count of records for a #{relation.class} (#{relation.inspect})" end # @param relation [Object] A database query object # @return [Object] A modified query object which will return no records def null_relation(relation) raise "#{self.class}#null_relation(relation) must return an empty relation for a #{relation.class} (#{relation.inspect})" end # @return [Integer] def offset_from_cursor(cursor) decode(cursor).to_i end # Abstract this operation so we can always ignore inputs less than zero. # (Sequel doesn't like it, understandably.) def set_offset(relation, offset_value) if offset_value >= 0 relation.offset(offset_value) else relation.offset(0) end end # Abstract this operation so we can always ignore inputs less than zero. # (Sequel doesn't like it, understandably.) def set_limit(relation, limit_value) if limit_value > 0 relation.limit(limit_value) elsif limit_value == 0 null_relation(relation) else relation end end # Apply `before` and `after` to the underlying `items`, # returning a new relation. def sliced_nodes @sliced_nodes ||= begin paginated_nodes = items if after_offset previous_offset = relation_offset(items) || 0 paginated_nodes = set_offset(paginated_nodes, previous_offset + after_offset) end if before_offset && after_offset if after_offset < before_offset # Get the number of items between the two cursors space_between = before_offset - after_offset - 1 paginated_nodes = set_limit(paginated_nodes, space_between) else # TODO I think this is untested # The cursors overextend one another to an empty set paginated_nodes = null_relation(paginated_nodes) end elsif before_offset # Use limit to cut off the tail of the relation paginated_nodes = set_limit(paginated_nodes, before_offset - 1) end paginated_nodes end end # @return [Integer, nil] def before_offset @before_offset ||= before && offset_from_cursor(before) end # @return [Integer, nil] def after_offset @after_offset ||= after && offset_from_cursor(after) end # Apply `first` and `last` to `sliced_nodes`, # returning a new relation def limited_nodes @limited_nodes ||= begin paginated_nodes = sliced_nodes previous_limit = relation_limit(paginated_nodes) if first && (previous_limit.nil? || previous_limit > first) # `first` would create a stricter limit that the one already applied, so add it paginated_nodes = set_limit(paginated_nodes, first) end if last if (lv = relation_limit(paginated_nodes)) if last <= lv # `last` is a smaller slice than the current limit, so apply it offset = (relation_offset(paginated_nodes) || 0) + (lv - last) paginated_nodes = set_offset(paginated_nodes, offset) paginated_nodes = set_limit(paginated_nodes, last) end else # No limit, so get the last items sliced_nodes_count = relation_count(@sliced_nodes) offset = (relation_offset(paginated_nodes) || 0) + sliced_nodes_count - [last, sliced_nodes_count].min paginated_nodes = set_offset(paginated_nodes, offset) paginated_nodes = set_limit(paginated_nodes, last) end end @paged_nodes_offset = relation_offset(paginated_nodes) paginated_nodes end end # Load nodes after applying first/last/before/after, # returns an array of nodes def load_nodes # Return an array so we can consistently use `.index(node)` on it @nodes ||= limited_nodes.to_a end end end end graphql-ruby-1.11.10/lib/graphql/pagination/sequel_dataset_connection.rb000066400000000000000000000011731414121453000263710ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/pagination/relation_connection" module GraphQL module Pagination # Customizes `RelationConnection` to work with `Sequel::Dataset`s. class SequelDatasetConnection < Pagination::RelationConnection private def relation_offset(relation) relation.opts[:offset] end def relation_limit(relation) relation.opts[:limit] end def relation_count(relation) # Remove order to make it faster relation.order(nil).count end def null_relation(relation) relation.where(false) end end end end graphql-ruby-1.11.10/lib/graphql/parse_error.rb000066400000000000000000000010341414121453000213350ustar00rootroot00000000000000# frozen_string_literal: true # test_via: language/parser.rb module GraphQL class ParseError < GraphQL::Error attr_reader :line, :col, :query def initialize(message, line, col, query, filename: nil) if filename message += " (#{filename})" end super(message) @line = line @col = col @query = query end def to_h locations = line ? [{ "line" => line, "column" => col }] : [] { "message" => message, "locations" => locations, } end end end graphql-ruby-1.11.10/lib/graphql/query.rb000066400000000000000000000354221414121453000201670ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/query/arguments" require "graphql/query/arguments_cache" require "graphql/query/context" require "graphql/query/executor" require "graphql/query/fingerprint" require "graphql/query/literal_input" require "graphql/query/null_context" require "graphql/query/result" require "graphql/query/serial_execution" require "graphql/query/variables" require "graphql/query/input_validation_result" require "graphql/query/variable_validation_error" require "graphql/query/validation_pipeline" module GraphQL # A combination of query string and {Schema} instance which can be reduced to a {#result}. class Query include Tracing::Traceable extend Forwardable class OperationNameMissingError < GraphQL::ExecutionError def initialize(name) msg = if name.nil? %|An operation name is required| else %|No operation named "#{name}"| end super(msg) end end attr_reader :schema, :context, :provided_variables # The value for root types attr_accessor :root_value # @return [nil, String] The operation name provided by client or the one inferred from the document. Used to determine which operation to run. attr_accessor :operation_name # @return [Boolean] if false, static validation is skipped (execution behavior for invalid queries is undefined) attr_accessor :validate attr_writer :query_string # @return [GraphQL::Language::Nodes::Document] def document # It's ok if this hasn't been assigned yet if @query_string || @document with_prepared_ast { @document } else nil end end def inspect "query ..." end # @return [String, nil] The name of the operation to run (may be inferred) def selected_operation_name return nil unless selected_operation selected_operation.name end # @return [String, nil] the triggered event, if this query is a subscription update attr_reader :subscription_topic attr_reader :tracers # Prepare query `query_string` on `schema` # @param schema [GraphQL::Schema] # @param query_string [String] # @param context [#[]] an arbitrary hash of values which you can access in {GraphQL::Field#resolve} # @param variables [Hash] values for `$variables` in the query # @param operation_name [String] if the query string contains many operations, this is the one which should be executed # @param root_value [Object] the object used to resolve fields on the root type # @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value) # @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value) # @param except [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns truthy # @param only [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns false def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, except: nil, only: nil, warden: nil) # Even if `variables: nil` is passed, use an empty hash for simpler logic variables ||= {} # Use the `.graphql_definition` here which will return legacy types instead of classes if schema.is_a?(Class) && !schema.interpreter? schema = schema.graphql_definition end @schema = schema @interpreter = @schema.interpreter? @filter = schema.default_filter.merge(except: except, only: only) @context = schema.context_class.new(query: self, object: root_value, values: context) @warden = warden @subscription_topic = subscription_topic @root_value = root_value @fragments = nil @operations = nil @validate = validate @tracers = schema.tracers + (context ? context.fetch(:tracers, []) : []) # Support `ctx[:backtrace] = true` for wrapping backtraces if context && context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer) @tracers << GraphQL::Backtrace::Tracer end @analysis_errors = [] if variables.is_a?(String) raise ArgumentError, "Query variables should be a Hash, not a String. Try JSON.parse to prepare variables." else @provided_variables = variables || {} end @query_string = query_string || query @document = document if @query_string && @document raise ArgumentError, "Query should only be provided a query string or a document, not both." end # A two-layer cache of type resolution: # { abstract_type => { value => resolved_type } } @resolved_types_cache = Hash.new do |h1, k1| h1[k1] = Hash.new do |h2, k2| h2[k2] = @schema.resolve_type(k1, k2, @context) end end # Trying to execute a document # with no operations returns an empty hash @ast_variables = [] @mutation = false @operation_name = operation_name @prepared_ast = false @validation_pipeline = nil @max_depth = max_depth @max_complexity = max_complexity @result_values = nil @executed = false # TODO add a general way to define schema-level filters if @schema.respond_to?(:visible?) merge_filters(only: @schema.method(:visible?)) end end # If a document was provided to `GraphQL::Schema#execute` instead of the raw query string, we will need to get it from the document def query_string @query_string ||= (document ? document.to_query_string : nil) end def interpreter? @interpreter end def subscription_update? @subscription_topic && subscription? end # A lookahead for the root selections of this query # @return [GraphQL::Execution::Lookahead] def lookahead @lookahead ||= begin ast_node = selected_operation root_type = warden.root_type_for_operation(ast_node.operation_type || "query") root_type = root_type.type_class || raise("Invariant: `lookahead` only works with class-based types") GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [ast_node]) end end # @api private def result_values=(result_hash) if @executed raise "Invariant: Can't reassign result" else @executed = true @result_values = result_hash end end # @api private attr_reader :result_values def fragments with_prepared_ast { @fragments } end def operations with_prepared_ast { @operations } end # Get the result for this query, executing it once # @return [Hash] A GraphQL response, with `"data"` and/or `"errors"` keys def result if !@executed with_prepared_ast { Execution::Multiplex.run_queries(@schema, [self], context: @context) } end @result ||= Query::Result.new(query: self, values: @result_values) end def executed? @executed end def static_errors validation_errors + analysis_errors + context.errors end # This is the operation to run for this query. # If more than one operation is present, it must be named at runtime. # @return [GraphQL::Language::Nodes::OperationDefinition, nil] def selected_operation with_prepared_ast { @selected_operation } end # Determine the values for variables of this query, using default values # if a value isn't provided at runtime. # # If some variable is invalid, errors are added to {#validation_errors}. # # @return [GraphQL::Query::Variables] Variables to apply to this query def variables @variables ||= begin with_prepared_ast { GraphQL::Query::Variables.new( @context, @ast_variables, @provided_variables, ) } end end def irep_selection @selection ||= begin if selected_operation && internal_representation internal_representation.operation_definitions[selected_operation.name] else nil end end end # Node-level cache for calculating arguments. Used during execution and query analysis. # @param ast_node [GraphQL::Language::Nodes::AbstractNode] # @param definition [GraphQL::Schema::Field] # @param parent_object [GraphQL::Schema::Object] # @return Hash{Symbol => Object} def arguments_for(ast_node, definition, parent_object: nil) if interpreter? @arguments_cache ||= Execution::Interpreter::ArgumentsCache.new(self) @arguments_cache.fetch(ast_node, definition, parent_object) else @arguments_cache ||= ArgumentsCache.build(self) @arguments_cache[ast_node][definition] end end # A version of the given query string, with: # - Variables inlined to the query # - Strings replaced with `` # @return [String, nil] Returns nil if the query is invalid. def sanitized_query_string(inline_variables: true) with_prepared_ast { GraphQL::Language::SanitizedPrinter.new(self, inline_variables: inline_variables).sanitized_query_string } end # This contains a few components: # # - The selected operation name (or `anonymous`) # - The fingerprint of the query string # - The number of given variables (for readability) # - The fingerprint of the given variables # # This fingerprint can be used to track runs of the same operation-variables combination over time. # # @see operation_fingerprint # @see variables_fingerprint # @return [String] An opaque hash identifying this operation-variables combination def fingerprint @fingerprint ||= "#{operation_fingerprint}/#{variables_fingerprint}" end # @return [String] An opaque hash for identifying this query's given query string and selected operation def operation_fingerprint @operation_fingerprint ||= "#{selected_operation_name || "anonymous"}/#{Fingerprint.generate(query_string)}" end # @return [String] An opaque hash for identifying this query's given a variable values (not including defaults) def variables_fingerprint @variables_fingerprint ||= "#{provided_variables.size}/#{Fingerprint.generate(provided_variables.to_json)}" end def validation_pipeline with_prepared_ast { @validation_pipeline } end def_delegators :validation_pipeline, :validation_errors, :internal_representation, :analyzers, :ast_analyzers, :max_depth, :max_complexity attr_accessor :analysis_errors def valid? validation_pipeline.valid? && analysis_errors.empty? end def warden with_prepared_ast { @warden } end def_delegators :warden, :get_type, :get_field, :possible_types, :root_type_for_operation # @param abstract_type [GraphQL::UnionType, GraphQL::InterfaceType] # @param value [Object] Any runtime value # @return [GraphQL::ObjectType, nil] The runtime type of `value` from {Schema#resolve_type} # @see {#possible_types} to apply filtering from `only` / `except` def resolve_type(abstract_type, value = :__undefined__) if value.is_a?(Symbol) && value == :__undefined__ # Old method signature value = abstract_type abstract_type = nil end if value.is_a?(GraphQL::Schema::Object) value = value.object end @resolved_types_cache[abstract_type][value] end def mutation? with_prepared_ast { @mutation } end def query? with_prepared_ast { @query } end # @return [void] def merge_filters(only: nil, except: nil) if @prepared_ast raise "Can't add filters after preparing the query" else @filter = @filter.merge(only: only, except: except) end nil end def subscription? with_prepared_ast { @subscription } end # @api private def with_error_handling schema.error_handler.with_error_handling(context) do yield end end private def find_operation(operations, operation_name) if operation_name.nil? && operations.length == 1 operations.values.first elsif !operations.key?(operation_name) nil else operations.fetch(operation_name) end end def prepare_ast @prepared_ast = true @warden ||= GraphQL::Schema::Warden.new(@filter, schema: @schema, context: @context) parse_error = nil @document ||= begin if query_string GraphQL.parse(query_string, tracer: self) end rescue GraphQL::ParseError => err parse_error = err @schema.parse_error(err, @context) nil end @fragments = {} @operations = {} if @document @document.definitions.each do |part| case part when GraphQL::Language::Nodes::FragmentDefinition @fragments[part.name] = part when GraphQL::Language::Nodes::OperationDefinition @operations[part.name] = part end end elsif parse_error # This will be handled later else parse_error = GraphQL::ExecutionError.new("No query string was present") @context.add_error(parse_error) end # Trying to execute a document # with no operations returns an empty hash @ast_variables = [] @mutation = false @subscription = false operation_name_error = nil if @operations.any? @selected_operation = find_operation(@operations, @operation_name) if @selected_operation.nil? operation_name_error = GraphQL::Query::OperationNameMissingError.new(@operation_name) else if @operation_name.nil? @operation_name = @selected_operation.name end @ast_variables = @selected_operation.variables @mutation = @selected_operation.operation_type == "mutation" @query = @selected_operation.operation_type == "query" @subscription = @selected_operation.operation_type == "subscription" end end @validation_pipeline = GraphQL::Query::ValidationPipeline.new( query: self, validate: @validate, parse_error: parse_error, operation_name_error: operation_name_error, max_depth: @max_depth, max_complexity: @max_complexity ) end # Since the query string is processed at the last possible moment, # any internal values which depend on it should be accessed within this wrapper. def with_prepared_ast if !@prepared_ast prepare_ast end yield end end end graphql-ruby-1.11.10/lib/graphql/query/000077500000000000000000000000001414121453000176345ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/query/arguments.rb000066400000000000000000000140541414121453000221720ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query # Read-only access to values, normalizing all keys to strings # # {Arguments} recursively wraps the input in {Arguments} instances. class Arguments extend Forwardable include GraphQL::Dig def self.construct_arguments_class(argument_owner) argument_definitions = argument_owner.arguments argument_owner.arguments_class = Class.new(self) do self.argument_owner = argument_owner self.argument_definitions = argument_definitions argument_definitions.each do |_arg_name, arg_definition| if arg_definition.method_access? expose_as = arg_definition.expose_as.to_s.freeze expose_as_underscored = GraphQL::Schema::Member::BuildType.underscore(expose_as).freeze method_names = [expose_as, expose_as_underscored].uniq method_names.each do |method_name| # Don't define a helper method if it would override something. if method_defined?(method_name) warn( "Unable to define a helper for argument with name '#{method_name}' "\ "as this is a reserved name. Add `method_access: false` to stop this warning." ) else define_method(method_name) do # Always use `expose_as` here, since #[] doesn't accept underscored names self[expose_as] end end end end end end end attr_reader :argument_values def initialize(values, context:, defaults_used:) @argument_values = values.inject({}) do |memo, (inner_key, inner_value)| arg_name = inner_key.to_s arg_defn = self.class.argument_definitions[arg_name] || raise("Not found #{arg_name} among #{self.class.argument_definitions.keys}") arg_default_used = defaults_used.include?(arg_name) arg_value = wrap_value(inner_value, arg_defn.type, context) string_key = arg_defn.expose_as memo[string_key] = ArgumentValue.new(string_key, arg_value, arg_defn, arg_default_used) memo end end # @param key [String, Symbol] name or index of value to access # @return [Object] the argument at that key def [](key) key_s = key.is_a?(String) ? key : key.to_s @argument_values.fetch(key_s, NULL_ARGUMENT_VALUE).value end # @param key [String, Symbol] name of value to access # @return [Boolean] true if the argument was present in this field def key?(key) key_s = key.is_a?(String) ? key : key.to_s @argument_values.key?(key_s) end # @param key [String, Symbol] name of value to access # @return [Boolean] true if the argument default was passed as the argument value to the resolver def default_used?(key) key_s = key.is_a?(String) ? key : key.to_s @argument_values.fetch(key_s, NULL_ARGUMENT_VALUE).default_used? end # Get the hash of all values, with stringified keys # @return [Hash] the stringified hash def to_h @to_h ||= begin h = {} each_value do |arg_value| arg_key = arg_value.definition.expose_as h[arg_key] = unwrap_value(arg_value.value) end h end end def_delegators :to_h, :keys, :values, :each def_delegators :@argument_values, :any? def prepare self end # Access each key, value and type for the arguments in this set. # @yield [argument_value] The {ArgumentValue} for each argument # @yieldparam argument_value [ArgumentValue] def each_value @argument_values.each_value do |argument_value| yield(argument_value) end end class << self attr_accessor :argument_definitions, :argument_owner end NoArguments = Class.new(self) do self.argument_definitions = [] end NO_ARGS = NoArguments.new({}, context: nil, defaults_used: Set.new) # Convert this instance into valid Ruby keyword arguments # @return [{Symbol=>Object}] def to_kwargs ruby_kwargs = {} keys.each do |key| ruby_kwargs[Schema::Member::BuildType.underscore(key).to_sym] = self[key] end ruby_kwargs end alias :to_hash :to_kwargs private class ArgumentValue attr_reader :key, :value, :definition attr_writer :default_used def initialize(key, value, definition, default_used) @key = key @value = value @definition = definition @default_used = default_used end # @return [Boolean] true if the argument default was passed as the argument value to the resolver def default_used? @default_used end end NULL_ARGUMENT_VALUE = ArgumentValue.new(nil, nil, nil, nil) def wrap_value(value, arg_defn_type, context) if value.nil? nil else case arg_defn_type when GraphQL::ListType value.map { |item| wrap_value(item, arg_defn_type.of_type, context) } when GraphQL::NonNullType wrap_value(value, arg_defn_type.of_type, context) when GraphQL::InputObjectType if value.is_a?(Hash) result = arg_defn_type.arguments_class.new(value, context: context, defaults_used: Set.new) result.prepare else value end else value end end end def unwrap_value(value) case value when Array value.map { |item| unwrap_value(item) } when Hash value.inject({}) do |memo, (key, value)| memo[key] = unwrap_value(value) memo end when GraphQL::Query::Arguments, GraphQL::Schema::InputObject value.to_h else value end end end end end graphql-ruby-1.11.10/lib/graphql/query/arguments_cache.rb000066400000000000000000000016131414121453000233120ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../query.rb module GraphQL class Query module ArgumentsCache # @return [Hash Hash GraphQL::Query::Arguments>>] def self.build(query) Hash.new do |h1, irep_or_ast_node| h1[irep_or_ast_node] = Hash.new do |h2, definition| ast_node = irep_or_ast_node.is_a?(GraphQL::InternalRepresentation::Node) ? irep_or_ast_node.ast_node : irep_or_ast_node h2[definition] = if definition.arguments.empty? GraphQL::Query::Arguments::NO_ARGS else GraphQL::Query::LiteralInput.from_arguments( ast_node.arguments, definition, query.variables, ) end end end end end end end graphql-ruby-1.11.10/lib/graphql/query/context.rb000066400000000000000000000256071414121453000216570ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../execution/execute.rb # test_via: ../execution/lazy.rb module GraphQL class Query # Expose some query-specific info to field resolve functions. # It delegates `[]` to the hash that's passed to `GraphQL::Query#initialize`. class Context module SharedMethods # @return [Object] The target for field resolution attr_accessor :object # @return [Hash, Array, String, Integer, Float, Boolean, nil] The resolved value for this field attr_reader :value # @return [Boolean] were any fields of this selection skipped? attr_reader :skipped alias :skipped? :skipped # @api private attr_writer :skipped # Return this value to tell the runtime # to exclude this field from the response altogether def skip GraphQL::Execution::Execute::SKIP end # @return [Boolean] True if this selection has been nullified by a null child def invalid_null? @invalid_null end # Remove this child from the result value # (used for null propagation and skip) # @api private def delete_child(child_ctx) @value.delete(child_ctx.key) end # Create a child context to use for `key` # @param key [String, Integer] The key in the response (name or index) # @param irep_node [InternalRepresentation::Node] The node being evaluated # @api private def spawn_child(key:, irep_node:, object:) FieldResolutionContext.new( @context, key, irep_node, self, object ) end # Add error at query-level. # @param error [GraphQL::ExecutionError] an execution error # @return [void] def add_error(error) if !error.is_a?(ExecutionError) raise TypeError, "expected error to be a ExecutionError, but was #{error.class}" end errors << error nil end # @example Print the GraphQL backtrace during field resolution # puts ctx.backtrace # # @return [GraphQL::Backtrace] The backtrace for this point in query execution def backtrace GraphQL::Backtrace.new(self) end def execution_errors @execution_errors ||= ExecutionErrors.new(self) end def lookahead ast_nodes = irep_node.ast_nodes field = irep_node.definition.metadata[:type_class] || raise("Lookahead is only compatible with class-based schemas") Execution::Lookahead.new(query: query, ast_nodes: ast_nodes, field: field) end end class ExecutionErrors def initialize(ctx) @context = ctx end def add(err_or_msg) err = case err_or_msg when String GraphQL::ExecutionError.new(err_or_msg) when GraphQL::ExecutionError err_or_msg else raise ArgumentError, "expected String or GraphQL::ExecutionError, not #{err_or_msg.class} (#{err_or_msg.inspect})" end # This will assign ast_node and path @context.add_error(err) end alias :>> :add alias :push :add end include SharedMethods extend Forwardable attr_reader :execution_strategy # `strategy` is required by GraphQL::Batch alias_method :strategy, :execution_strategy def execution_strategy=(new_strategy) # GraphQL::Batch re-assigns this value but it was previously not used # (ExecutionContext#strategy was used instead) # now it _is_ used, but it breaks GraphQL::Batch tests @execution_strategy ||= new_strategy end # @return [GraphQL::InternalRepresentation::Node] The internal representation for this query node def irep_node @irep_node ||= query.irep_selection end # @return [GraphQL::Language::Nodes::Field] The AST node for the currently-executing field def ast_node @irep_node.ast_node end # @return [Array] errors returned during execution attr_reader :errors # @return [GraphQL::Query] The query whose context this is attr_reader :query # @return [GraphQL::Schema] attr_reader :schema # @return [Array] The current position in the result attr_reader :path # Make a new context which delegates key lookup to `values` # @param query [GraphQL::Query] the query who owns this context # @param values [Hash] A hash of arbitrary values which will be accessible at query-time def initialize(query:, schema: query.schema, values:, object:) @query = query @schema = schema @provided_values = values || {} @object = object # Namespaced storage, where user-provided values are in `nil` namespace: @storage = Hash.new { |h, k| h[k] = {} } @storage[nil] = @provided_values @errors = [] @path = [] @value = nil @context = self # for SharedMethods @scoped_context = {} end # @api private attr_writer :interpreter # @api private attr_writer :value # @api private attr_accessor :scoped_context def []=(key, value) @provided_values[key] = value end def_delegators :@query, :trace, :interpreter? # @!method []=(key, value) # Reassign `key` to the hash passed to {Schema#execute} as `context:` # Lookup `key` from the hash passed to {Schema#execute} as `context:` def [](key) return @scoped_context[key] if @scoped_context.key?(key) @provided_values[key] end def delete(key) if @scoped_context.key?(key) @scoped_context.delete(key) else @provided_values.delete(key) end end UNSPECIFIED_FETCH_DEFAULT = Object.new def fetch(key, default = UNSPECIFIED_FETCH_DEFAULT) if @scoped_context.key?(key) @scoped_context[key] elsif @provided_values.key?(key) @provided_values[key] elsif default != UNSPECIFIED_FETCH_DEFAULT default elsif block_given? yield(self, key) else raise KeyError.new(key: key) end end def dig(key, *other_keys) @scoped_context.key?(key) ? @scoped_context.dig(key, *other_keys) : @provided_values.dig(key, *other_keys) end def to_h @provided_values.merge(@scoped_context) end alias :to_hash :to_h def key?(key) @scoped_context.key?(key) || @provided_values.key?(key) end # @return [GraphQL::Schema::Warden] def warden @warden ||= @query.warden end # Get an isolated hash for `ns`. Doesn't affect user-provided storage. # @param ns [Object] a usage-specific namespace identifier # @return [Hash] namespaced storage def namespace(ns) @storage[ns] end def inspect "#" end # @api private def received_null_child @invalid_null = true @value = nil end def scoped_merge!(hash) @scoped_context = @scoped_context.merge(hash) end def scoped_set!(key, value) scoped_merge!(key => value) nil end class FieldResolutionContext include SharedMethods include Tracing::Traceable extend Forwardable attr_reader :irep_node, :field, :parent_type, :query, :schema, :parent, :key, :type alias :selection :irep_node def initialize(context, key, irep_node, parent, object) @context = context @key = key @parent = parent @object = object @irep_node = irep_node @field = irep_node.definition @parent_type = irep_node.owner_type @type = field.type # This is needed constantly, so set it ahead of time: @query = context.query @schema = context.schema @tracers = @query.tracers # This hack flag is required by ConnectionResolve @wrapped_connection = false @wrapped_object = false end # @api private attr_accessor :wrapped_connection, :wrapped_object def path @path ||= @parent.path.dup << @key end def_delegators :@context, :[], :[]=, :key?, :fetch, :to_h, :namespace, :dig, :spawn, :warden, :errors, :execution_strategy, :strategy, :interpreter? # @return [GraphQL::Language::Nodes::Field] The AST node for the currently-executing field def ast_node @irep_node.ast_node end # Add error to current field resolution. # @param error [GraphQL::ExecutionError] an execution error # @return [void] def add_error(error) super error.ast_node ||= irep_node.ast_node error.path ||= path nil end def inspect "#" end # Set a new value for this field in the response. # It may be updated after resolving a {Lazy}. # If it is {Execute::PROPAGATE_NULL}, tell the owner to propagate null. # If it's {Execute::Execution::SKIP}, remove this field result from its parent # @param new_value [Any] The GraphQL-ready value # @api private def value=(new_value) case new_value when GraphQL::Execution::Execute::PROPAGATE_NULL, nil @invalid_null = true @value = nil if @type.kind.non_null? @parent.received_null_child end when GraphQL::Execution::Execute::SKIP @parent.skipped = true @parent.delete_child(self) else @value = new_value end end protected def received_null_child case @value when Hash self.value = GraphQL::Execution::Execute::PROPAGATE_NULL when Array if list_of_non_null_items?(@type) self.value = GraphQL::Execution::Execute::PROPAGATE_NULL end when nil # TODO This is a hack # It was already nulled out but it's getting reassigned else raise "Unexpected value for received_null_child (#{self.value.class}): #{value}" end end private def list_of_non_null_items?(type) case type when GraphQL::NonNullType # Unwrap [T]! list_of_non_null_items?(type.of_type) when GraphQL::ListType type.of_type.is_a?(GraphQL::NonNullType) else raise "Unexpected list_of_non_null_items check: #{type}" end end end end end end graphql-ruby-1.11.10/lib/graphql/query/executor.rb000066400000000000000000000026751414121453000220310ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../query.rb module GraphQL class Query class Executor class PropagateNull < StandardError; end # @return [GraphQL::Query] the query being executed attr_reader :query def initialize(query) @query = query end # Evaluate {operation_name} on {query}. # Handle {GraphQL::ExecutionError}s by putting them in the "errors" key. # @return [Hash] A GraphQL response, with either a "data" key or an "errors" key def result execute rescue GraphQL::ExecutionError => err query.context.errors << err {"errors" => [err.to_h]} end private def execute operation = query.selected_operation return {} if operation.nil? op_type = operation.operation_type root_type = query.root_type_for_operation(op_type) execution_strategy_class = query.schema.execution_strategy_for_operation(op_type) execution_strategy = execution_strategy_class.new query.context.execution_strategy = execution_strategy data_result = begin execution_strategy.execute(operation, root_type, query) rescue PropagateNull nil end result = { "data" => data_result } error_result = query.context.errors.map(&:to_h) if error_result.any? result["errors"] = error_result end result end end end end graphql-ruby-1.11.10/lib/graphql/query/fingerprint.rb000066400000000000000000000013401414121453000225060ustar00rootroot00000000000000# frozen_string_literal: true require 'digest/sha2' module GraphQL class Query # @api private # @see Query#query_fingerprint # @see Query#variables_fingerprint # @see Query#fingerprint module Fingerprint # Make an obfuscated hash of the given string (either a query string or variables JSON) # @param string [String] # @return [String] A normalized, opaque hash def self.generate(input_str) # Implemented to be: # - Short (and uniform) length # - Stable # - Irreversibly Opaque (don't want to leak variable values) # - URL-friendly bytes = Digest::SHA256.digest(input_str) Base64.urlsafe_encode64(bytes) end end end end graphql-ruby-1.11.10/lib/graphql/query/input_validation_result.rb000066400000000000000000000021611414121453000251300ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query class InputValidationResult attr_accessor :problems def initialize(valid: true, problems: nil) @valid = valid @problems = problems end def valid? @valid end def add_problem(explanation, path = nil, extensions: nil, message: nil) @problems ||= [] @valid = false problem = { "path" => path || [], "explanation" => explanation } if extensions problem["extensions"] = extensions end if message problem["message"] = message end @problems.push(problem) end def merge_result!(path, inner_result) return if inner_result.valid? if inner_result.problems inner_result.problems.each do |p| item_path = [path, *p["path"]] add_problem(p["explanation"], item_path, message: p["message"], extensions: p["extensions"]) end end # It could have been explicitly set on inner_result (if it had no problems) @valid = false end end end end graphql-ruby-1.11.10/lib/graphql/query/literal_input.rb000066400000000000000000000121351414121453000230360ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query # Turn query string values into something useful for query execution class LiteralInput def self.coerce(type, ast_node, variables) case ast_node when nil nil when Language::Nodes::NullValue nil when Language::Nodes::VariableIdentifier variables[ast_node.name] else case type.kind.name when "SCALAR" # TODO smell # This gets used for plain values during subscriber.trigger if variables type.coerce_input(ast_node, variables.context) else type.coerce_isolated_input(ast_node) end when "ENUM" # TODO smell # This gets used for plain values sometimes v = ast_node.is_a?(GraphQL::Language::Nodes::Enum) ? ast_node.name : ast_node if variables type.coerce_input(v, variables.context) else type.coerce_isolated_input(v) end when "NON_NULL" LiteralInput.coerce(type.of_type, ast_node, variables) when "LIST" if ast_node.is_a?(Array) ast_node.map { |element_ast| LiteralInput.coerce(type.of_type, element_ast, variables) } else [LiteralInput.coerce(type.of_type, ast_node, variables)] end when "INPUT_OBJECT" # TODO smell: handling AST vs handling plain Ruby next_args = ast_node.is_a?(Hash) ? ast_node : ast_node.arguments from_arguments(next_args, type, variables) else raise "Invariant: unexpected type to coerce to: #{type}" end end end def self.from_arguments(ast_arguments, argument_owner, variables) context = variables ? variables.context : nil values_hash = {} defaults_used = Set.new indexed_arguments = case ast_arguments when Hash ast_arguments when Array ast_arguments.each_with_object({}) { |a, memo| memo[a.name] = a } else raise ArgumentError, "Unexpected ast_arguments: #{ast_arguments}" end argument_defns = argument_owner.arguments argument_defns.each do |arg_name, arg_defn| ast_arg = indexed_arguments[arg_name] # First, check the argument in the AST. # If the value is a variable, # only add a value if the variable is actually present. # Otherwise, coerce the value in the AST, prepare the value and add it. # # TODO: since indexed_arguments can come from a plain Ruby hash, # have to check for `false` or `nil` as hash values. This is getting smelly :S if indexed_arguments.key?(arg_name) arg_value = ast_arg.is_a?(GraphQL::Language::Nodes::Argument) ? ast_arg.value : ast_arg value_is_a_variable = arg_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) if (!value_is_a_variable || (value_is_a_variable && variables.key?(arg_value.name))) value = coerce(arg_defn.type, arg_value, variables) # Legacy `prepare` application if arg_defn.is_a?(GraphQL::Argument) value = arg_defn.prepare(value, context) end if value.is_a?(GraphQL::ExecutionError) value.ast_node = ast_arg raise value end values_hash[arg_name] = value end end # Then, the definition for a default value. # If the definition has a default value and # a value wasn't provided from the AST, # then add the default value. if arg_defn.default_value? && !values_hash.key?(arg_name) value = arg_defn.default_value defaults_used << arg_name # `context` isn't present when pre-calculating defaults if context if arg_defn.is_a?(GraphQL::Argument) value = arg_defn.prepare(value, context) end if value.is_a?(GraphQL::ExecutionError) value.ast_node = ast_arg raise value end end values_hash[arg_name] = value end end if argument_owner.is_a?(Class) || argument_owner.is_a?(GraphQL::Schema::Field) # A Schema::InputObject, Schema::GraphQL::Field, Schema::Directive, logic from Query::Arguments#to_kwargs ruby_kwargs = {} values_hash.each do |key, value| ruby_kwargs[Schema::Member::BuildType.underscore(key).to_sym] = value end if argument_owner.is_a?(Class) && argument_owner < GraphQL::Schema::InputObject argument_owner.new(ruby_kwargs: ruby_kwargs, context: context, defaults_used: defaults_used) else ruby_kwargs end else result = argument_owner.arguments_class.new(values_hash, context: context, defaults_used: defaults_used) result.prepare end end end end end graphql-ruby-1.11.10/lib/graphql/query/null_context.rb000066400000000000000000000016011414121453000226750ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query # This object can be `ctx` in places where there is no query class NullContext class NullWarden < GraphQL::Schema::Warden def visible?(t); true; end def visible_field?(t); true; end def visible_type?(t); true; end end attr_reader :schema, :query, :warden def initialize @query = nil @schema = GraphQL::Schema.new @warden = NullWarden.new( GraphQL::Filter.new, context: self, schema: @schema, ) end def [](key); end def interpreter? false end class << self extend Forwardable def [](key); end def instance @instance = self.new end def_delegators :instance, :query, :schema, :warden, :interpreter? end end end end graphql-ruby-1.11.10/lib/graphql/query/result.rb000066400000000000000000000031631414121453000215020ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query # A result from {Schema#execute}. # It provides the requested data and # access to the {Query} and {Query::Context}. class Result extend Forwardable def initialize(query:, values:) @query = query @to_h = values end # @return [GraphQL::Query] The query that was executed attr_reader :query # @return [Hash] The resulting hash of "data" and/or "errors" attr_reader :to_h def_delegators :@query, :context, :mutation?, :query?, :subscription? def_delegators :@to_h, :[], :keys, :values, :to_json, :as_json # Delegate any hash-like method to the underlying hash. def method_missing(method_name, *args, &block) if @to_h.respond_to?(method_name) @to_h.public_send(method_name, *args, &block) else super end end def respond_to_missing?(method_name, include_private = false) @to_h.respond_to?(method_name) || super end def inspect "#" end # A result is equal to another object when: # # - The other object is a Hash whose value matches `result.to_h` # - The other object is a Result whose value matches `result.to_h` # # (The query is ignored for comparing result equality.) # # @return [Boolean] def ==(other) case other when Hash @to_h == other when Query::Result @to_h == other.to_h else super end end end end end graphql-ruby-1.11.10/lib/graphql/query/serial_execution.rb000066400000000000000000000024571414121453000235330ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/query/serial_execution/value_resolution" require "graphql/query/serial_execution/field_resolution" require "graphql/query/serial_execution/operation_resolution" require "graphql/query/serial_execution/selection_resolution" module GraphQL class Query class SerialExecution # This is the only required method for an Execution strategy. # You could create a custom execution strategy and configure your schema to # use that custom strategy instead. # # @param ast_operation [GraphQL::Language::Nodes::OperationDefinition] The operation definition to run # @param root_type [GraphQL::ObjectType] either the query type or the mutation type # @param query_object [GraphQL::Query] the query object for this execution # @return [Hash] a spec-compliant GraphQL result, as a hash def execute(ast_operation, root_type, query_object) operation_resolution.resolve( query_object.irep_selection, root_type, query_object ) end def field_resolution self.class::FieldResolution end def operation_resolution self.class::OperationResolution end def selection_resolution self.class::SelectionResolution end end end end graphql-ruby-1.11.10/lib/graphql/query/serial_execution/000077500000000000000000000000001414121453000231765ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/query/serial_execution/field_resolution.rb000066400000000000000000000054571414121453000271040ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query class SerialExecution class FieldResolution attr_reader :irep_node, :parent_type, :target, :field, :arguments, :query def initialize(selection, parent_type, target, query_ctx) @irep_node = selection @selection = selection @parent_type = parent_type @target = target @query = query_ctx.query @field = irep_node.definition @field_ctx = query_ctx.spawn_child( key: irep_node.name, object: target, irep_node: irep_node, ) @arguments = @query.arguments_for(irep_node, @field) end def result result_name = irep_node.name raw_value = get_raw_value if raw_value.is_a?(GraphQL::Execution::Execute::Skip) {} else { result_name => get_finished_value(raw_value) } end end # GraphQL::Batch depends on this def execution_context @field_ctx end private # After getting the value from the field's resolve method, # continue by "finishing" the value, eg. executing sub-fields or coercing values def get_finished_value(raw_value) case raw_value when GraphQL::ExecutionError raw_value.ast_node = @field_ctx.ast_node raw_value.path = @field_ctx.path @query.context.errors.push(raw_value) when Array list_errors = raw_value.each_with_index.select { |value, _| value.is_a?(GraphQL::ExecutionError) } if list_errors.any? list_errors.each do |error, index| error.ast_node = @field_ctx.ast_node error.path = @field_ctx.path + [index] @query.context.errors.push(error) end end end begin GraphQL::Query::SerialExecution::ValueResolution.resolve( parent_type, field, field.type, raw_value, @selection, @field_ctx, ) rescue GraphQL::Query::Executor::PropagateNull if field.type.kind.non_null? raise else nil end end end # Get the result of: # - Any middleware on this schema # - The field's resolve method # If the middleware chain returns a GraphQL::ExecutionError, its message # is added to the "errors" key. def get_raw_value begin @field_ctx.schema.middleware.invoke([parent_type, target, field, arguments, @field_ctx]) rescue GraphQL::ExecutionError => err err end end end end end end graphql-ruby-1.11.10/lib/graphql/query/serial_execution/operation_resolution.rb000066400000000000000000000006451414121453000300130ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query class SerialExecution module OperationResolution def self.resolve(selection, target, query) result = query.context.execution_strategy.selection_resolution.resolve( query.root_value, target, selection, query.context, ) result end end end end end graphql-ruby-1.11.10/lib/graphql/query/serial_execution/selection_resolution.rb000066400000000000000000000011171414121453000277730ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query class SerialExecution module SelectionResolution def self.resolve(target, current_type, selection, query_ctx) selection_result = {} selection.typed_children[current_type].each do |name, subselection| selection_result.merge!(query_ctx.execution_strategy.field_resolution.new( subselection, current_type, target, query_ctx ).result) end selection_result end end end end end graphql-ruby-1.11.10/lib/graphql/query/serial_execution/value_resolution.rb000066400000000000000000000055611414121453000271310ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query class SerialExecution module ValueResolution def self.resolve(parent_type, field_defn, field_type, value, selection, query_ctx) if value.nil? || value.is_a?(GraphQL::ExecutionError) if field_type.kind.non_null? if value.nil? type_error = GraphQL::InvalidNullError.new(parent_type, field_defn, value) query_ctx.schema.type_error(type_error, query_ctx) end raise GraphQL::Query::Executor::PropagateNull else nil end else case field_type.kind when GraphQL::TypeKinds::SCALAR, GraphQL::TypeKinds::ENUM field_type.coerce_result(value, query_ctx) when GraphQL::TypeKinds::LIST wrapped_type = field_type.of_type result = [] i = 0 value.each do |inner_value| inner_ctx = query_ctx.spawn_child( key: i, object: inner_value, irep_node: selection, ) result << resolve( parent_type, field_defn, wrapped_type, inner_value, selection, inner_ctx, ) i += 1 end result when GraphQL::TypeKinds::NON_NULL wrapped_type = field_type.of_type resolve( parent_type, field_defn, wrapped_type, value, selection, query_ctx, ) when GraphQL::TypeKinds::OBJECT query_ctx.execution_strategy.selection_resolution.resolve( value, field_type, selection, query_ctx ) when GraphQL::TypeKinds::UNION, GraphQL::TypeKinds::INTERFACE query = query_ctx.query resolved_type = query.resolve_type(value) possible_types = query.possible_types(field_type) if !possible_types.include?(resolved_type) type_error = GraphQL::UnresolvedTypeError.new(value, field_defn, parent_type, resolved_type, possible_types) query.schema.type_error(type_error, query_ctx) raise GraphQL::Query::Executor::PropagateNull else resolve( parent_type, field_defn, resolved_type, value, selection, query_ctx, ) end else raise("Unknown type kind: #{field_type.kind}") end end end end end end end graphql-ruby-1.11.10/lib/graphql/query/validation_pipeline.rb000066400000000000000000000110371414121453000242020ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query # Contain the validation pipeline and expose the results. # # 0. Checks in {Query#initialize}: # - Rescue a ParseError, halt if there is one # - Check for selected operation, halt if not found # 1. Validate the AST, halt if errors # 2. Validate the variables, halt if errors # 3. Run query analyzers, halt if errors # # {#valid?} is false if any of the above checks halted the pipeline. # # @api private class ValidationPipeline attr_reader :max_depth, :max_complexity def initialize(query:, validate:, parse_error:, operation_name_error:, max_depth:, max_complexity:) @validation_errors = [] @internal_representation = nil @validate = validate @parse_error = parse_error @operation_name_error = operation_name_error @query = query @schema = query.schema @max_depth = max_depth @max_complexity = max_complexity @has_validated = false end # @return [Boolean] does this query have errors that should prevent it from running? def valid? ensure_has_validated @valid end # @return [Array] Static validation errors for the query string def validation_errors ensure_has_validated @validation_errors end # @return [Hash GraphQL::InternalRepresentation::Node] Operation name -> Irep node pairs def internal_representation ensure_has_validated @internal_representation end def analyzers ensure_has_validated @query_analyzers end private # If the pipeline wasn't run yet, run it. # If it was already run, do nothing. def ensure_has_validated return if @has_validated @has_validated = true if @parse_error # This is kind of crazy: we push the parse error into `ctx` # in {DefaultParseError} so that users can _opt out_ by redefining that hook. # That means we can't _re-add_ the error here (otherwise we'd either # add it twice _or_ override the user's choice to not add it). # So we just have to know that it was invalid and go from there. @valid = false return elsif @operation_name_error @validation_errors << @operation_name_error else validation_result = @schema.static_validator.validate(@query, validate: @validate, timeout: @schema.validate_timeout, max_errors: @schema.validate_max_errors) @validation_errors.concat(validation_result[:errors]) @internal_representation = validation_result[:irep] if @validation_errors.empty? @validation_errors.concat(@query.variables.errors) end if @validation_errors.empty? @query_analyzers = build_analyzers( @schema, @max_depth, @max_complexity ) end end @valid = @validation_errors.empty? rescue SystemStackError => err @valid = false @schema.query_stack_error(@query, err) end # If there are max_* values, add them, # otherwise reuse the schema's list of analyzers. def build_analyzers(schema, max_depth, max_complexity) qa = schema.query_analyzers.dup # Filter out the built in authorization analyzer. # It is deprecated and does not have an AST analyzer alternative. qa = qa.select do |analyzer| if analyzer == GraphQL::Authorization::Analyzer && schema.using_ast_analysis? raise "The Authorization analyzer is not supported with AST Analyzers" else true end end if max_depth || max_complexity # Depending on the analysis engine, we must use different analyzers # remove this once everything has switched over to AST analyzers if schema.using_ast_analysis? if max_depth qa << GraphQL::Analysis::AST::MaxQueryDepth end if max_complexity qa << GraphQL::Analysis::AST::MaxQueryComplexity end else if max_depth qa << GraphQL::Analysis::MaxQueryDepth.new(max_depth) end if max_complexity qa << GraphQL::Analysis::MaxQueryComplexity.new(max_complexity) end end qa else qa end end end end end graphql-ruby-1.11.10/lib/graphql/query/variable_validation_error.rb000066400000000000000000000025071414121453000253750ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query class VariableValidationError < GraphQL::ExecutionError attr_accessor :value, :validation_result def initialize(variable_ast, type, value, validation_result) @value = value @validation_result = validation_result msg = "Variable $#{variable_ast.name} of type #{type.to_type_signature} was provided invalid value" if problem_fields.any? msg += " for #{problem_fields.join(", ")}" end super(msg) self.ast_node = variable_ast end def to_h # It is possible there are other extension items in this error, so handle # a one level deep merge explicitly. However beyond that only show the # latest value and problems. super.merge({ "extensions" => { "value" => value, "problems" => validation_result.problems }}) do |key, oldValue, newValue| if oldValue.respond_to? merge oldValue.merge(newValue) else newValue end end end private def problem_fields @problem_fields ||= @validation_result .problems .reject { |problem| problem["path"].empty? } .map { |problem| "#{problem['path'].join('.')} (#{problem['explanation']})" } end end end end graphql-ruby-1.11.10/lib/graphql/query/variables.rb000066400000000000000000000063701414121453000221370ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Query # Read-only access to query variables, applying default values if needed. class Variables extend Forwardable # @return [Array] Any errors encountered when parsing the provided variables and literal values attr_reader :errors attr_reader :context def initialize(ctx, ast_variables, provided_variables) schema = ctx.schema @context = ctx @provided_variables = GraphQL::Argument.deep_stringify(provided_variables) @errors = [] @storage = ast_variables.each_with_object({}) do |ast_variable, memo| # Find the right value for this variable: # - First, use the value provided at runtime # - Then, fall back to the default value from the query string # If it's still nil, raise an error if it's required. variable_type = schema.type_from_ast(ast_variable.type, context: ctx) if variable_type.nil? # Pass -- it will get handled by a validator else variable_name = ast_variable.name default_value = ast_variable.default_value provided_value = @provided_variables[variable_name] value_was_provided = @provided_variables.key?(variable_name) begin validation_result = variable_type.validate_input(provided_value, ctx) if validation_result.valid? if value_was_provided # Add the variable if a value was provided memo[variable_name] = if ctx.interpreter? provided_value elsif provided_value.nil? nil else schema.error_handler.with_error_handling(context) do variable_type.coerce_input(provided_value, ctx) end end elsif default_value != nil memo[variable_name] = if ctx.interpreter? default_value else # Add the variable if it wasn't provided but it has a default value (including `null`) GraphQL::Query::LiteralInput.coerce(variable_type, default_value, self) end end end rescue GraphQL::ExecutionError => ex # TODO: This should really include the path to the problematic node in the variable value # like InputValidationResults generated by validate_non_null_input but unfortunately we don't # have this information available in the coerce_input call chain. Note this path is the path # that appears under errors.extensions.problems.path and NOT the result path under errors.path. validation_result = GraphQL::Query::InputValidationResult.new validation_result.add_problem(ex.message) end if !validation_result.valid? @errors << GraphQL::Query::VariableValidationError.new(ast_variable, variable_type, provided_value, validation_result) end end end end def_delegators :@storage, :length, :key?, :[], :fetch, :to_h end end end graphql-ruby-1.11.10/lib/graphql/railtie.rb000066400000000000000000000104741414121453000204530ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Railtie < Rails::Railtie rake_tasks do # Defer this so that you only need the `parser` gem when you _run_ the upgrader def load_upgraders require_relative './upgrader/member' require_relative './upgrader/schema' end namespace :graphql do task :upgrade, [:dir] do |t, args| unless (dir = args[:dir]) fail 'You have to give me a directory where your GraphQL schema and types live. ' \ 'For example: `bin/rake graphql:upgrade[app/graphql/**/*]`' end Dir[dir].each do |file| # Members (types, interfaces, etc.) if file =~ /.*_(type|interface|enum|union|)\.rb$/ Rake::Task["graphql:upgrade:member"].execute(Struct.new(:member_file).new(file)) end end puts "Upgrade complete! Note that this is a best-effort approach, and may very well contain some bugs." puts "Don't forget to create the base objects. For example, you could run:" puts "\tbin/rake graphql:upgrade:create_base_objects[app/graphql]" end namespace :upgrade do task :create_base_objects, [:base_dir] do |t, args| unless (base_dir = args[:base_dir]) fail 'You have to give me a directory where your GraphQL types live. ' \ 'For example: `bin/rake graphql:upgrade:create_base_objects[app/graphql]`' end destination_file = File.join(base_dir, "types", "base_scalar.rb") unless File.exists?(destination_file) FileUtils.mkdir_p(File.dirname(destination_file)) File.open(destination_file, 'w') do |f| f.puts "class Types::BaseScalar < GraphQL::Schema::Scalar\nend" end end destination_file = File.join(base_dir, "types", "base_input_object.rb") unless File.exists?(destination_file) FileUtils.mkdir_p(File.dirname(destination_file)) File.open(destination_file, 'w') do |f| f.puts "class Types::BaseInputObject < GraphQL::Schema::InputObject\nend" end end destination_file = File.join(base_dir, "types", "base_enum.rb") unless File.exists?(destination_file) FileUtils.mkdir_p(File.dirname(destination_file)) File.open(destination_file, 'w') do |f| f.puts "class Types::BaseEnum < GraphQL::Schema::Enum\nend" end end destination_file = File.join(base_dir, "types", "base_union.rb") unless File.exists?(destination_file) FileUtils.mkdir_p(File.dirname(destination_file)) File.open(destination_file, 'w') do |f| f.puts "class Types::BaseUnion < GraphQL::Schema::Union\nend" end end destination_file = File.join(base_dir, "types", "base_interface.rb") unless File.exists?(destination_file) FileUtils.mkdir_p(File.dirname(destination_file)) File.open(destination_file, 'w') do |f| f.puts "module Types::BaseInterface\n include GraphQL::Schema::Interface\nend" end end destination_file = File.join(base_dir, "types", "base_object.rb") unless File.exists?(destination_file) File.open(destination_file, 'w') do |f| f.puts "class Types::BaseObject < GraphQL::Schema::Object\nend" end end end task :schema, [:schema_file] do |t, args| schema_file = args.schema_file load_upgraders upgrader = GraphQL::Upgrader::Schema.new File.read(schema_file) puts "- Transforming schema #{schema_file}" File.open(schema_file, 'w') { |f| f.write upgrader.upgrade } end task :member, [:member_file] do |t, args| member_file = args.member_file load_upgraders upgrader = GraphQL::Upgrader::Member.new File.read(member_file) next unless upgrader.upgradeable? puts "- Transforming member #{member_file}" File.open(member_file, 'w') { |f| f.write upgrader.upgrade } end end end end end end graphql-ruby-1.11.10/lib/graphql/rake_task.rb000066400000000000000000000077451414121453000207750ustar00rootroot00000000000000# frozen_string_literal: true require "fileutils" require "graphql/rake_task/validate" module GraphQL # A rake task for dumping a schema as IDL or JSON. # # By default, schemas are looked up by name as constants using `schema_name:`. # You can provide a `load_schema` function to return your schema another way. # # `load_context:`, `only:` and `except:` are supported so that # you can keep an eye on how filters affect your schema. # # @example Dump a Schema to .graphql + .json files # require "graphql/rake_task" # GraphQL::RakeTask.new(schema_name: "MySchema") # # # $ rake graphql:schema:dump # # Schema IDL dumped to ./schema.graphql # # Schema JSON dumped to ./schema.json # # @example Invoking the task from Ruby # require "rake" # Rake::Task["graphql:schema:dump"].invoke class RakeTask include Rake::DSL DEFAULT_OPTIONS = { namespace: "graphql", dependencies: nil, schema_name: nil, load_schema: ->(task) { Object.const_get(task.schema_name) }, load_context: ->(task) { {} }, only: nil, except: nil, directory: ".", idl_outfile: "schema.graphql", json_outfile: "schema.json", } # @return [String] Namespace for generated tasks attr_writer :namespace def rake_namespace @namespace end # @return [Array] attr_accessor :dependencies # @return [String] By default, used to find the schema as a constant. # @see {#load_schema} for loading a schema another way attr_accessor :schema_name # @return [<#call(task)>] A proc for loading the target GraphQL schema attr_accessor :load_schema # @return [<#call(task)>] A callable for loading the query context attr_accessor :load_context # @return [<#call(member, ctx)>, nil] A filter for this task attr_accessor :only # @return [<#call(member, ctx)>, nil] A filter for this task attr_accessor :except # @return [String] target for IDL task attr_accessor :idl_outfile # @return [String] target for JSON task attr_accessor :json_outfile # @return [String] directory for IDL & JSON files attr_accessor :directory # Set the parameters of this task by passing keyword arguments # or assigning attributes inside the block def initialize(options = {}) all_options = DEFAULT_OPTIONS.merge(options) all_options.each do |k, v| self.public_send("#{k}=", v) end if block_given? yield(self) end define_task end private # Use the provided `method_name` to generate a string from the specified schema # then write it to `file`. def write_outfile(method_name, file) schema = @load_schema.call(self) context = @load_context.call(self) result = schema.public_send(method_name, only: @only, except: @except, context: context) dir = File.dirname(file) FileUtils.mkdir_p(dir) File.write(file, result) end def idl_path File.join(@directory, @idl_outfile) end def json_path File.join(@directory, @json_outfile) end def load_rails_environment_if_defined if Rake::Task.task_defined?('environment') Rake::Task['environment'].invoke end end # Use the Rake DSL to add tasks def define_task namespace(@namespace) do namespace("schema") do desc("Dump the schema to IDL in #{idl_path}") task :idl => @dependencies do load_rails_environment_if_defined write_outfile(:to_definition, idl_path) puts "Schema IDL dumped into #{idl_path}" end desc("Dump the schema to JSON in #{json_path}") task :json => @dependencies do load_rails_environment_if_defined write_outfile(:to_json, json_path) puts "Schema JSON dumped into #{json_path}" end desc("Dump the schema to JSON and IDL") task :dump => [:idl, :json] end end end end end graphql-ruby-1.11.10/lib/graphql/rake_task/000077500000000000000000000000001414121453000204335ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/rake_task/validate.rb000066400000000000000000000046261414121453000225610ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class RakeTask extend Rake::DSL desc "Get the checksum of a graphql-pro version and compare it to published versions on GitHub and graphql-ruby.org" task "graphql:pro:validate", [:gem_version] do |t, args| version = args[:gem_version] if version.nil? raise ArgumentError, "A specific version is required, eg `rake graphql:pro:validate[1.12.0]`" end check = "\e[32m✓\e[0m" ex = "\e[31m✘\e[0m" puts "Validating graphql-pro v#{version}" puts " - Checking for graphql-pro credentials..." creds = `bundle config gems.graphql.pro`[/[a-z0-9]{11}:[a-z0-9]{11}/] if creds.nil? puts " #{ex} failed, please set with `bundle config gems.graphql.pro $MY_CREDENTIALS`" exit(1) else puts " #{check} found" end puts " - Fetching the gem..." fetch_result = `gem fetch graphql-pro -v #{version} --source https://#{creds}@gems.graphql.pro` if fetch_result.empty? puts " #{ex} failed to fetch v#{version}" exit(1) else puts " #{check} fetched" end puts " - Validating digest..." require "digest/sha2" gem_digest = Digest::SHA512.new.hexdigest(File.read("graphql-pro-#{version}.gem")) require "net/http" github_uri = URI("https://raw.githubusercontent.com/rmosolgo/graphql-ruby/master/guides/pro/checksums/graphql-pro-#{version}.txt") # Remove final newline from .txt file github_digest = Net::HTTP.get(github_uri).chomp docs_uri = URI("https://graphql-ruby.org/pro/checksums/graphql-pro-#{version}.txt") docs_digest = Net::HTTP.get(docs_uri).chomp if docs_digest == gem_digest && github_digest == gem_digest puts " #{check} validated from GitHub" puts " #{check} validated from graphql-ruby.org" else puts " #{ex} SHA mismatch:" puts " Downloaded: #{gem_digest}" puts " GitHub: #{github_digest}" puts " graphql-ruby.org: #{docs_digest}" puts "" puts " This download of graphql-pro is invalid, please open an issue:" puts " https://github.com/rmosolgo/graphql-ruby/issues/new?title=graphql-pro%20digest%20mismatch%20(#{version})" exit(1) end puts "\e[32m✔\e[0m graphql-pro #{version} validated successfully!" end end end graphql-ruby-1.11.10/lib/graphql/relay.rb000066400000000000000000000012251414121453000201300ustar00rootroot00000000000000# frozen_string_literal: true require 'graphql/relay/page_info' require 'graphql/relay/edge' require 'graphql/relay/edge_type' require 'graphql/relay/edges_instrumentation' require 'graphql/relay/base_connection' require 'graphql/relay/array_connection' require 'graphql/relay/range_add' require 'graphql/relay/relation_connection' require 'graphql/relay/mongo_relation_connection' require 'graphql/relay/global_id_resolve' require 'graphql/relay/mutation' require 'graphql/relay/node' require 'graphql/relay/connection_instrumentation' require 'graphql/relay/connection_resolve' require 'graphql/relay/connection_type' require 'graphql/relay/type_extensions' graphql-ruby-1.11.10/lib/graphql/relay/000077500000000000000000000000001414121453000176035ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/relay/array_connection.rb000066400000000000000000000043441414121453000234720ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay class ArrayConnection < BaseConnection def cursor_from_node(item) idx = (after ? index_from_cursor(after) : 0) + sliced_nodes.find_index(item) + 1 encode(idx.to_s) end def has_next_page if first # There are more items after these items sliced_nodes.count > first elsif GraphQL::Relay::ConnectionType.bidirectional_pagination && before # The original array is longer than the `before` index index_from_cursor(before) < nodes.length + 1 else false end end def has_previous_page if last # There are items preceding the ones in this result sliced_nodes.count > last elsif GraphQL::Relay::ConnectionType.bidirectional_pagination && after # We've paginated into the Array a bit, there are some behind us index_from_cursor(after) > 0 else false end end def first @first ||= begin capped = limit_pagination_argument(arguments[:first], max_page_size) if capped.nil? && last.nil? capped = max_page_size end capped end end def last @last ||= limit_pagination_argument(arguments[:last], max_page_size) end private # apply first / last limit results def paged_nodes @paged_nodes ||= begin items = sliced_nodes items = items.first(first) if first items = items.last(last) if last items = items.first(max_page_size) if max_page_size && !first && !last items end end # Apply cursors to edges def sliced_nodes @sliced_nodes ||= if before && after nodes[index_from_cursor(after)..index_from_cursor(before)-1] || [] elsif before nodes[0..index_from_cursor(before)-2] || [] elsif after nodes[index_from_cursor(after)..-1] || [] else nodes end end def index_from_cursor(cursor) decode(cursor).to_i end end BaseConnection.register_connection_implementation(Array, ArrayConnection) end end graphql-ruby-1.11.10/lib/graphql/relay/base_connection.rb000066400000000000000000000146131414121453000232660ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # Subclasses must implement: # - {#cursor_from_node}, which returns an opaque cursor for the given item # - {#sliced_nodes}, which slices by `before` & `after` # - {#paged_nodes}, which applies `first` & `last` limits # # In a subclass, you have access to # - {#nodes}, the collection which the connection will wrap # - {#first}, {#after}, {#last}, {#before} (arguments passed to the field) # - {#max_page_size} (the specified maximum page size that can be returned from a connection) # class BaseConnection # Just to encode data in the cursor, use something that won't conflict CURSOR_SEPARATOR = "---" # Map of collection class names -> connection_classes # eg `{"Array" => ArrayConnection}` CONNECTION_IMPLEMENTATIONS = {} class << self # Find a connection implementation suitable for exposing `nodes` # # @param nodes [Object] A collection of nodes (eg, Array, AR::Relation) # @return [subclass of BaseConnection] a connection Class for wrapping `nodes` def connection_for_nodes(nodes) # If it's a new-style connection object, it's already ready to go if nodes.is_a?(GraphQL::Pagination::Connection) return nodes end # Check for class _names_ because classes can be redefined in Rails development nodes.class.ancestors.each do |ancestor| conn_impl = CONNECTION_IMPLEMENTATIONS[ancestor.name] if conn_impl return conn_impl end end # Should have found a connection during the loop: raise("No connection implementation to wrap #{nodes.class} (#{nodes})") end # Add `connection_class` as the connection wrapper for `nodes_class` # eg, `RelationConnection` is the implementation for `AR::Relation` # @param nodes_class [Class] A class representing a collection (eg, Array, AR::Relation) # @param connection_class [Class] A class implementing Connection methods def register_connection_implementation(nodes_class, connection_class) CONNECTION_IMPLEMENTATIONS[nodes_class.name] = connection_class end end attr_reader :nodes, :arguments, :max_page_size, :parent, :field, :context # Make a connection, wrapping `nodes` # @param nodes [Object] The collection of nodes # @param arguments [GraphQL::Query::Arguments] Query arguments # @param field [GraphQL::Field] The underlying field # @param max_page_size [Int] The maximum number of results to return # @param parent [Object] The object which this collection belongs to # @param context [GraphQL::Query::Context] The context from the field being resolved def initialize(nodes, arguments, field: nil, max_page_size: nil, parent: nil, context: nil) @context = context @nodes = nodes @arguments = arguments @field = field @parent = parent @encoder = context ? @context.schema.cursor_encoder : GraphQL::Schema::Base64Encoder @max_page_size = max_page_size.nil? && context ? @context.schema.default_max_page_size : max_page_size end def encode(data) @encoder.encode(data, nonce: true) end def decode(data) @encoder.decode(data, nonce: true) end # The value passed as `first:`, if there was one. Negative numbers become `0`. # @return [Integer, nil] def first @first ||= begin capped = limit_pagination_argument(arguments[:first], max_page_size) if capped.nil? && last.nil? capped = max_page_size end capped end end # The value passed as `after:`, if there was one # @return [String, nil] def after arguments[:after] end # The value passed as `last:`, if there was one. Negative numbers become `0`. # @return [Integer, nil] def last @last ||= limit_pagination_argument(arguments[:last], max_page_size) end # The value passed as `before:`, if there was one # @return [String, nil] def before arguments[:before] end # These are the nodes to render for this connection, # probably wrapped by {GraphQL::Relay::Edge} def edge_nodes @edge_nodes ||= paged_nodes end # Support the `pageInfo` field def page_info self end # Used by `pageInfo` def has_next_page !!(first && sliced_nodes.count > first) end # Used by `pageInfo` def has_previous_page !!(last && sliced_nodes.count > last) end # Used by `pageInfo` def start_cursor if start_node = (respond_to?(:paged_nodes_array, true) ? paged_nodes_array : paged_nodes).first return cursor_from_node(start_node) else return nil end end # Used by `pageInfo` def end_cursor if end_node = (respond_to?(:paged_nodes_array, true) ? paged_nodes_array : paged_nodes).last return cursor_from_node(end_node) else return nil end end # An opaque operation which returns a connection-specific cursor. def cursor_from_node(object) raise GraphQL::RequiredImplementationMissingError, "must return a cursor for this object/connection pair" end def inspect "#" end private # @param argument [nil, Integer] `first` or `last`, as provided by the client # @param max_page_size [nil, Integer] # @return [nil, Integer] `nil` if the input was `nil`, otherwise a value between `0` and `max_page_size` def limit_pagination_argument(argument, max_page_size) if argument if argument < 0 argument = 0 elsif max_page_size && argument > max_page_size argument = max_page_size end end argument end def paged_nodes raise GraphQL::RequiredImplementationMissingError, "must return nodes for this connection after paging" end def sliced_nodes raise GraphQL::RequiredImplementationMissingError, "must return all nodes for this connection after chopping off first and last" end end end end graphql-ruby-1.11.10/lib/graphql/relay/connection_instrumentation.rb000066400000000000000000000044111414121453000256120ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # Provided a GraphQL field which returns a collection of nodes, # wrap that field to expose those nodes as a connection. # # The original resolve proc is used to fetch nodes, # then a connection implementation is fetched with {BaseConnection.connection_for_nodes}. module ConnectionInstrumentation def self.default_arguments @default_arguments ||= begin argument_definitions = [ ["first", GraphQL::INT_TYPE, "Returns the first _n_ elements from the list."], ["after", GraphQL::STRING_TYPE, "Returns the elements in the list that come after the specified cursor."], ["last", GraphQL::INT_TYPE, "Returns the last _n_ elements from the list."], ["before", GraphQL::STRING_TYPE, "Returns the elements in the list that come before the specified cursor."], ] argument_definitions.reduce({}) do |memo, arg_defn| argument = GraphQL::Argument.new name, type, description = arg_defn argument.name = name argument.type = type argument.description = description memo[argument.name.to_s] = argument memo end end end # Build a connection field from a {GraphQL::Field} by: # - Merging in the default arguments # - Transforming its resolve function to return a connection object def self.instrument(type, field) # Don't apply the wrapper to class-based fields, since they # use Schema::Field::ConnectionFilter if field.connection? && !field.metadata[:type_class] connection_arguments = default_arguments.merge(field.arguments) original_resolve = field.resolve_proc original_lazy_resolve = field.lazy_resolve_proc connection_resolve = GraphQL::Relay::ConnectionResolve.new(field, original_resolve) connection_lazy_resolve = GraphQL::Relay::ConnectionResolve.new(field, original_lazy_resolve) field.redefine( resolve: connection_resolve, lazy_resolve: connection_lazy_resolve, arguments: connection_arguments, ) else field end end end end end graphql-ruby-1.11.10/lib/graphql/relay/connection_resolve.rb000066400000000000000000000024461414121453000240340ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay class ConnectionResolve def initialize(field, underlying_resolve) @field = field @underlying_resolve = underlying_resolve @max_page_size = field.connection_max_page_size end def call(obj, args, ctx) # in a lazy resolve hook, obj is the promise, # get the object that the promise was # originally derived from parent = ctx.object nodes = @underlying_resolve.call(obj, args, ctx) if nodes.nil? || ctx.schema.lazy?(nodes) || nodes.is_a?(GraphQL::Execution::Execute::Skip) || ctx.wrapped_connection nodes else ctx.wrapped_connection = true build_connection(nodes, args, parent, ctx) end end private def build_connection(nodes, args, parent, ctx) if nodes.is_a? GraphQL::ExecutionError ctx.add_error(nodes) nil else if parent.is_a?(GraphQL::Schema::Object) parent = parent.object end connection_class = GraphQL::Relay::BaseConnection.connection_for_nodes(nodes) connection_class.new(nodes, args, field: @field, max_page_size: @max_page_size, parent: parent, context: ctx) end end end end end graphql-ruby-1.11.10/lib/graphql/relay/connection_type.rb000066400000000000000000000031341414121453000233310ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # @api deprecated module ConnectionType class << self # @return [Boolean] If true, connection types get a `nodes` shortcut field attr_accessor :default_nodes_field # @return [Boolean] If true, connections check for reverse-direction `has*Page` values attr_accessor :bidirectional_pagination end self.default_nodes_field = false self.bidirectional_pagination = false # @api deprecated def self.create_type(wrapped_type, edge_type: nil, edge_class: GraphQL::Relay::Edge, nodes_field: ConnectionType.default_nodes_field, &block) custom_edge_class = edge_class # Any call that would trigger `wrapped_type.ensure_defined` # must be inside this lazy block, otherwise we get weird # cyclical dependency errors :S ObjectType.define do type_name = wrapped_type.is_a?(GraphQL::BaseType) ? wrapped_type.name : wrapped_type.graphql_name edge_type ||= wrapped_type.edge_type name("#{type_name}Connection") description("The connection type for #{type_name}.") field :edges, types[edge_type], "A list of edges.", edge_class: custom_edge_class, property: :edge_nodes if nodes_field field :nodes, types[wrapped_type], "A list of nodes.", property: :edge_nodes end field :pageInfo, !PageInfo, "Information to aid in pagination.", property: :page_info relay_node_type(wrapped_type) block && instance_eval(&block) end end end end end graphql-ruby-1.11.10/lib/graphql/relay/edge.rb000066400000000000000000000011321414121453000210310ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # Mostly an internal concern. # # Wraps an object as a `node`, and exposes a connection-specific `cursor`. class Edge attr_reader :node, :connection def initialize(node, connection) @node = node @connection = connection end def cursor @cursor ||= connection.cursor_from_node(node) end def parent @parent ||= connection.parent end def inspect "# #{node.inspect})>" end end end end graphql-ruby-1.11.10/lib/graphql/relay/edge_type.rb000066400000000000000000000012251414121453000220750ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay module EdgeType # @api deprecated def self.create_type(wrapped_type, name: nil, &block) GraphQL::ObjectType.define do type_name = wrapped_type.is_a?(GraphQL::BaseType) ? wrapped_type.name : wrapped_type.graphql_name name("#{type_name}Edge") description "An edge in a connection." field :node, wrapped_type, "The item at the end of the edge." field :cursor, !types.String, "A cursor for use in pagination." relay_node_type(wrapped_type) block && instance_eval(&block) end end end end end graphql-ruby-1.11.10/lib/graphql/relay/edges_instrumentation.rb000066400000000000000000000022031414121453000245370ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay module EdgesInstrumentation def self.instrument(type, field) if field.edges? edges_resolve = EdgesResolve.new(edge_class: field.edge_class, resolve: field.resolve_proc) edges_lazy_resolve = EdgesResolve.new(edge_class: field.edge_class, resolve: field.lazy_resolve_proc) field.redefine( resolve: edges_resolve, lazy_resolve: edges_lazy_resolve, ) else field end end class EdgesResolve def initialize(edge_class:, resolve:) @edge_class = edge_class @resolve_proc = resolve end # A user's custom Connection may return a lazy object, # if so, handle it later. def call(obj, args, ctx) parent = ctx.object nodes = @resolve_proc.call(obj, args, ctx) if ctx.schema.lazy?(nodes) nodes else nodes.map { |item| item.is_a?(GraphQL::Pagination::Connection::Edge) ? item : @edge_class.new(item, parent) } end end end end end end graphql-ruby-1.11.10/lib/graphql/relay/global_id_resolve.rb000066400000000000000000000006631414121453000236100ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay class GlobalIdResolve def initialize(type:) @type = type end def call(obj, args, ctx) if obj.is_a?(GraphQL::Schema::Object) obj = obj.object end type = @type.respond_to?(:graphql_definition) ? @type.graphql_definition : @type ctx.query.schema.id_from_object(obj, type, ctx) end end end end graphql-ruby-1.11.10/lib/graphql/relay/mongo_relation_connection.rb000066400000000000000000000030311414121453000253600ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # A connection implementation to expose MongoDB collection objects. # It works for: # - `Mongoid::Criteria` class MongoRelationConnection < RelationConnection private def relation_offset(relation) relation.options.skip end def relation_limit(relation) relation.options.limit end def relation_count(relation) # Must perform query (hence #to_a) to count results https://jira.mongodb.org/browse/MONGOID-2325 relation.to_a.count end def limit_nodes(sliced_nodes, limit) if limit == 0 if sliced_nodes.respond_to?(:none) # added in Mongoid 4.0 sliced_nodes.without_options.none else sliced_nodes.where(id: nil) # trying to simulate #none for 3.1.7 end else sliced_nodes.limit(limit) end end end if defined?(Mongoid::Criteria) BaseConnection.register_connection_implementation(Mongoid::Criteria, MongoRelationConnection) end # Mongoid 5 and 6 if defined?(Mongoid::Relations::Targets::Enumerable) BaseConnection.register_connection_implementation(Mongoid::Relations::Targets::Enumerable, MongoRelationConnection) end # Mongoid 7 if defined?(Mongoid::Association::Referenced::HasMany::Targets::Enumerable) BaseConnection.register_connection_implementation(Mongoid::Association::Referenced::HasMany::Targets::Enumerable, MongoRelationConnection) end end end graphql-ruby-1.11.10/lib/graphql/relay/mutation.rb000066400000000000000000000064141414121453000217750ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/relay/mutation/instrumentation" require "graphql/relay/mutation/resolve" require "graphql/relay/mutation/result" module GraphQL module Relay # @api deprecated class Mutation include GraphQL::Define::InstanceDefinable accepts_definitions( :name, :description, :resolve, :return_type, :return_interfaces, input_field: GraphQL::Define::AssignArgument, return_field: GraphQL::Define::AssignObjectField, function: GraphQL::Define::AssignMutationFunction, ) attr_accessor :name, :description, :fields, :arguments attr_writer :return_type, :return_interfaces ensure_defined( :input_fields, :return_fields, :name, :description, :fields, :arguments, :return_type, :return_interfaces, :resolve=, :field, :result_class, :input_type ) # For backwards compat, but do we need this separate API? alias :return_fields :fields alias :input_fields :arguments def initialize @fields = {} @arguments = {} @has_generated_return_type = false end def has_generated_return_type? # Trigger the generation of the return type, if it is dynamically generated: return_type @has_generated_return_type end def resolve=(new_resolve_proc) @resolve_proc = new_resolve_proc end def field @field ||= begin relay_mutation = self field_resolve_proc = @resolve_proc GraphQL::Field.define do type(relay_mutation.return_type) description(relay_mutation.description) argument :input, !relay_mutation.input_type resolve(field_resolve_proc) mutation(relay_mutation) end end end def return_interfaces @return_interfaces ||= [] end def return_type @return_type ||= begin @has_generated_return_type = true relay_mutation = self GraphQL::ObjectType.define do name("#{relay_mutation.name}Payload") description("Autogenerated return type of #{relay_mutation.name}") field :clientMutationId, types.String, "A unique identifier for the client performing the mutation.", property: :client_mutation_id interfaces relay_mutation.return_interfaces relay_mutation.return_fields.each do |name, field_obj| field name, field: field_obj end mutation(relay_mutation) end end end def input_type @input_type ||= begin relay_mutation = self input_object_type = GraphQL::InputObjectType.define do name("#{relay_mutation.name}Input") description("Autogenerated input type of #{relay_mutation.name}") input_field :clientMutationId, types.String, "A unique identifier for the client performing the mutation." mutation(relay_mutation) end input_fields.each do |name, arg| input_object_type.arguments[name] = arg end input_object_type end end def result_class @result_class ||= Result.define_subclass(self) end end end end graphql-ruby-1.11.10/lib/graphql/relay/mutation/000077500000000000000000000000001414121453000214435ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/relay/mutation/instrumentation.rb000066400000000000000000000014751414121453000252420ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay class Mutation # @api private module Instrumentation # Modify mutation `return_field` resolves by wrapping the returned object # in a {Mutation::Result}. # # By using an instrumention, we can apply our wrapper _last_, # giving users access to the original resolve function in earlier instrumentation. def self.instrument(type, field) if field.mutation.is_a?(GraphQL::Relay::Mutation) || (field.mutation.is_a?(Class) && field.mutation < GraphQL::Schema::RelayClassicMutation) new_resolve = Mutation::Resolve.new(field.mutation, field.resolve_proc) field.redefine(resolve: new_resolve) else field end end end end end end graphql-ruby-1.11.10/lib/graphql/relay/mutation/resolve.rb000066400000000000000000000034031414121453000234470ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay class Mutation # Wrap a user-provided resolve function, # wrapping the returned value in a {Mutation::Result}. # Also, pass the `clientMutationId` to that result object. # @api private class Resolve def initialize(mutation, resolve) @mutation = mutation @resolve = resolve @wrap_result = mutation.is_a?(GraphQL::Relay::Mutation) && mutation.has_generated_return_type? @class_based = mutation.is_a?(Class) end def call(obj, args, ctx) mutation_result = begin @resolve.call(obj, args[:input], ctx) rescue GraphQL::ExecutionError => err err end ctx.schema.after_lazy(mutation_result) do |res| build_result(res, args, ctx) end end private def build_result(mutation_result, args, ctx) if mutation_result.is_a?(GraphQL::ExecutionError) ctx.add_error(mutation_result) mutation_result = nil end if mutation_result.nil? nil elsif @wrap_result if mutation_result && !mutation_result.is_a?(Hash) raise StandardError, "Expected `#{mutation_result}` to be a Hash."\ " Return a hash when using `return_field` or specify a custom `return_type`." end @mutation.result_class.new(client_mutation_id: args[:input][:clientMutationId], result: mutation_result) elsif @class_based mutation_result[:client_mutation_id] = args[:input][:client_mutation_id] mutation_result else mutation_result end end end end end end graphql-ruby-1.11.10/lib/graphql/relay/mutation/result.rb000066400000000000000000000022601414121453000233060ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay class Mutation # Use this when the mutation's return type was generated from `return_field`s. # It delegates field lookups to the hash returned from `resolve`. # @api private class Result attr_reader :client_mutation_id def initialize(client_mutation_id:, result:) @client_mutation_id = client_mutation_id result && result.each do |key, value| self.public_send("#{key}=", value) end end class << self attr_accessor :mutation end # Build a subclass whose instances have a method # for each of `mutation_defn`'s `return_field`s # @param mutation_defn [GraphQL::Relay::Mutation] # @return [Class] def self.define_subclass(mutation_defn) subclass = Class.new(self) do mutation_result_methods = mutation_defn.return_type.all_fields.map do |f| f.property || f.name end attr_accessor(*mutation_result_methods) self.mutation = mutation_defn end subclass end end end end end graphql-ruby-1.11.10/lib/graphql/relay/node.rb000066400000000000000000000020161414121453000210540ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # Helpers for working with Relay-specific Node objects. module Node # @return [GraphQL::Field] a field for finding objects by their global ID. def self.field(**kwargs, &block) # We have to define it fresh each time because # its name will be modified and its description # _may_ be modified. field = GraphQL::Types::Relay::NodeField.graphql_definition if kwargs.any? || block field = field.redefine(**kwargs, &block) end field end def self.plural_field(**kwargs, &block) field = GraphQL::Types::Relay::NodesField.graphql_definition if kwargs.any? || block field = field.redefine(**kwargs, &block) end field end # @return [GraphQL::InterfaceType] The interface which all Relay types must implement def self.interface @interface ||= GraphQL::Types::Relay::Node.graphql_definition end end end end graphql-ruby-1.11.10/lib/graphql/relay/page_info.rb000066400000000000000000000002711414121453000220570ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # Wrap a Connection and expose its page info PageInfo = GraphQL::Types::Relay::PageInfo.graphql_definition end end graphql-ruby-1.11.10/lib/graphql/relay/range_add.rb000066400000000000000000000043541414121453000220420ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # This provides some isolation from `GraphQL::Relay` internals. # # Given a list of items and a new item, it will provide a connection and an edge. # # The connection doesn't receive outside arguments, so the list of items # should be ordered and paginated before providing it here. # # @example Adding a comment to list of comments # post = Post.find(args[:postId]) # comments = post.comments # new_comment = comments.build(body: args[:body]) # new_comment.save! # # range_add = GraphQL::Relay::RangeAdd.new( # parent: post, # collection: comments, # item: new_comment, # context: ctx, # ) # # response = { # post: post, # commentsConnection: range_add.connection, # newCommentEdge: range_add.edge, # } class RangeAdd attr_reader :edge, :connection, :parent # @param collection [Object] The list of items to wrap in a connection # @param item [Object] The newly-added item (will be wrapped in `edge_class`) # @param parent [Object] The owner of `collection`, will be passed to the connection if provided # @param context [GraphQL::Query::Context] The surrounding `ctx`, will be passed to the connection if provided (this is required for cursor encoders) # @param edge_class [Class] The class to wrap `item` with (defaults to the connection's edge class) def initialize(collection:, item:, parent: nil, context: nil, edge_class: nil) if context && context.schema.new_connections? conn_class = context.schema.connections.wrapper_for(collection) # The rest will be added by ConnectionExtension @connection = conn_class.new(collection, parent: parent, context: context, edge_class: edge_class) @edge = @connection.edge_class.new(item, @connection) else connection_class = BaseConnection.connection_for_nodes(collection) @connection = connection_class.new(collection, {}, parent: parent, context: context) edge_class ||= Relay::Edge @edge = edge_class.new(item, @connection) end @parent = parent end end end end graphql-ruby-1.11.10/lib/graphql/relay/relation_connection.rb000066400000000000000000000134201414121453000241640ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # A connection implementation to expose SQL collection objects. # It works for: # - `ActiveRecord::Relation` # - `Sequel::Dataset` class RelationConnection < BaseConnection def cursor_from_node(item) item_index = paged_nodes.index(item) if item_index.nil? raise("Can't generate cursor, item not found in connection: #{item}") else offset = item_index + 1 + ((paged_nodes_offset || 0) - (relation_offset(sliced_nodes) || 0)) if after offset += offset_from_cursor(after) elsif before offset += offset_from_cursor(before) - 1 - sliced_nodes_count end encode(offset.to_s) end end def has_next_page if first if defined?(ActiveRecord::Relation) && nodes.is_a?(ActiveRecord::Relation) initial_offset = after ? offset_from_cursor(after) : 0 return paged_nodes.length >= first && nodes.offset(first + initial_offset).exists? end return paged_nodes.length >= first && sliced_nodes_count > first end if GraphQL::Relay::ConnectionType.bidirectional_pagination && last return sliced_nodes_count >= last end false end def has_previous_page if last paged_nodes.length >= last && sliced_nodes_count > last elsif GraphQL::Relay::ConnectionType.bidirectional_pagination && after # We've already paginated through the collection a bit, # there are nodes behind us offset_from_cursor(after) > 0 else false end end def first @first ||= begin capped = limit_pagination_argument(arguments[:first], max_page_size) if capped.nil? && last.nil? capped = max_page_size end capped end end def last @last ||= limit_pagination_argument(arguments[:last], max_page_size) end private # apply first / last limit results # @return [Array] def paged_nodes return @paged_nodes if defined? @paged_nodes items = sliced_nodes if first if relation_limit(items).nil? || relation_limit(items) > first items = items.limit(first) end end if last if relation_limit(items) if last <= relation_limit(items) offset = (relation_offset(items) || 0) + (relation_limit(items) - last) items = items.offset(offset).limit(last) end else slice_count = relation_count(items) offset = (relation_offset(items) || 0) + slice_count - [last, slice_count].min items = items.offset(offset).limit(last) end end if max_page_size && !first && !last if relation_limit(items).nil? || relation_limit(items) > max_page_size items = items.limit(max_page_size) end end # Store this here so we can convert the relation to an Array # (this avoids an extra DB call on Sequel) @paged_nodes_offset = relation_offset(items) @paged_nodes = items.to_a end def paged_nodes_offset paged_nodes && @paged_nodes_offset end def relation_offset(relation) if relation.respond_to?(:offset_value) relation.offset_value else relation.opts[:offset] end end def relation_limit(relation) if relation.respond_to?(:limit_value) relation.limit_value else relation.opts[:limit] end end # If a relation contains a `.group` clause, a `.count` will return a Hash. def relation_count(relation) count_or_hash = if(defined?(ActiveRecord::Relation) && relation.is_a?(ActiveRecord::Relation)) relation.respond_to?(:unscope)? relation.unscope(:order).count(:all) : relation.count(:all) else # eg, Sequel::Dataset, don't mess up others relation.count end count_or_hash.is_a?(Integer) ? count_or_hash : count_or_hash.length end # Apply cursors to edges def sliced_nodes return @sliced_nodes if defined? @sliced_nodes @sliced_nodes = nodes if after offset = (relation_offset(@sliced_nodes) || 0) + offset_from_cursor(after) @sliced_nodes = @sliced_nodes.offset(offset) end if before && after if offset_from_cursor(after) < offset_from_cursor(before) @sliced_nodes = limit_nodes(@sliced_nodes, offset_from_cursor(before) - offset_from_cursor(after) - 1) else @sliced_nodes = limit_nodes(@sliced_nodes, 0) end elsif before @sliced_nodes = limit_nodes(@sliced_nodes, offset_from_cursor(before) - 1) end @sliced_nodes end def limit_nodes(sliced_nodes, limit) if limit > 0 || defined?(ActiveRecord::Relation) && sliced_nodes.is_a?(ActiveRecord::Relation) sliced_nodes.limit(limit) else sliced_nodes.where(false) end end def sliced_nodes_count return @sliced_nodes_count if defined? @sliced_nodes_count # If a relation contains a `.group` clause, a `.count` will return a Hash. @sliced_nodes_count = relation_count(sliced_nodes) end def offset_from_cursor(cursor) decode(cursor).to_i end end if defined?(ActiveRecord::Relation) BaseConnection.register_connection_implementation(ActiveRecord::Relation, RelationConnection) end if defined?(Sequel::Dataset) BaseConnection.register_connection_implementation(Sequel::Dataset, RelationConnection) end end end graphql-ruby-1.11.10/lib/graphql/relay/type_extensions.rb000066400000000000000000000017041414121453000233720ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Relay # Mixin for Relay-related methods in type objects # (used by BaseType and Schema::Member). module TypeExtensions # @return [GraphQL::ObjectType] The default connection type for this object type def connection_type @connection_type ||= define_connection end # Define a custom connection type for this object type # @return [GraphQL::ObjectType] def define_connection(**kwargs, &block) GraphQL::Relay::ConnectionType.create_type(self, **kwargs, &block) end # @return [GraphQL::ObjectType] The default edge type for this object type def edge_type @edge_type ||= define_edge end # Define a custom edge type for this object type # @return [GraphQL::ObjectType] def define_edge(**kwargs, &block) GraphQL::Relay::EdgeType.create_type(self, **kwargs, &block) end end end end graphql-ruby-1.11.10/lib/graphql/runtime_type_error.rb000066400000000000000000000001411414121453000227450ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class RuntimeTypeError < GraphQL::Error end end graphql-ruby-1.11.10/lib/graphql/scalar_type.rb000066400000000000000000000043731414121453000213310ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # @api deprecated class ScalarType < GraphQL::BaseType accepts_definitions :coerce, :coerce_input, :coerce_result ensure_defined :coerce_non_null_input, :coerce_result module NoOpCoerce def self.call(val, ctx) val end end def initialize super self.coerce = NoOpCoerce end def coerce=(proc) self.coerce_input = proc self.coerce_result = proc end def coerce_input=(coerce_input_fn) if !coerce_input_fn.nil? @coerce_input_proc = ensure_two_arg(coerce_input_fn, :coerce_input) end end def coerce_result(value, ctx = nil) if ctx.nil? warn_deprecated_coerce("coerce_isolated_result") ctx = GraphQL::Query::NullContext end @coerce_result_proc.call(value, ctx) end def coerce_result=(coerce_result_fn) if !coerce_result_fn.nil? @coerce_result_proc = ensure_two_arg(coerce_result_fn, :coerce_result) end end def kind GraphQL::TypeKinds::SCALAR end private def ensure_two_arg(callable, method_name) GraphQL::BackwardsCompatibility.wrap_arity(callable, from: 1, to: 2, name: "#{name}.#{method_name}(val, ctx)") end def coerce_non_null_input(value, ctx) @coerce_input_proc.call(raw_coercion_input(value), ctx) end def raw_coercion_input(value) if value.is_a?(GraphQL::Language::Nodes::InputObject) value.to_h elsif value.is_a?(Array) value.map { |element| raw_coercion_input(element) } else value end end def validate_non_null_input(value, ctx) result = Query::InputValidationResult.new coerced_result = begin coerce_non_null_input(value, ctx) rescue GraphQL::CoercionError => err err end if value.is_a?(GraphQL::Language::Nodes::Enum) || coerced_result.nil? result.add_problem("Could not coerce value #{GraphQL::Language.serialize(value)} to #{name}") elsif coerced_result.is_a?(GraphQL::CoercionError) result.add_problem( coerced_result.message, message: coerced_result.message, extensions: coerced_result.extensions ) end result end end end graphql-ruby-1.11.10/lib/graphql/schema.rb000066400000000000000000002207431414121453000202640ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/schema/base_64_encoder" require "graphql/schema/catchall_middleware" require "graphql/schema/default_parse_error" require "graphql/schema/default_type_error" require "graphql/schema/find_inherited_value" require "graphql/schema/finder" require "graphql/schema/invalid_type_error" require "graphql/schema/introspection_system" require "graphql/schema/late_bound_type" require "graphql/schema/middleware_chain" require "graphql/schema/null_mask" require "graphql/schema/possible_types" require "graphql/schema/rescue_middleware" require "graphql/schema/timeout" require "graphql/schema/timeout_middleware" require "graphql/schema/traversal" require "graphql/schema/type_expression" require "graphql/schema/unique_within_type" require "graphql/schema/validation" require "graphql/schema/warden" require "graphql/schema/build_from_definition" require "graphql/schema/member" require "graphql/schema/wrapper" require "graphql/schema/list" require "graphql/schema/non_null" require "graphql/schema/argument" require "graphql/schema/enum_value" require "graphql/schema/enum" require "graphql/schema/field_extension" require "graphql/schema/field" require "graphql/schema/input_object" require "graphql/schema/interface" require "graphql/schema/scalar" require "graphql/schema/object" require "graphql/schema/union" require "graphql/schema/directive" require "graphql/schema/directive/deprecated" require "graphql/schema/directive/include" require "graphql/schema/directive/skip" require "graphql/schema/directive/feature" require "graphql/schema/directive/transform" require "graphql/schema/type_membership" require "graphql/schema/resolver" require "graphql/schema/mutation" require "graphql/schema/relay_classic_mutation" require "graphql/schema/subscription" module GraphQL # A GraphQL schema which may be queried with {GraphQL::Query}. # # The {Schema} contains: # # - types for exposing your application # - query analyzers for assessing incoming queries (including max depth & max complexity restrictions) # - execution strategies for running incoming queries # # Schemas start with root types, {Schema#query}, {Schema#mutation} and {Schema#subscription}. # The schema will traverse the tree of fields & types, using those as starting points. # Any undiscoverable types may be provided with the `types` configuration. # # Schemas can restrict large incoming queries with `max_depth` and `max_complexity` configurations. # (These configurations can be overridden by specific calls to {Schema#execute}) # # Schemas can specify how queries should be executed against them. # `query_execution_strategy`, `mutation_execution_strategy` and `subscription_execution_strategy` # each apply to corresponding root types. # # # @example defining a schema # class MySchema < GraphQL::Schema # query QueryType # # If types are only connected by way of interfaces, they must be added here # orphan_types ImageType, AudioType # end # class Schema extend Forwardable extend GraphQL::Schema::Member::AcceptsDefinition extend GraphQL::Schema::Member::HasAstNode include GraphQL::Define::InstanceDefinable extend GraphQL::Schema::FindInheritedValue class DuplicateTypeNamesError < GraphQL::Error def initialize(type_name:, first_definition:, second_definition:, path:) super("Multiple definitions for `#{type_name}`. Previously found #{first_definition.inspect} (#{first_definition.class}), then found #{second_definition.inspect} (#{second_definition.class}) at #{path.join(".")}") end end class UnresolvedLateBoundTypeError < GraphQL::Error attr_reader :type def initialize(type:) @type = type super("Late bound type was never found: #{type.inspect}") end end module LazyHandlingMethods # Call the given block at the right time, either: # - Right away, if `value` is not registered with `lazy_resolve` # - After resolving `value`, if it's registered with `lazy_resolve` (eg, `Promise`) # @api private def after_lazy(value, &block) if lazy?(value) GraphQL::Execution::Lazy.new do result = sync_lazy(value) # The returned result might also be lazy, so check it, too after_lazy(result, &block) end else yield(value) if block_given? end end # Override this method to handle lazy objects in a custom way. # @param value [Object] an instance of a class registered with {.lazy_resolve} # @return [Object] A GraphQL-ready (non-lazy) object # @api private def sync_lazy(value) lazy_method = lazy_method_name(value) if lazy_method synced_value = value.public_send(lazy_method) sync_lazy(synced_value) else value end end # @return [Symbol, nil] The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered with {#lazy_resolve}. def lazy_method_name(obj) lazy_methods.get(obj) end # @return [Boolean] True if this object should be lazily resolved def lazy?(obj) !!lazy_method_name(obj) end # Return a lazy if any of `maybe_lazies` are lazy, # otherwise, call the block eagerly and return the result. # @param maybe_lazies [Array] # @api private def after_any_lazies(maybe_lazies) if maybe_lazies.any? { |l| lazy?(l) } GraphQL::Execution::Lazy.all(maybe_lazies).then do |result| yield result end else yield maybe_lazies end end end include LazyHandlingMethods extend LazyHandlingMethods accepts_definitions \ :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy, :validate_timeout, :validate_max_errors, :max_depth, :max_complexity, :default_max_page_size, :orphan_types, :resolve_type, :type_error, :parse_error, :error_bubbling, :raise_definition_error, :object_from_id, :id_from_object, :default_mask, :cursor_encoder, # If these are given as classes, normalize them. Accept `nil` when building from string. query: ->(schema, t) { schema.query = t.respond_to?(:graphql_definition) ? t.graphql_definition : t }, mutation: ->(schema, t) { schema.mutation = t.respond_to?(:graphql_definition) ? t.graphql_definition : t }, subscription: ->(schema, t) { schema.subscription = t.respond_to?(:graphql_definition) ? t.graphql_definition : t }, disable_introspection_entry_points: ->(schema) { schema.disable_introspection_entry_points = true }, disable_schema_introspection_entry_point: ->(schema) { schema.disable_schema_introspection_entry_point = true }, disable_type_introspection_entry_point: ->(schema) { schema.disable_type_introspection_entry_point = true }, directives: ->(schema, directives) { schema.directives = directives.reduce({}) { |m, d| m[d.graphql_name] = d; m } }, directive: ->(schema, directive) { schema.directives[directive.graphql_name] = directive }, instrument: ->(schema, type, instrumenter, after_built_ins: false) { if type == :field && after_built_ins type = :field_after_built_ins end schema.instrumenters[type] << instrumenter }, query_analyzer: ->(schema, analyzer) { if analyzer == GraphQL::Authorization::Analyzer warn("The Authorization query analyzer is deprecated. Authorizing at query runtime is generally a better idea.") end schema.query_analyzers << analyzer }, multiplex_analyzer: ->(schema, analyzer) { schema.multiplex_analyzers << analyzer }, middleware: ->(schema, middleware) { schema.middleware << middleware }, lazy_resolve: ->(schema, lazy_class, lazy_value_method) { schema.lazy_methods.set(lazy_class, lazy_value_method) }, rescue_from: ->(schema, err_class, &block) { schema.rescue_from(err_class, &block) }, tracer: ->(schema, tracer) { schema.tracers.push(tracer) } ensure_defined :introspection_system attr_accessor \ :query, :mutation, :subscription, :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy, :validate_timeout, :validate_max_errors, :max_depth, :max_complexity, :default_max_page_size, :orphan_types, :directives, :query_analyzers, :multiplex_analyzers, :instrumenters, :lazy_methods, :cursor_encoder, :ast_node, :raise_definition_error, :introspection_namespace, :analysis_engine # [Boolean] True if this object bubbles validation errors up from a field into its parent InputObject, if there is one. attr_accessor :error_bubbling # Single, long-lived instance of the provided subscriptions class, if there is one. # @return [GraphQL::Subscriptions] attr_accessor :subscriptions # @return [MiddlewareChain] MiddlewareChain which is applied to fields during execution attr_accessor :middleware # @return [<#call(member, ctx)>] A callable for filtering members of the schema # @see {Query.new} for query-specific filters with `except:` attr_accessor :default_mask # @see {GraphQL::Query::Context} The parent class of these classes # @return [Class] Instantiated for each query attr_accessor :context_class # [Boolean] True if this object disables the introspection entry point fields attr_accessor :disable_introspection_entry_points def disable_introspection_entry_points? !!@disable_introspection_entry_points end # [Boolean] True if this object disables the __schema introspection entry point field attr_accessor :disable_schema_introspection_entry_point def disable_schema_introspection_entry_point? !!@disable_schema_introspection_entry_point end # [Boolean] True if this object disables the __type introspection entry point field attr_accessor :disable_type_introspection_entry_point def disable_type_introspection_entry_point? !!@disable_type_introspection_entry_point end class << self attr_writer :default_execution_strategy end def default_filter GraphQL::Filter.new(except: default_mask) end # @return [Array<#trace(key, data)>] Tracers applied to every query # @see {Query#tracers} for query-specific tracers attr_reader :tracers DYNAMIC_FIELDS = ["__type", "__typename", "__schema"].freeze attr_reader :static_validator, :object_from_id_proc, :id_from_object_proc, :resolve_type_proc def initialize @tracers = [] @definition_error = nil @orphan_types = [] @directives = {} self.class.default_directives.each do |name, dir| @directives[name] = dir.graphql_definition end @static_validator = GraphQL::StaticValidation::Validator.new(schema: self) @middleware = MiddlewareChain.new(final_step: GraphQL::Execution::Execute::FieldResolveStep) @query_analyzers = [] @multiplex_analyzers = [] @resolve_type_proc = nil @object_from_id_proc = nil @id_from_object_proc = nil @type_error_proc = DefaultTypeError @parse_error_proc = DefaultParseError @instrumenters = Hash.new { |h, k| h[k] = [] } @lazy_methods = GraphQL::Execution::Lazy::LazyMethodMap.new @lazy_methods.set(GraphQL::Execution::Lazy, :value) @cursor_encoder = Base64Encoder # Default to the built-in execution strategy: @analysis_engine = GraphQL::Analysis @query_execution_strategy = self.class.default_execution_strategy @mutation_execution_strategy = self.class.default_execution_strategy @subscription_execution_strategy = self.class.default_execution_strategy @default_mask = GraphQL::Schema::NullMask @rebuilding_artifacts = false @context_class = GraphQL::Query::Context @introspection_namespace = nil @introspection_system = nil @interpreter = false @error_bubbling = false @disable_introspection_entry_points = false @disable_schema_introspection_entry_point = false @disable_type_introspection_entry_point = false end # @return [Boolean] True if using the new {GraphQL::Execution::Interpreter} def interpreter? @interpreter end # @api private attr_writer :interpreter def inspect "#<#{self.class.name} ...>" end def initialize_copy(other) super @orphan_types = other.orphan_types.dup @directives = other.directives.dup @static_validator = GraphQL::StaticValidation::Validator.new(schema: self) @middleware = other.middleware.dup @query_analyzers = other.query_analyzers.dup @multiplex_analyzers = other.multiplex_analyzers.dup @tracers = other.tracers.dup @possible_types = GraphQL::Schema::PossibleTypes.new(self) @lazy_methods = other.lazy_methods.dup @instrumenters = Hash.new { |h, k| h[k] = [] } other.instrumenters.each do |key, insts| @instrumenters[key].concat(insts) end if other.rescues? @rescue_middleware = other.rescue_middleware end # This will be rebuilt when it's requested # or during a later `define` call @types = nil @introspection_system = nil end def rescue_from(*args, &block) rescue_middleware.rescue_from(*args, &block) end def remove_handler(*args, &block) rescue_middleware.remove_handler(*args, &block) end def using_ast_analysis? @analysis_engine == GraphQL::Analysis::AST end # For forwards-compatibility with Schema classes alias :graphql_definition :itself # Validate a query string according to this schema. # @param string_or_document [String, GraphQL::Language::Nodes::Document] # @return [Array] def validate(string_or_document, rules: nil, context: nil) doc = if string_or_document.is_a?(String) GraphQL.parse(string_or_document) else string_or_document end query = GraphQL::Query.new(self, document: doc, context: context) validator_opts = { schema: self } rules && (validator_opts[:rules] = rules) validator = GraphQL::StaticValidation::Validator.new(**validator_opts) res = validator.validate(query, timeout: validate_timeout, max_errors: validate_max_errors) res[:errors] end def define(**kwargs, &block) super ensure_defined # Assert that all necessary configs are present: validation_error = Validation.validate(self) validation_error && raise(GraphQL::RequiredImplementationMissingError, validation_error) rebuild_artifacts @definition_error = nil nil rescue StandardError => err if @raise_definition_error || err.is_a?(CyclicalDefinitionError) || err.is_a?(GraphQL::RequiredImplementationMissingError) raise else # Raise this error _later_ to avoid messing with Rails constant loading @definition_error = err end nil end # Attach `instrumenter` to this schema for instrumenting events of `instrumentation_type`. # @param instrumentation_type [Symbol] # @param instrumenter # @return [void] def instrument(instrumentation_type, instrumenter) @instrumenters[instrumentation_type] << instrumenter if instrumentation_type == :field rebuild_artifacts end end # @return [Array] The root types of this schema def root_types @root_types ||= begin rebuild_artifacts @root_types end end # @see [GraphQL::Schema::Warden] Restricted access to members of a schema # @return [GraphQL::Schema::TypeMap] `{ name => type }` pairs of types in this schema def types @types ||= begin rebuild_artifacts @types end end def get_type(type_name) @types[type_name] end # @api private def introspection_system @introspection_system ||= begin rebuild_artifacts @introspection_system end end # Returns a list of Arguments and Fields referencing a certain type # @param type_name [String] # @return [Hash] def references_to(type_name = nil) rebuild_artifacts unless defined?(@type_reference_map) if type_name @type_reference_map.fetch(type_name, []) else @type_reference_map end end # Returns a list of Union types in which a type is a member # @param type [GraphQL::ObjectType] # @return [Array] list of union types of which the type is a member def union_memberships(type) rebuild_artifacts unless defined?(@union_memberships) @union_memberships.fetch(type.name, []) end # Execute a query on itself. Raises an error if the schema definition is invalid. # @see {Query#initialize} for arguments. # @return [Hash] query result, ready to be serialized as JSON def execute(query_str = nil, **kwargs) if query_str kwargs[:query] = query_str end # Some of the query context _should_ be passed to the multiplex, too multiplex_context = if (ctx = kwargs[:context]) { backtrace: ctx[:backtrace], tracers: ctx[:tracers], } else {} end # Since we're running one query, don't run a multiplex-level complexity analyzer all_results = multiplex([kwargs], max_complexity: nil, context: multiplex_context) all_results[0] end # Execute several queries on itself. Raises an error if the schema definition is invalid. # @example Run several queries at once # context = { ... } # queries = [ # { query: params[:query_1], variables: params[:variables_1], context: context }, # { query: params[:query_2], variables: params[:variables_2], context: context }, # ] # results = MySchema.multiplex(queries) # render json: { # result_1: results[0], # result_2: results[1], # } # # @see {Query#initialize} for query keyword arguments # @see {Execution::Multiplex#run_queries} for multiplex keyword arguments # @param queries [Array] Keyword arguments for each query # @param context [Hash] Multiplex-level context # @return [Array] One result for each query in the input def multiplex(queries, **kwargs) with_definition_error_check { GraphQL::Execution::Multiplex.run_all(self, queries, **kwargs) } end # Search for a schema member using a string path # @example Finding a Field # Schema.find("Ensemble.musicians") # # @see {GraphQL::Schema::Finder} for more examples # @param path [String] A dot-separated path to the member # @raise [Schema::Finder::MemberNotFoundError] if path could not be found # @return [GraphQL::BaseType, GraphQL::Field, GraphQL::Argument, GraphQL::Directive] A GraphQL Schema Member def find(path) rebuild_artifacts unless defined?(@finder) @find_cache[path] ||= @finder.find(path) end # Resolve field named `field_name` for type `parent_type`. # Handles dynamic fields `__typename`, `__type` and `__schema`, too # @param parent_type [String, GraphQL::BaseType] # @param field_name [String] # @return [GraphQL::Field, nil] The field named `field_name` on `parent_type` # @see [GraphQL::Schema::Warden] Restricted access to members of a schema def get_field(parent_type, field_name) with_definition_error_check do parent_type_name = case parent_type when GraphQL::BaseType, Class, Module parent_type.graphql_name when String parent_type else raise "Unexpected parent_type: #{parent_type}" end defined_field = @instrumented_field_map[parent_type_name][field_name] if defined_field defined_field elsif parent_type == query && (entry_point_field = introspection_system.entry_point(name: field_name)) entry_point_field elsif (dynamic_field = introspection_system.dynamic_field(name: field_name)) dynamic_field else nil end end end # Fields for this type, after instrumentation is applied # @return [Hash] def get_fields(type) @instrumented_field_map[type.graphql_name] end def type_from_ast(ast_node, context:) GraphQL::Schema::TypeExpression.build_type(self, ast_node) end # @see [GraphQL::Schema::Warden] Restricted access to members of a schema # @param type_defn [GraphQL::InterfaceType, GraphQL::UnionType] the type whose members you want to retrieve # @param context [GraphQL::Query::Context] The context for the current query # @return [Array] types which belong to `type_defn` in this schema def possible_types(type_defn, context = GraphQL::Query::NullContext) if context == GraphQL::Query::NullContext @possible_types ||= GraphQL::Schema::PossibleTypes.new(self) @possible_types.possible_types(type_defn, context) else # Use the incoming context to cache this instance -- # if it were cached on the schema, we'd have a memory leak # https://github.com/rmosolgo/graphql-ruby/issues/2878 ns = context.namespace(:possible_types) per_query_possible_types = ns[:possible_types] ||= GraphQL::Schema::PossibleTypes.new(self) per_query_possible_types.possible_types(type_defn, context) end end # @see [GraphQL::Schema::Warden] Resticted access to root types # @return [GraphQL::ObjectType, nil] def root_type_for_operation(operation) case operation when "query" query when "mutation" mutation when "subscription" subscription else raise ArgumentError, "unknown operation type: #{operation}" end end def execution_strategy_for_operation(operation) case operation when "query" query_execution_strategy when "mutation" mutation_execution_strategy when "subscription" subscription_execution_strategy else raise ArgumentError, "unknown operation type: #{operation}" end end # Determine the GraphQL type for a given object. # This is required for unions and interfaces (including Relay's `Node` interface) # @see [GraphQL::Schema::Warden] Restricted access to members of a schema # @param type [GraphQL::UnionType, GraphQL:InterfaceType] the abstract type which is being resolved # @param object [Any] An application object which GraphQL is currently resolving on # @param ctx [GraphQL::Query::Context] The context for the current query # @return [GraphQL::ObjectType] The type for exposing `object` in GraphQL def resolve_type(type, object, ctx = :__undefined__) check_resolved_type(type, object, ctx) do |ok_type, ok_object, ok_ctx| if @resolve_type_proc.nil? raise(GraphQL::RequiredImplementationMissingError, "Can't determine GraphQL type for: #{ok_object.inspect}, define `resolve_type (type, obj, ctx) -> { ... }` inside `Schema.define`.") end @resolve_type_proc.call(ok_type, ok_object, ok_ctx) end end # This is a compatibility hack so that instance-level and class-level # methods can get correctness checks without calling one another # @api private def check_resolved_type(type, object, ctx = :__undefined__) if ctx == :__undefined__ # Old method signature ctx = object object = type type = nil end if object.is_a?(GraphQL::Schema::Object) object = object.object end if type.respond_to?(:graphql_definition) type = type.graphql_definition end # Prefer a type-local function; fall back to the schema-level function type_proc = type && type.resolve_type_proc type_result = if type_proc type_proc.call(object, ctx) else yield(type, object, ctx) end if type_result.nil? nil else after_lazy(type_result) do |resolved_type_result| if resolved_type_result.respond_to?(:graphql_definition) resolved_type_result = resolved_type_result.graphql_definition end if !resolved_type_result.is_a?(GraphQL::BaseType) type_str = "#{resolved_type_result} (#{resolved_type_result.class.name})" raise "resolve_type(#{object}) returned #{type_str}, but it should return a GraphQL type" else resolved_type_result end end end end def resolve_type=(new_resolve_type_proc) callable = GraphQL::BackwardsCompatibility.wrap_arity(new_resolve_type_proc, from: 2, to: 3, last: true, name: "Schema#resolve_type(type, obj, ctx)") @resolve_type_proc = callable end # Fetch an application object by its unique id # @param id [String] A unique identifier, provided previously by this GraphQL schema # @param ctx [GraphQL::Query::Context] The context for the current query # @return [Any] The application object identified by `id` def object_from_id(id, ctx) if @object_from_id_proc.nil? raise(GraphQL::RequiredImplementationMissingError, "Can't fetch an object for id \"#{id}\" because the schema's `object_from_id (id, ctx) -> { ... }` function is not defined") else @object_from_id_proc.call(id, ctx) end end # @param new_proc [#call] A new callable for fetching objects by ID def object_from_id=(new_proc) @object_from_id_proc = new_proc end # When we encounter a type error during query execution, we call this hook. # # You can use this hook to write a log entry, # add a {GraphQL::ExecutionError} to the response (with `ctx.add_error`) # or raise an exception and halt query execution. # # @example A `nil` is encountered by a non-null field # type_error ->(err, query_ctx) { # err.is_a?(GraphQL::InvalidNullError) # => true # } # # @example An object doesn't resolve to one of a {UnionType}'s members # type_error ->(err, query_ctx) { # err.is_a?(GraphQL::UnresolvedTypeError) # => true # } # # @see {DefaultTypeError} is the default behavior. # @param err [GraphQL::TypeError] The error encountered during execution # @param ctx [GraphQL::Query::Context] The context for the field where the error occurred # @return void def type_error(err, ctx) @type_error_proc.call(err, ctx) end # @param new_proc [#call] A new callable for handling type errors during execution def type_error=(new_proc) @type_error_proc = new_proc end # Can't delegate to `class` alias :_schema_class :class def_delegators :_schema_class, :unauthorized_object, :unauthorized_field, :inaccessible_fields def_delegators :_schema_class, :directive def_delegators :_schema_class, :error_handler # Given this schema member, find the class-based definition object # whose `method_name` should be treated as an application hook # @see {.visible?} # @see {.accessible?} def call_on_type_class(member, method_name, context, default:) member = if member.respond_to?(:type_class) member.type_class else member end if member.respond_to?(:relay_node_type) && (t = member.relay_node_type) member = t end if member.respond_to?(method_name) member.public_send(method_name, context) else default end end def visible?(member, context) call_on_type_class(member, :visible?, context, default: true) end def accessible?(member, context) call_on_type_class(member, :accessible?, context, default: true) end # A function to call when {#execute} receives an invalid query string # # @see {DefaultParseError} is the default behavior. # @param err [GraphQL::ParseError] The error encountered during parsing # @param ctx [GraphQL::Query::Context] The context for the query where the error occurred # @return void def parse_error(err, ctx) @parse_error_proc.call(err, ctx) end # @param new_proc [#call] A new callable for handling parse errors during execution def parse_error=(new_proc) @parse_error_proc = new_proc end # Get a unique identifier from this object # @param object [Any] An application object # @param type [GraphQL::BaseType] The current type definition # @param ctx [GraphQL::Query::Context] the context for the current query # @return [String] a unique identifier for `object` which clients can use to refetch it def id_from_object(object, type, ctx) if @id_from_object_proc.nil? raise(GraphQL::RequiredImplementationMissingError, "Can't generate an ID for #{object.inspect} of type #{type}, schema's `id_from_object` must be defined") else @id_from_object_proc.call(object, type, ctx) end end # @param new_proc [#call] A new callable for generating unique IDs def id_from_object=(new_proc) @id_from_object_proc = new_proc end # Create schema with the result of an introspection query. # @param introspection_result [Hash] A response from {GraphQL::Introspection::INTROSPECTION_QUERY} # @return [GraphQL::Schema] the schema described by `input` def self.from_introspection(introspection_result) GraphQL::Schema::Loader.load(introspection_result) end # Create schema from an IDL schema or file containing an IDL definition. # @param definition_or_path [String] A schema definition string, or a path to a file containing the definition # @param default_resolve [<#call(type, field, obj, args, ctx)>] A callable for handling field resolution # @param parser [Object] An object for handling definition string parsing (must respond to `parse`) # @param using [Hash] Plugins to attach to the created schema with `use(key, value)` # @param interpreter [Boolean] If false, the legacy {Execution::Execute} runtime will be used # @return [Class] the schema described by `document` def self.from_definition(definition_or_path, default_resolve: nil, interpreter: true, parser: GraphQL.default_parser, using: {}) # If the file ends in `.graphql`, treat it like a filepath if definition_or_path.end_with?(".graphql") GraphQL::Schema::BuildFromDefinition.from_definition_path( definition_or_path, default_resolve: default_resolve, parser: parser, using: using, interpreter: interpreter, ) else GraphQL::Schema::BuildFromDefinition.from_definition( definition_or_path, default_resolve: default_resolve, parser: parser, using: using, interpreter: interpreter, ) end end # Error that is raised when [#Schema#from_definition] is passed an invalid schema definition string. class InvalidDocumentError < Error; end; # Return the GraphQL IDL for the schema # @param context [Hash] # @param only [<#call(member, ctx)>] # @param except [<#call(member, ctx)>] # @return [String] def to_definition(only: nil, except: nil, context: {}) GraphQL::Schema::Printer.print_schema(self, only: only, except: except, context: context) end # Return the GraphQL::Language::Document IDL AST for the schema # @param context [Hash] # @param only [<#call(member, ctx)>] # @param except [<#call(member, ctx)>] # @return [GraphQL::Language::Document] def to_document(only: nil, except: nil, context: {}) GraphQL::Language::DocumentFromSchemaDefinition.new(self, only: only, except: except, context: context).document end # Return the Hash response of {Introspection::INTROSPECTION_QUERY}. # @param context [Hash] # @param only [<#call(member, ctx)>] # @param except [<#call(member, ctx)>] # @return [Hash] GraphQL result def as_json(only: nil, except: nil, context: {}) execute(Introspection.query(include_deprecated_args: true), only: only, except: except, context: context).to_h end # Returns the JSON response of {Introspection::INTROSPECTION_QUERY}. # @see {#as_json} # @return [String] def to_json(*args) JSON.pretty_generate(as_json(*args)) end def new_connections? !!connections end attr_accessor :connections class << self extend Forwardable # For compatibility, these methods all: # - Cause the Schema instance to be created, if it hasn't been created yet # - Delegate to that instance # Eventually, the methods will be moved into this class, removing the need for the singleton. def_delegators :graphql_definition, # Execution :execution_strategy_for_operation, :validate, # Configuration :metadata, :redefine, :id_from_object_proc, :object_from_id_proc, :id_from_object=, :object_from_id=, :remove_handler # @return [GraphQL::Subscriptions] attr_accessor :subscriptions # Returns the JSON response of {Introspection::INTROSPECTION_QUERY}. # @see {#as_json} # @return [String] def to_json(**args) JSON.pretty_generate(as_json(**args)) end # Return the Hash response of {Introspection::INTROSPECTION_QUERY}. # @param context [Hash] # @param only [<#call(member, ctx)>] # @param except [<#call(member, ctx)>] # @return [Hash] GraphQL result def as_json(only: nil, except: nil, context: {}) execute(Introspection.query(include_deprecated_args: true), only: only, except: except, context: context).to_h end # Return the GraphQL IDL for the schema # @param context [Hash] # @param only [<#call(member, ctx)>] # @param except [<#call(member, ctx)>] # @return [String] def to_definition(only: nil, except: nil, context: {}) GraphQL::Schema::Printer.print_schema(self, only: only, except: except, context: context) end # Return the GraphQL::Language::Document IDL AST for the schema # @return [GraphQL::Language::Document] def to_document GraphQL::Language::DocumentFromSchemaDefinition.new(self).document end def find(path) if !@finder @find_cache = {} @finder ||= GraphQL::Schema::Finder.new(self) end @find_cache[path] ||= @finder.find(path) end def graphql_definition @graphql_definition ||= to_graphql end def default_filter GraphQL::Filter.new(except: default_mask) end def default_mask(new_mask = nil) if new_mask @own_default_mask = new_mask else @own_default_mask || find_inherited_value(:default_mask, Schema::NullMask) end end def static_validator GraphQL::StaticValidation::Validator.new(schema: self) end def use(plugin, **kwargs) if kwargs.any? plugin.use(self, **kwargs) else plugin.use(self) end own_plugins << [plugin, kwargs] end def plugins find_inherited_value(:plugins, EMPTY_ARRAY) + own_plugins end def to_graphql schema_defn = self.new schema_defn.raise_definition_error = true schema_defn.query = query && query.graphql_definition schema_defn.mutation = mutation && mutation.graphql_definition schema_defn.subscription = subscription && subscription.graphql_definition schema_defn.validate_timeout = validate_timeout schema_defn.validate_max_errors = validate_max_errors schema_defn.max_complexity = max_complexity schema_defn.error_bubbling = error_bubbling schema_defn.max_depth = max_depth schema_defn.default_max_page_size = default_max_page_size schema_defn.orphan_types = orphan_types.map(&:graphql_definition) schema_defn.disable_introspection_entry_points = disable_introspection_entry_points? schema_defn.disable_schema_introspection_entry_point = disable_schema_introspection_entry_point? schema_defn.disable_type_introspection_entry_point = disable_type_introspection_entry_point? prepped_dirs = {} directives.each { |k, v| prepped_dirs[k] = v.graphql_definition} schema_defn.directives = prepped_dirs schema_defn.introspection_namespace = introspection schema_defn.resolve_type = method(:resolve_type) schema_defn.object_from_id = method(:object_from_id) schema_defn.id_from_object = method(:id_from_object) schema_defn.type_error = method(:type_error) schema_defn.context_class = context_class schema_defn.cursor_encoder = cursor_encoder schema_defn.tracers.concat(tracers) schema_defn.query_analyzers.concat(query_analyzers) schema_defn.analysis_engine = analysis_engine schema_defn.middleware.concat(all_middleware) schema_defn.multiplex_analyzers.concat(multiplex_analyzers) schema_defn.query_execution_strategy = query_execution_strategy schema_defn.mutation_execution_strategy = mutation_execution_strategy schema_defn.subscription_execution_strategy = subscription_execution_strategy schema_defn.default_mask = default_mask instrumenters.each do |step, insts| insts.each do |inst| schema_defn.instrumenters[step] << inst end end lazy_methods.each do |lazy_class, value_method| schema_defn.lazy_methods.set(lazy_class, value_method) end rescues.each do |err_class, handler| schema_defn.rescue_from(err_class, &handler) end schema_defn.subscriptions ||= self.subscriptions if !schema_defn.interpreter? schema_defn.instrumenters[:query] << GraphQL::Schema::Member::Instrumentation end if new_connections? schema_defn.connections = self.connections end schema_defn.send(:rebuild_artifacts) schema_defn end # Build a map of `{ name => type }` and return it # @return [Hash Class>] A dictionary of type classes by their GraphQL name # @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes. def types non_introspection_types.merge(introspection_system.types) end # @param type_name [String] # @return [Module, nil] A type, or nil if there's no type called `type_name` def get_type(type_name) own_types[type_name] || introspection_system.types[type_name] || find_inherited_value(:types, EMPTY_HASH)[type_name] end # @api private attr_writer :connections # @return [GraphQL::Pagination::Connections] if installed def connections if defined?(@connections) @connections else inherited_connections = find_inherited_value(:connections, nil) # This schema is part of an inheritance chain which is using new connections, # make a new instance, so we don't pollute the upstream one. if inherited_connections @connections = Pagination::Connections.new(schema: self) else nil end end end def new_connections? !!connections end def query(new_query_object = nil) if new_query_object if @query_object raise GraphQL::Error, "Second definition of `query(...)` (#{new_query_object.inspect}) is invalid, already configured with #{@query_object.inspect}" else @query_object = new_query_object add_type_and_traverse(new_query_object, root: true) nil end else @query_object || find_inherited_value(:query) end end def mutation(new_mutation_object = nil) if new_mutation_object if @mutation_object raise GraphQL::Error, "Second definition of `mutation(...)` (#{new_mutation_object.inspect}) is invalid, already configured with #{@mutation_object.inspect}" else @mutation_object = new_mutation_object add_type_and_traverse(new_mutation_object, root: true) nil end else @mutation_object || find_inherited_value(:mutation) end end def subscription(new_subscription_object = nil) if new_subscription_object if @subscription_object raise GraphQL::Error, "Second definition of `subscription(...)` (#{new_subscription_object.inspect}) is invalid, already configured with #{@subscription_object.inspect}" else @subscription_object = new_subscription_object add_subscription_extension_if_necessary add_type_and_traverse(new_subscription_object, root: true) nil end else @subscription_object || find_inherited_value(:subscription) end end # @see [GraphQL::Schema::Warden] Resticted access to root types # @return [GraphQL::ObjectType, nil] def root_type_for_operation(operation) case operation when "query" query when "mutation" mutation when "subscription" subscription else raise ArgumentError, "unknown operation type: #{operation}" end end def root_types @root_types end # @param type [Module] The type definition whose possible types you want to see # @return [Hash] All possible types, if no `type` is given. # @return [Array] Possible types for `type`, if it's given. def possible_types(type = nil, context = GraphQL::Query::NullContext) if type # TODO duck-typing `.possible_types` would probably be nicer here if type.kind.union? type.possible_types(context: context) else stored_possible_types = own_possible_types[type.graphql_name] visible_possible_types = if stored_possible_types && type.kind.interface? stored_possible_types.select do |possible_type| # Use `.graphql_name` comparison to match legacy vs class-based types. # When we don't need to support legacy `.define` types, use `.include?(type)` instead. possible_type.interfaces(context).any? { |interface| interface.graphql_name == type.graphql_name } end else stored_possible_types end visible_possible_types || introspection_system.possible_types[type.graphql_name] || ( superclass.respond_to?(:possible_types) ? superclass.possible_types(type, context) : EMPTY_ARRAY ) end else find_inherited_value(:possible_types, EMPTY_HASH) .merge(own_possible_types) .merge(introspection_system.possible_types) end end def union_memberships(type = nil) if type own_um = own_union_memberships.fetch(type.graphql_name, EMPTY_ARRAY) inherited_um = find_inherited_value(:union_memberships, EMPTY_HASH).fetch(type.graphql_name, EMPTY_ARRAY) own_um + inherited_um else joined_um = own_union_memberships.dup find_inherited_value(:union_memberhips, EMPTY_HASH).each do |k, v| um = joined_um[k] ||= [] um.concat(v) end joined_um end end def references_to(to_type = nil, from: nil) @own_references_to ||= Hash.new { |h, k| h[k] = [] } if to_type if !to_type.is_a?(String) to_type = to_type.graphql_name end if from @own_references_to[to_type] << from else own_refs = @own_references_to[to_type] inherited_refs = find_inherited_value(:references_to, EMPTY_HASH)[to_type] || EMPTY_ARRAY own_refs + inherited_refs end else # `@own_references_to` can be quite large for big schemas, # and generally speaking, we won't inherit any values. # So optimize the most common case -- don't create a duplicate Hash. inherited_value = find_inherited_value(:references_to, EMPTY_HASH) if inherited_value.any? inherited_value.merge(@own_references_to) else @own_references_to end end end def type_from_ast(ast_node, context: nil) type_owner = context ? context.warden : self GraphQL::Schema::TypeExpression.build_type(type_owner, ast_node) end def get_field(type_or_name, field_name) parent_type = case type_or_name when LateBoundType get_type(type_or_name.name) when String get_type(type_or_name) when Module type_or_name else raise ArgumentError, "unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})" end if parent_type.kind.fields? && (field = parent_type.get_field(field_name)) field elsif parent_type == query && (entry_point_field = introspection_system.entry_point(name: field_name)) entry_point_field elsif (dynamic_field = introspection_system.dynamic_field(name: field_name)) dynamic_field else nil end end def get_fields(type) type.fields end def introspection(new_introspection_namespace = nil) if new_introspection_namespace @introspection = new_introspection_namespace # reset this cached value: @introspection_system = nil else @introspection || find_inherited_value(:introspection) end end def introspection_system if !@introspection_system @introspection_system = Schema::IntrospectionSystem.new(self) @introspection_system.resolve_late_bindings end @introspection_system end def cursor_encoder(new_encoder = nil) if new_encoder @cursor_encoder = new_encoder end @cursor_encoder || find_inherited_value(:cursor_encoder, Base64Encoder) end def default_max_page_size(new_default_max_page_size = nil) if new_default_max_page_size @default_max_page_size = new_default_max_page_size else @default_max_page_size || find_inherited_value(:default_max_page_size) end end def query_execution_strategy(new_query_execution_strategy = nil) if new_query_execution_strategy @query_execution_strategy = new_query_execution_strategy else @query_execution_strategy || find_inherited_value(:query_execution_strategy, self.default_execution_strategy) end end def mutation_execution_strategy(new_mutation_execution_strategy = nil) if new_mutation_execution_strategy @mutation_execution_strategy = new_mutation_execution_strategy else @mutation_execution_strategy || find_inherited_value(:mutation_execution_strategy, self.default_execution_strategy) end end def subscription_execution_strategy(new_subscription_execution_strategy = nil) if new_subscription_execution_strategy @subscription_execution_strategy = new_subscription_execution_strategy else @subscription_execution_strategy || find_inherited_value(:subscription_execution_strategy, self.default_execution_strategy) end end attr_writer :validate_timeout def validate_timeout(new_validate_timeout = nil) if new_validate_timeout @validate_timeout = new_validate_timeout elsif defined?(@validate_timeout) @validate_timeout else find_inherited_value(:validate_timeout) end end attr_writer :validate_max_errors def validate_max_errors(new_validate_max_errors = nil) if new_validate_max_errors @validate_max_errors = new_validate_max_errors elsif defined?(@validate_max_errors) @validate_max_errors else find_inherited_value(:validate_max_errors) end end attr_writer :max_complexity def max_complexity(max_complexity = nil) if max_complexity @max_complexity = max_complexity elsif defined?(@max_complexity) @max_complexity else find_inherited_value(:max_complexity) end end attr_writer :analysis_engine def analysis_engine @analysis_engine || find_inherited_value(:analysis_engine, GraphQL::Analysis) end def using_ast_analysis? analysis_engine == GraphQL::Analysis::AST end def interpreter? if defined?(@interpreter) @interpreter else find_inherited_value(:interpreter?, false) end end attr_writer :interpreter def error_bubbling(new_error_bubbling = nil) if !new_error_bubbling.nil? @error_bubbling = new_error_bubbling else @error_bubbling.nil? ? find_inherited_value(:error_bubbling) : @error_bubbling end end attr_writer :error_bubbling attr_writer :max_depth def max_depth(new_max_depth = nil) if new_max_depth @max_depth = new_max_depth elsif defined?(@max_depth) @max_depth else find_inherited_value(:max_depth) end end def disable_introspection_entry_points @disable_introspection_entry_points = true # TODO: this clears the cache made in `def types`. But this is not a great solution. @introspection_system = nil end def disable_schema_introspection_entry_point @disable_schema_introspection_entry_point = true # TODO: this clears the cache made in `def types`. But this is not a great solution. @introspection_system = nil end def disable_type_introspection_entry_point @disable_type_introspection_entry_point = true # TODO: this clears the cache made in `def types`. But this is not a great solution. @introspection_system = nil end def disable_introspection_entry_points? if instance_variable_defined?(:@disable_introspection_entry_points) @disable_introspection_entry_points else find_inherited_value(:disable_introspection_entry_points?, false) end end def disable_schema_introspection_entry_point? if instance_variable_defined?(:@disable_schema_introspection_entry_point) @disable_schema_introspection_entry_point else find_inherited_value(:disable_schema_introspection_entry_point?, false) end end def disable_type_introspection_entry_point? if instance_variable_defined?(:@disable_type_introspection_entry_point) @disable_type_introspection_entry_point else find_inherited_value(:disable_type_introspection_entry_point?, false) end end def orphan_types(*new_orphan_types) if new_orphan_types.any? new_orphan_types = new_orphan_types.flatten add_type_and_traverse(new_orphan_types, root: false) @orphan_types = new_orphan_types own_orphan_types.concat(new_orphan_types.flatten) end find_inherited_value(:orphan_types, EMPTY_ARRAY) + own_orphan_types end def default_execution_strategy if superclass <= GraphQL::Schema superclass.default_execution_strategy else @default_execution_strategy ||= GraphQL::Execution::Execute end end def context_class(new_context_class = nil) if new_context_class @context_class = new_context_class else @context_class || find_inherited_value(:context_class, GraphQL::Query::Context) end end def rescue_from(*err_classes, &handler_block) err_classes.each do |err_class| own_rescues[err_class] = handler_block end end # rubocop:disable Lint/DuplicateMethods module ResolveTypeWithType def resolve_type(type, obj, ctx) first_resolved_type, resolved_value = if type.is_a?(Module) && type.respond_to?(:resolve_type) type.resolve_type(obj, ctx) else super end after_lazy(first_resolved_type) do |resolved_type| if resolved_type.nil? || (resolved_type.is_a?(Module) && resolved_type.respond_to?(:kind)) || resolved_type.is_a?(GraphQL::BaseType) if resolved_value [resolved_type, resolved_value] else resolved_type end else raise ".resolve_type should return a type definition, but got #{resolved_type.inspect} (#{resolved_type.class}) from `resolve_type(#{type}, #{obj}, #{ctx})`" end end end end def resolve_type(type, obj, ctx) if type.kind.object? type else raise GraphQL::RequiredImplementationMissingError, "#{self.name}.resolve_type(type, obj, ctx) must be implemented to use Union types or Interface types (tried to resolve: #{type.name})" end end # rubocop:enable Lint/DuplicateMethods def inherited(child_class) if self == GraphQL::Schema child_class.directives(default_directives.values) end child_class.singleton_class.prepend(ResolveTypeWithType) super end def rescues find_inherited_value(:rescues, EMPTY_HASH).merge(own_rescues) end def object_from_id(node_id, ctx) raise GraphQL::RequiredImplementationMissingError, "#{self.name}.object_from_id(node_id, ctx) must be implemented to load by ID (tried to load from id `#{node_id}`)" end def id_from_object(object, type, ctx) raise GraphQL::RequiredImplementationMissingError, "#{self.name}.id_from_object(object, type, ctx) must be implemented to create global ids (tried to create an id for `#{object.inspect}`)" end def visible?(member, ctx) member.type_class.visible?(ctx) end def accessible?(member, ctx) member.type_class.accessible?(ctx) end # This hook is called when a client tries to access one or more # fields that fail the `accessible?` check. # # By default, an error is added to the response. Override this hook to # track metrics or return a different error to the client. # # @param error [InaccessibleFieldsError] The analysis error for this check # @return [AnalysisError, nil] Return an error to skip the query def inaccessible_fields(error) error end # This hook is called when an object fails an `authorized?` check. # You might report to your bug tracker here, so you can correct # the field resolvers not to return unauthorized objects. # # By default, this hook just replaces the unauthorized object with `nil`. # # Whatever value is returned from this method will be used instead of the # unauthorized object (accessible as `unauthorized_error.object`). If an # error is raised, then `nil` will be used. # # If you want to add an error to the `"errors"` key, raise a {GraphQL::ExecutionError} # in this hook. # # @param unauthorized_error [GraphQL::UnauthorizedError] # @return [Object] The returned object will be put in the GraphQL response def unauthorized_object(unauthorized_error) nil end # This hook is called when a field fails an `authorized?` check. # # By default, this hook implements the same behavior as unauthorized_object. # # Whatever value is returned from this method will be used instead of the # unauthorized field . If an error is raised, then `nil` will be used. # # If you want to add an error to the `"errors"` key, raise a {GraphQL::ExecutionError} # in this hook. # # @param unauthorized_error [GraphQL::UnauthorizedFieldError] # @return [Field] The returned field will be put in the GraphQL response def unauthorized_field(unauthorized_error) unauthorized_object(unauthorized_error) end def type_error(type_err, ctx) DefaultTypeError.call(type_err, ctx) end # A function to call when {#execute} receives an invalid query string # # The default is to add the error to `context.errors` # @param err [GraphQL::ParseError] The error encountered during parsing # @param ctx [GraphQL::Query::Context] The context for the query where the error occurred # @return void def parse_error(parse_err, ctx) ctx.errors.push(parse_err) end attr_writer :error_handler # @return [GraphQL::Execution::Errors, Class] def error_handler if defined?(@error_handler) @error_handler else find_inherited_value(:error_handler, GraphQL::Execution::Errors::NullErrorHandler) end end def lazy_resolve(lazy_class, value_method) lazy_methods.set(lazy_class, value_method) end def instrument(instrument_step, instrumenter, options = {}) step = if instrument_step == :field && options[:after_built_ins] :field_after_built_ins else instrument_step end own_instrumenters[step] << instrumenter end # Add several directives at once # @param new_directives [Class] def directives(*new_directives) if new_directives.any? new_directives.flatten.each { |d| directive(d) } end find_inherited_value(:directives, default_directives).merge(own_directives) end # Attach a single directive to this schema # @param new_directive [Class] def directive(new_directive) add_type_and_traverse(new_directive, root: false) own_directives[new_directive.graphql_name] = new_directive end def default_directives @default_directives ||= { "include" => GraphQL::Schema::Directive::Include, "skip" => GraphQL::Schema::Directive::Skip, "deprecated" => GraphQL::Schema::Directive::Deprecated, }.freeze end def tracer(new_tracer) own_tracers << new_tracer end def tracers find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers end def query_analyzer(new_analyzer) if new_analyzer == GraphQL::Authorization::Analyzer warn("The Authorization query analyzer is deprecated. Authorizing at query runtime is generally a better idea.") end own_query_analyzers << new_analyzer end def query_analyzers find_inherited_value(:query_analyzers, EMPTY_ARRAY) + own_query_analyzers end def middleware(new_middleware = nil) if new_middleware own_middleware << new_middleware else # TODO make sure this is cached when running a query MiddlewareChain.new(steps: all_middleware, final_step: GraphQL::Execution::Execute::FieldResolveStep) end end def multiplex_analyzer(new_analyzer) own_multiplex_analyzers << new_analyzer end def multiplex_analyzers find_inherited_value(:multiplex_analyzers, EMPTY_ARRAY) + own_multiplex_analyzers end # Execute a query on itself. # @see {Query#initialize} for arguments. # @return [Hash] query result, ready to be serialized as JSON def execute(query_str = nil, **kwargs) if query_str kwargs[:query] = query_str end # Some of the query context _should_ be passed to the multiplex, too multiplex_context = if (ctx = kwargs[:context]) { backtrace: ctx[:backtrace], tracers: ctx[:tracers], } else {} end # Since we're running one query, don't run a multiplex-level complexity analyzer all_results = multiplex([kwargs], max_complexity: nil, context: multiplex_context) all_results[0] end # Execute several queries on itself, concurrently. # # @example Run several queries at once # context = { ... } # queries = [ # { query: params[:query_1], variables: params[:variables_1], context: context }, # { query: params[:query_2], variables: params[:variables_2], context: context }, # ] # results = MySchema.multiplex(queries) # render json: { # result_1: results[0], # result_2: results[1], # } # # @see {Query#initialize} for query keyword arguments # @see {Execution::Multiplex#run_queries} for multiplex keyword arguments # @param queries [Array] Keyword arguments for each query # @param context [Hash] Multiplex-level context # @return [Array] One result for each query in the input def multiplex(queries, **kwargs) schema = if interpreter? self else graphql_definition end GraphQL::Execution::Multiplex.run_all(schema, queries, **kwargs) end def instrumenters inherited_instrumenters = find_inherited_value(:instrumenters) || Hash.new { |h,k| h[k] = [] } inherited_instrumenters.merge(own_instrumenters) do |_step, inherited, own| inherited + own end end # @api private def add_subscription_extension_if_necessary if interpreter? && !defined?(@subscription_extension_added) && subscription && self.subscriptions @subscription_extension_added = true if subscription.singleton_class.ancestors.include?(Subscriptions::SubscriptionRoot) warn("`extend Subscriptions::SubscriptionRoot` is no longer required; you may remove it from #{self}'s `subscription` root type (#{subscription}).") else subscription.fields.each do |name, field| field.extension(Subscriptions::DefaultSubscriptionResolveExtension) end end end end def query_stack_error(query, err) query.context.errors.push(GraphQL::ExecutionError.new("This query is too large to execute.")) end private def lazy_methods if !defined?(@lazy_methods) if inherited_map = find_inherited_value(:lazy_methods) # this isn't _completely_ inherited :S (Things added after `dup` won't work) @lazy_methods = inherited_map.dup else @lazy_methods = GraphQL::Execution::Lazy::LazyMethodMap.new @lazy_methods.set(GraphQL::Execution::Lazy, :value) end end @lazy_methods end def own_types @own_types ||= {} end def non_introspection_types find_inherited_value(:non_introspection_types, EMPTY_HASH).merge(own_types) end def own_plugins @own_plugins ||= [] end def own_rescues @own_rescues ||= {} end def own_orphan_types @own_orphan_types ||= [] end def own_possible_types @own_possible_types ||= {} end def own_union_memberships @own_union_memberships ||= {} end def own_directives @own_directives ||= {} end def own_instrumenters @own_instrumenters ||= Hash.new { |h,k| h[k] = [] } end def own_tracers @own_tracers ||= [] end def own_query_analyzers @defined_query_analyzers ||= [] end def all_middleware find_inherited_value(:all_middleware, EMPTY_ARRAY) + own_middleware end def own_middleware @own_middleware ||= [] end def own_multiplex_analyzers @own_multiplex_analyzers ||= [] end # @param t [Module, Array] # @return [void] def add_type_and_traverse(t, root:) if root @root_types ||= [] @root_types << t end late_types = [] new_types = Array(t) new_types.each { |t| add_type(t, owner: nil, late_types: late_types, path: [t.graphql_name]) } missed_late_types = 0 while (late_type_vals = late_types.shift) type_owner, lt = late_type_vals if lt.is_a?(String) type = Member::BuildType.constantize(lt) # Reset the counter, since we might succeed next go-round missed_late_types = 0 update_type_owner(type_owner, type) add_type(type, owner: type_owner, late_types: late_types, path: [type.graphql_name]) elsif lt.is_a?(LateBoundType) if (type = get_type(lt.graphql_name)) # Reset the counter, since we might succeed next go-round missed_late_types = 0 update_type_owner(type_owner, type) add_type(type, owner: type_owner, late_types: late_types, path: [type.graphql_name]) else missed_late_types += 1 # Add it back to the list, maybe we'll be able to resolve it later. late_types << [type_owner, lt] if missed_late_types == late_types.size # We've looked at all of them and haven't resolved one. raise UnresolvedLateBoundTypeError.new(type: lt) else # Try the next one end end else raise ArgumentError, "Unexpected late type: #{lt.inspect}" end end nil end def update_type_owner(owner, type) case owner when Class if owner.kind.union? # It's a union with possible_types # Replace the item by class name owner.assign_type_membership_object_type(type) own_possible_types[owner.graphql_name] = owner.possible_types elsif type.kind.interface? && owner.kind.object? new_interfaces = [] owner.interfaces.each do |int_t| if int_t.is_a?(String) && int_t == type.graphql_name new_interfaces << type elsif int_t.is_a?(LateBoundType) && int_t.graphql_name == type.graphql_name new_interfaces << type else # Don't re-add proper interface definitions, # they were probably already added, maybe with options. end end owner.implements(*new_interfaces) new_interfaces.each do |int| pt = own_possible_types[int.graphql_name] ||= [] if !pt.include?(owner) pt << owner end end end when nil # It's a root type own_types[type.graphql_name] = type when GraphQL::Schema::Field, GraphQL::Schema::Argument orig_type = owner.type # Apply list/non-null wrapper as needed if orig_type.respond_to?(:of_type) transforms = [] while (orig_type.respond_to?(:of_type)) if orig_type.kind.non_null? transforms << :to_non_null_type elsif orig_type.kind.list? transforms << :to_list_type else raise "Invariant: :of_type isn't non-null or list" end orig_type = orig_type.of_type end transforms.reverse_each { |t| type = type.public_send(t) } end owner.type = type else raise "Unexpected update: #{owner.inspect} #{type.inspect}" end end def add_type(type, owner:, late_types:, path:) if type.respond_to?(:metadata) && type.metadata.is_a?(Hash) type_class = type.metadata[:type_class] if type_class.nil? raise ArgumentError, "Can't add legacy type: #{type} (#{type.class})" else type = type_class end elsif type.is_a?(String) || type.is_a?(GraphQL::Schema::LateBoundType) late_types << [owner, type] return end if owner.is_a?(Class) && owner < GraphQL::Schema::Union um = own_union_memberships[type.graphql_name] ||= [] um << owner end if (prev_type = own_types[type.graphql_name]) if prev_type != type raise DuplicateTypeNamesError.new( type_name: type.graphql_name, first_definition: prev_type, second_definition: type, path: path, ) else # This type was already added end elsif type.is_a?(Class) && type < GraphQL::Schema::Directive type.arguments.each do |name, arg| arg_type = arg.type.unwrap references_to(arg_type, from: arg) add_type(arg_type, owner: arg, late_types: late_types, path: path + [name]) end else own_types[type.graphql_name] = type if type.kind.fields? type.fields.each do |name, field| field_type = field.type.unwrap references_to(field_type, from: field) field_path = path + [name] add_type(field_type, owner: field, late_types: late_types, path: field_path) field.arguments.each do |arg_name, arg| arg_type = arg.type.unwrap references_to(arg_type, from: arg) add_type(arg_type, owner: arg, late_types: late_types, path: field_path + [arg_name]) end end end if type.kind.input_object? type.arguments.each do |arg_name, arg| arg_type = arg.type.unwrap references_to(arg_type, from: arg) add_type(arg_type, owner: arg, late_types: late_types, path: path + [arg_name]) end end if type.kind.union? own_possible_types[type.graphql_name] = type.possible_types type.possible_types.each do |t| add_type(t, owner: type, late_types: late_types, path: path + ["possible_types"]) end end if type.kind.interface? type.orphan_types.each do |t| add_type(t, owner: type, late_types: late_types, path: path + ["orphan_types"]) end end if type.kind.object? own_possible_types[type.graphql_name] = [type] type.interface_type_memberships.each do |interface_type_membership| case interface_type_membership when Schema::TypeMembership interface_type = interface_type_membership.abstract_type # We can get these now; we'll have to get late-bound types later if interface_type.is_a?(Module) implementers = own_possible_types[interface_type.graphql_name] ||= [] implementers << type end when String, Schema::LateBoundType interface_type = interface_type_membership else raise ArgumentError, "Invariant: unexpected type membership for #{type.graphql_name}: #{interface_type_membership.class} (#{interface_type_membership.inspect})" end add_type(interface_type, owner: type, late_types: late_types, path: path + ["implements"]) end end end end end protected def rescues? !!@rescue_middleware end # Lazily create a middleware and add it to the schema # (Don't add it if it's not used) def rescue_middleware @rescue_middleware ||= GraphQL::Schema::RescueMiddleware.new.tap { |m| middleware.insert(0, m) } end private def rebuild_artifacts if @rebuilding_artifacts raise CyclicalDefinitionError, "Part of the schema build process re-triggered the schema build process, causing an infinite loop. Avoid using Schema#types, Schema#possible_types, and Schema#get_field during schema build." else @rebuilding_artifacts = true @introspection_system = Schema::IntrospectionSystem.new(self) traversal = Traversal.new(self) @types = traversal.type_map @root_types = [query, mutation, subscription] @instrumented_field_map = traversal.instrumented_field_map @type_reference_map = traversal.type_reference_map @union_memberships = traversal.union_memberships @find_cache = {} @finder = Finder.new(self) end ensure @rebuilding_artifacts = false end class CyclicalDefinitionError < GraphQL::Error end def with_definition_error_check if @definition_error raise @definition_error else yield end end end end graphql-ruby-1.11.10/lib/graphql/schema/000077500000000000000000000000001414121453000177275ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/schema/argument.rb000066400000000000000000000207751414121453000221110ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Argument if !String.method_defined?(:-@) using GraphQL::StringDedupBackport end include GraphQL::Schema::Member::CachedGraphQLDefinition include GraphQL::Schema::Member::AcceptsDefinition include GraphQL::Schema::Member::HasPath include GraphQL::Schema::Member::HasAstNode NO_DEFAULT = :__no_default__ # @return [String] the GraphQL name for this argument, camelized unless `camelize: false` is provided attr_reader :name alias :graphql_name :name # @return [GraphQL::Schema::Field, Class] The field or input object this argument belongs to attr_reader :owner # @return [Symbol] A method to call to transform this value before sending it to field resolution method attr_reader :prepare # @return [Symbol] This argument's name in Ruby keyword arguments attr_reader :keyword # @return [Class, Module, nil] If this argument should load an application object, this is the type of object to load attr_reader :loads # @return [Boolean] true if a resolver defined this argument def from_resolver? @from_resolver end # @param arg_name [Symbol] # @param type_expr # @param desc [String] # @param required [Boolean] if true, this argument is non-null; if false, this argument is nullable # @param description [String] # @param default_value [Object] # @param as [Symbol] Override the keyword name when passed to a method # @param prepare [Symbol] A method to call to transform this argument's valuebefore sending it to field resolution # @param camelize [Boolean] if true, the name will be camelized when building the schema # @param from_resolver [Boolean] if true, a Resolver class defined this argument # @param method_access [Boolean] If false, don't build method access on legacy {Query::Arguments} instances. # @param deprecation_reason [String] def initialize(arg_name = nil, type_expr = nil, desc = nil, required:, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NO_DEFAULT, as: nil, from_resolver: false, camelize: true, prepare: nil, method_access: true, owner:, deprecation_reason: nil, &definition_block) arg_name ||= name @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s) @type_expr = type_expr || type @description = desc || description @null = !required @default_value = default_value @owner = owner @as = as @loads = loads @keyword = as || (arg_name.is_a?(Symbol) ? arg_name : Schema::Member::BuildType.underscore(@name).to_sym) @prepare = prepare @ast_node = ast_node @from_resolver = from_resolver @method_access = method_access self.deprecation_reason = deprecation_reason if definition_block if definition_block.arity == 1 instance_exec(self, &definition_block) else instance_eval(&definition_block) end end end # @return [Object] the value used when the client doesn't provide a value for this argument attr_reader :default_value # @return [Boolean] True if this argument has a default value def default_value? @default_value != NO_DEFAULT end attr_writer :description # @return [String] Documentation for this argument def description(text = nil) if text @description = text else @description end end # @return [String] Deprecation reason for this argument def deprecation_reason(text = nil) if text validate_deprecated_or_optional(null: @null, deprecation_reason: text) @deprecation_reason = text else @deprecation_reason end end alias_method :deprecation_reason=, :deprecation_reason def visible?(context) true end def accessible?(context) true end def authorized?(obj, value, ctx) authorized_as_type?(obj, value, ctx, as_type: type) end def authorized_as_type?(obj, value, ctx, as_type:) if value.nil? return true end if as_type.kind.non_null? as_type = as_type.of_type end if as_type.kind.list? value.each do |v| if !authorized_as_type?(obj, v, ctx, as_type: as_type.of_type) return false end end elsif as_type.kind.input_object? as_type.arguments.each do |_name, input_obj_arg| input_obj_arg = input_obj_arg.type_class # TODO: this skips input objects whose values were alread replaced with application objects. # See: https://github.com/rmosolgo/graphql-ruby/issues/2633 if value.respond_to?(:key?) && value.key?(input_obj_arg.keyword) && !input_obj_arg.authorized?(obj, value[input_obj_arg.keyword], ctx) return false end end end # None of the early-return conditions were activated, # so this is authorized. true end def to_graphql argument = GraphQL::Argument.new argument.name = @name argument.type = -> { type } argument.description = @description argument.metadata[:type_class] = self argument.as = @as argument.ast_node = ast_node argument.method_access = @method_access if NO_DEFAULT != @default_value argument.default_value = @default_value end if @deprecation_reason argument.deprecation_reason = @deprecation_reason end argument end def type=(new_type) validate_input_type(new_type) # This isn't true for LateBoundTypes, but we can assume those will # be updated via this codepath later in schema setup. if new_type.respond_to?(:non_null?) validate_deprecated_or_optional(null: !new_type.non_null?, deprecation_reason: deprecation_reason) end @type = new_type end def type @type ||= begin parsed_type = begin Member::BuildType.parse_type(@type_expr, null: @null) rescue StandardError => err raise ArgumentError, "Couldn't build type for Argument #{@owner.name}.#{name}: #{err.class.name}: #{err.message}", err.backtrace end # Use the setter method to get validations self.type = parsed_type end end def statically_coercible? return @statically_coercible if defined?(@statically_coercible) @statically_coercible = !@prepare.is_a?(String) && !@prepare.is_a?(Symbol) end # Apply the {prepare} configuration to `value`, using methods from `obj`. # Used by the runtime. # @api private def prepare_value(obj, value, context: nil) if value.is_a?(GraphQL::Schema::InputObject) value = value.prepare end if @prepare.nil? value elsif @prepare.is_a?(String) || @prepare.is_a?(Symbol) if obj.nil? # The problem here is, we _used to_ prepare while building variables. # But now we don't have the runtime object there. # # This will have to be called later, when the runtime object _is_ available. value else obj.public_send(@prepare, value) end elsif @prepare.respond_to?(:call) @prepare.call(value, context || obj.context) else raise "Invalid prepare for #{@owner.name}.name: #{@prepare.inspect}" end end private def validate_input_type(input_type) if input_type.is_a?(String) || input_type.is_a?(GraphQL::Schema::LateBoundType) # Do nothing; assume this will be validated later elsif input_type.kind.non_null? || input_type.kind.list? validate_input_type(input_type.unwrap) elsif !input_type.kind.input? raise ArgumentError, "Invalid input type for #{path}: #{input_type.graphql_name}. Must be scalar, enum, or input object, not #{input_type.kind.name}." else # It's an input type, we're OK end end def validate_deprecated_or_optional(null:, deprecation_reason:) if deprecation_reason && !null raise ArgumentError, "Required arguments cannot be deprecated: #{path}." end end end end end graphql-ruby-1.11.10/lib/graphql/schema/base_64_bp.rb000066400000000000000000000010131414121453000221530ustar00rootroot00000000000000# frozen_string_literal: true require 'base64' # backport from ruby v2.5 to v2.2 that has no `padding` things # @api private module Base64Bp extend Base64 module_function def urlsafe_encode64(bin, padding:) str = strict_encode64(bin) str.tr!("+/", "-_") str.delete!("=") unless padding str end def urlsafe_decode64(str) str = str.tr("-_", "+/") if !str.end_with?("=") && str.length % 4 != 0 str = str.ljust((str.length + 3) & ~3, "=") end strict_decode64(str) end end graphql-ruby-1.11.10/lib/graphql/schema/base_64_encoder.rb000066400000000000000000000010601414121453000231730ustar00rootroot00000000000000# frozen_string_literal: true require 'graphql/schema/base_64_bp' module GraphQL class Schema # @api private module Base64Encoder def self.encode(unencoded_text, nonce: false) Base64Bp.urlsafe_encode64(unencoded_text, padding: false) end def self.decode(encoded_text, nonce: false) # urlsafe_decode64 is for forward compatibility Base64Bp.urlsafe_decode64(encoded_text) rescue ArgumentError raise GraphQL::ExecutionError, "Invalid input: #{encoded_text.inspect}" end end end end graphql-ruby-1.11.10/lib/graphql/schema/build_from_definition.rb000066400000000000000000000365101414121453000246130ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/schema/build_from_definition/resolve_map" module GraphQL class Schema module BuildFromDefinition if !String.method_defined?(:-@) using GraphQL::StringDedupBackport end class << self # @see {Schema.from_definition} def from_definition(definition_string, parser: GraphQL.default_parser, **kwargs) from_document(parser.parse(definition_string), **kwargs) end def from_definition_path(definition_path, parser: GraphQL.default_parser, **kwargs) from_document(parser.parse_file(definition_path), **kwargs) end def from_document(document, default_resolve:, using: {}, relay: false, interpreter: true) Builder.build(document, default_resolve: default_resolve || {}, relay: relay, using: using, interpreter: interpreter) end end # @api private module Builder extend self def build(document, default_resolve:, using: {}, interpreter: true, relay:) raise InvalidDocumentError.new('Must provide a document ast.') if !document || !document.is_a?(GraphQL::Language::Nodes::Document) if default_resolve.is_a?(Hash) default_resolve = ResolveMap.new(default_resolve) end schema_defns = document.definitions.select { |d| d.is_a?(GraphQL::Language::Nodes::SchemaDefinition) } if schema_defns.size > 1 raise InvalidDocumentError.new('Must provide only one schema definition.') end schema_definition = schema_defns.first types = {} directives = {} type_resolver = ->(type) { resolve_type(types, type) } document.definitions.each do |definition| case definition when GraphQL::Language::Nodes::SchemaDefinition nil # already handled when GraphQL::Language::Nodes::EnumTypeDefinition types[definition.name] = build_enum_type(definition, type_resolver) when GraphQL::Language::Nodes::ObjectTypeDefinition types[definition.name] = build_object_type(definition, type_resolver) when GraphQL::Language::Nodes::InterfaceTypeDefinition types[definition.name] = build_interface_type(definition, type_resolver) when GraphQL::Language::Nodes::UnionTypeDefinition types[definition.name] = build_union_type(definition, type_resolver) when GraphQL::Language::Nodes::ScalarTypeDefinition types[definition.name] = build_scalar_type(definition, type_resolver, default_resolve: default_resolve) when GraphQL::Language::Nodes::InputObjectTypeDefinition types[definition.name] = build_input_object_type(definition, type_resolver) when GraphQL::Language::Nodes::DirectiveDefinition directives[definition.name] = build_directive(definition, type_resolver) end end # At this point, if types named by the built in types are _late-bound_ types, # that means they were referenced in the schema but not defined in the schema. # That's supported for built-in types. (Eg, you can use `String` without defining it.) # In that case, insert the concrete type definition now. # # However, if the type in `types` is a _concrete_ type definition, that means that # the document contained an explicit definition of the scalar type. # Don't override it in this case. GraphQL::Schema::BUILT_IN_TYPES.each do |scalar_name, built_in_scalar| existing_type = types[scalar_name] if existing_type.is_a?(GraphQL::Schema::LateBoundType) types[scalar_name] = built_in_scalar end end directives = GraphQL::Schema.default_directives.merge(directives) if schema_definition if schema_definition.query raise InvalidDocumentError.new("Specified query type \"#{schema_definition.query}\" not found in document.") unless types[schema_definition.query] query_root_type = types[schema_definition.query] end if schema_definition.mutation raise InvalidDocumentError.new("Specified mutation type \"#{schema_definition.mutation}\" not found in document.") unless types[schema_definition.mutation] mutation_root_type = types[schema_definition.mutation] end if schema_definition.subscription raise InvalidDocumentError.new("Specified subscription type \"#{schema_definition.subscription}\" not found in document.") unless types[schema_definition.subscription] subscription_root_type = types[schema_definition.subscription] end else query_root_type = types['Query'] mutation_root_type = types['Mutation'] subscription_root_type = types['Subscription'] end raise InvalidDocumentError.new('Must provide schema definition with query type or a type named Query.') unless query_root_type Class.new(GraphQL::Schema) do begin # Add these first so that there's some chance of resolving late-bound types orphan_types types.values query query_root_type mutation mutation_root_type subscription subscription_root_type rescue Schema::UnresolvedLateBoundTypeError => err type_name = err.type.name err_backtrace = err.backtrace raise InvalidDocumentError, "Type \"#{type_name}\" not found in document.", err_backtrace end if default_resolve.respond_to?(:resolve_type) def self.resolve_type(*args) self.definition_default_resolve.resolve_type(*args) end else def self.resolve_type(*args) NullResolveType.call(*args) end end directives directives.values if schema_definition ast_node(schema_definition) end if interpreter use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST end using.each do |plugin, options| if options use(plugin, **options) else use(plugin) end end # Empty `orphan_types` -- this will make unreachable types ... unreachable. own_orphan_types.clear class << self attr_accessor :definition_default_resolve end self.definition_default_resolve = default_resolve def definition_default_resolve self.class.definition_default_resolve end def self.inherited(child_class) child_class.definition_default_resolve = self.definition_default_resolve end end end NullResolveType = ->(type, obj, ctx) { raise(GraphQL::RequiredImplementationMissingError, "Generated Schema cannot use Interface or Union types for execution. Implement resolve_type on your resolver.") } def build_enum_type(enum_type_definition, type_resolver) builder = self Class.new(GraphQL::Schema::Enum) do graphql_name(enum_type_definition.name) description(enum_type_definition.description) ast_node(enum_type_definition) enum_type_definition.values.each do |enum_value_definition| value(enum_value_definition.name, value: enum_value_definition.name, deprecation_reason: builder.build_deprecation_reason(enum_value_definition.directives), description: enum_value_definition.description, ast_node: enum_value_definition, ) end end end def build_deprecation_reason(directives) deprecated_directive = directives.find{ |d| d.name == 'deprecated' } return unless deprecated_directive reason = deprecated_directive.arguments.find{ |a| a.name == 'reason' } return GraphQL::Schema::Directive::DEFAULT_DEPRECATION_REASON unless reason reason.value end def build_scalar_type(scalar_type_definition, type_resolver, default_resolve:) builder = self Class.new(GraphQL::Schema::Scalar) do graphql_name(scalar_type_definition.name) description(scalar_type_definition.description) ast_node(scalar_type_definition) if default_resolve.respond_to?(:coerce_input) # Put these method definitions in another method to avoid retaining `type_resolve` # from this method's bindiing builder.build_scalar_type_coerce_method(self, :coerce_input, default_resolve) builder.build_scalar_type_coerce_method(self, :coerce_result, default_resolve) end end end def build_scalar_type_coerce_method(scalar_class, method_name, default_definition_resolve) scalar_class.define_singleton_method(method_name) do |val, ctx| default_definition_resolve.public_send(method_name, self, val, ctx) end end def build_union_type(union_type_definition, type_resolver) Class.new(GraphQL::Schema::Union) do graphql_name(union_type_definition.name) description(union_type_definition.description) possible_types(*union_type_definition.types.map { |type_name| type_resolver.call(type_name) }) ast_node(union_type_definition) end end def build_object_type(object_type_definition, type_resolver) builder = self Class.new(GraphQL::Schema::Object) do graphql_name(object_type_definition.name) description(object_type_definition.description) ast_node(object_type_definition) object_type_definition.interfaces.each do |interface_name| interface_defn = type_resolver.call(interface_name) implements(interface_defn) end builder.build_fields(self, object_type_definition.fields, type_resolver, default_resolve: true) end end def build_input_object_type(input_object_type_definition, type_resolver) builder = self Class.new(GraphQL::Schema::InputObject) do graphql_name(input_object_type_definition.name) description(input_object_type_definition.description) ast_node(input_object_type_definition) builder.build_arguments(self, input_object_type_definition.fields, type_resolver) end end def build_default_value(default_value) case default_value when GraphQL::Language::Nodes::Enum default_value.name when GraphQL::Language::Nodes::NullValue nil when GraphQL::Language::Nodes::InputObject default_value.to_h when Array default_value.map { |v| build_default_value(v) } else default_value end end NO_DEFAULT_VALUE = {}.freeze def build_arguments(type_class, arguments, type_resolver) builder = self arguments.each do |argument_defn| default_value_kwargs = if !argument_defn.default_value.nil? { default_value: builder.build_default_value(argument_defn.default_value) } else NO_DEFAULT_VALUE end type_class.argument( argument_defn.name, type: type_resolver.call(argument_defn.type), required: false, description: argument_defn.description, deprecation_reason: builder.build_deprecation_reason(argument_defn.directives), ast_node: argument_defn, camelize: false, method_access: false, **default_value_kwargs ) end end def build_directive(directive_definition, type_resolver) builder = self Class.new(GraphQL::Schema::Directive) do graphql_name(directive_definition.name) description(directive_definition.description) locations(*directive_definition.locations.map { |location| location.name.to_sym }) ast_node(directive_definition) builder.build_arguments(self, directive_definition.arguments, type_resolver) end end def build_interface_type(interface_type_definition, type_resolver) builder = self Module.new do include GraphQL::Schema::Interface graphql_name(interface_type_definition.name) description(interface_type_definition.description) ast_node(interface_type_definition) builder.build_fields(self, interface_type_definition.fields, type_resolver, default_resolve: nil) end end def build_fields(owner, field_definitions, type_resolver, default_resolve:) builder = self field_definitions.each do |field_definition| type_name = resolve_type_name(field_definition.type) resolve_method_name = -"resolve_field_#{field_definition.name}" schema_field_defn = owner.field( field_definition.name, description: field_definition.description, type: type_resolver.call(field_definition.type), null: true, connection: type_name.end_with?("Connection"), connection_extension: nil, deprecation_reason: build_deprecation_reason(field_definition.directives), ast_node: field_definition, method_conflict_warning: false, camelize: false, resolver_method: resolve_method_name, ) builder.build_arguments(schema_field_defn, field_definition.arguments, type_resolver) # Don't do this for interfaces if default_resolve owner.class_eval <<-RUBY, __FILE__, __LINE__ # frozen_string_literal: true def #{resolve_method_name}(**args) field_instance = self.class.get_field("#{field_definition.name}") context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context) end RUBY end end end def resolve_type(types, ast_node) case ast_node when GraphQL::Language::Nodes::TypeName type_name = ast_node.name types[type_name] ||= GraphQL::Schema::LateBoundType.new(type_name) when GraphQL::Language::Nodes::NonNullType resolve_type(types, ast_node.of_type).to_non_null_type when GraphQL::Language::Nodes::ListType resolve_type(types, ast_node.of_type).to_list_type else raise "Unexpected ast_node: #{ast_node.inspect}" end end def resolve_type_name(type) case type when GraphQL::Language::Nodes::TypeName return type.name else resolve_type_name(type.of_type) end end end private_constant :Builder end end end graphql-ruby-1.11.10/lib/graphql/schema/build_from_definition/000077500000000000000000000000001414121453000242615ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/schema/build_from_definition/resolve_map.rb000066400000000000000000000054151414121453000271270ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/schema/build_from_definition/resolve_map/default_resolve" module GraphQL class Schema module BuildFromDefinition # Wrap a user-provided hash of resolution behavior for easy access at runtime. # # Coerce scalar values by: # - Checking for a function in the map like `{ Date: { coerce_input: ->(val, ctx) { ... }, coerce_result: ->(val, ctx) { ... } } }` # - Falling back to a passthrough # # Interface/union resolution can be provided as a `resolve_type:` key. # # @api private class ResolveMap module NullScalarCoerce def self.call(val, _ctx) val end end def initialize(user_resolve_hash) @resolve_hash = Hash.new do |h, k| # For each type name, provide a new hash if one wasn't given: h[k] = Hash.new do |h2, k2| if k2 == "coerce_input" || k2 == "coerce_result" # This isn't an object field, it's a scalar coerce function. # Use a passthrough NullScalarCoerce else # For each field, provide a resolver that will # make runtime checks & replace itself h2[k2] = DefaultResolve.new(h2, k2) end end end @user_resolve_hash = user_resolve_hash # User-provided resolve functions take priority over the default: @user_resolve_hash.each do |type_name, fields| type_name_s = type_name.to_s case fields when Hash fields.each do |field_name, resolve_fn| @resolve_hash[type_name_s][field_name.to_s] = resolve_fn end when Proc # for example, "resolve_type" @resolve_hash[type_name_s] = fields else raise ArgumentError, "Unexpected resolve hash value for #{type_name.inspect}: #{fields.inspect} (#{fields.class})" end end # Check the normalized hash, not the user input: if @resolve_hash.key?("resolve_type") define_singleton_method :resolve_type do |type, obj, ctx| @resolve_hash.fetch("resolve_type").call(type, obj, ctx) end end end def call(type, field, obj, args, ctx) resolver = @resolve_hash[type.graphql_name][field.graphql_name] resolver.call(obj, args, ctx) end def coerce_input(type, value, ctx) @resolve_hash[type.graphql_name]["coerce_input"].call(value, ctx) end def coerce_result(type, value, ctx) @resolve_hash[type.graphql_name]["coerce_result"].call(value, ctx) end end end end end graphql-ruby-1.11.10/lib/graphql/schema/build_from_definition/resolve_map/000077500000000000000000000000001414121453000265755ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb000066400000000000000000000032361414121453000323110ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema module BuildFromDefinition class ResolveMap class DefaultResolve def initialize(field_map, field_name) @field_map = field_map @field_name = field_name end # Make some runtime checks about # how `obj` implements the `field_name`. # # Create a new resolve function according to that implementation, then: # - update `field_map` with this implementation # - call the implementation now (to satisfy this field execution) # # If `obj` doesn't implement `field_name`, raise an error. def call(obj, args, ctx) method_name = @field_name if !obj.respond_to?(method_name) raise KeyError, "Can't resolve field #{method_name} on #{obj.inspect}" else method_arity = obj.method(method_name).arity resolver = case method_arity when 0, -1 # -1 Handles method_missing, eg openstruct ->(o, a, c) { o.public_send(method_name) } when 1 ->(o, a, c) { o.public_send(method_name, a) } when 2 ->(o, a, c) { o.public_send(method_name, a, c) } else raise "Unexpected resolve arity: #{method_arity}. Must be 0, 1, 2" end # Call the resolver directly next time @field_map[method_name] = resolver # Call through this time resolver.call(obj, args, ctx) end end end end end end end graphql-ruby-1.11.10/lib/graphql/schema/built_in_types.rb000066400000000000000000000004471414121453000233120ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema BUILT_IN_TYPES = { "Int" => GraphQL::Types::Int, "String" => GraphQL::Types::String, "Float" => GraphQL::Types::Float, "Boolean" => GraphQL::Types::Boolean, "ID" => GraphQL::Types::ID, } end end graphql-ruby-1.11.10/lib/graphql/schema/catchall_middleware.rb000066400000000000000000000025251414121453000242300ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # In early GraphQL versions, errors would be "automatically" # rescued and replaced with `"Internal error"`. That behavior # was undesirable but this middleware is offered for people who # want to preserve it. # # It has a couple of differences from the previous behavior: # # - Other parts of the query _will_ be run (previously, # execution would stop when the error was raised and the result # would have no `"data"` key at all) # - The entry in {Query::Context#errors} is a {GraphQL::ExecutionError}, _not_ # the originally-raised error. # - The entry in the `"errors"` key includes the location of the field # which raised the errors. # # @example Use CatchallMiddleware with your schema # # All errors will be suppressed and replaced with "Internal error" messages # MySchema.middleware << GraphQL::Schema::CatchallMiddleware # module CatchallMiddleware MESSAGE = "Internal error" # Rescue any error and replace it with a {GraphQL::ExecutionError} # whose message is {MESSAGE} def self.call(parent_type, parent_object, field_definition, field_args, query_context) yield rescue StandardError GraphQL::ExecutionError.new(MESSAGE) end end end end graphql-ruby-1.11.10/lib/graphql/schema/default_parse_error.rb000066400000000000000000000003001414121453000242740ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema module DefaultParseError def self.call(parse_error, ctx) ctx.errors.push(parse_error) end end end end graphql-ruby-1.11.10/lib/graphql/schema/default_type_error.rb000066400000000000000000000007021414121453000241510ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema module DefaultTypeError def self.call(type_error, ctx) case type_error when GraphQL::InvalidNullError ctx.errors << type_error when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError raise type_error when GraphQL::IntegerDecodingError nil end end end end end graphql-ruby-1.11.10/lib/graphql/schema/directive.rb000066400000000000000000000124631414121453000222400ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Subclasses of this can influence how {GraphQL::Execution::Interpreter} runs queries. # # - {.include?}: if it returns `false`, the field or fragment will be skipped altogether, as if it were absent # - {.resolve}: Wraps field resolution (so it should call `yield` to continue) class Directive < GraphQL::Schema::Member extend GraphQL::Schema::Member::HasArguments class << self # Return a name based on the class name, # but downcase the first letter. def default_graphql_name @default_graphql_name ||= begin camelized_name = super camelized_name[0] = camelized_name[0].downcase camelized_name end end def locations(*new_locations) if new_locations.any? @locations = new_locations else @locations ||= (superclass.respond_to?(:locations) ? superclass.locations : []) end end def default_directive(new_default_directive = nil) if new_default_directive != nil @default_directive = new_default_directive elsif @default_directive.nil? @default_directive = (superclass.respond_to?(:default_directive) ? superclass.default_directive : false) else !!@default_directive end end def default_directive? default_directive end def to_graphql defn = GraphQL::Directive.new defn.name = self.graphql_name defn.description = self.description defn.locations = self.locations defn.default_directive = self.default_directive defn.ast_node = ast_node defn.metadata[:type_class] = self arguments.each do |name, arg_defn| arg_graphql = arg_defn.to_graphql defn.arguments[arg_graphql.name] = arg_graphql end # Make a reference to a classic-style Arguments class defn.arguments_class = GraphQL::Query::Arguments.construct_arguments_class(defn) defn end # If false, this part of the query won't be evaluated def include?(_object, arguments, context) static_include?(arguments, context) end # Determines whether {Execution::Lookahead} considers the field to be selected def static_include?(_arguments, _context) true end # Continuing is passed as a block; `yield` to continue def resolve(object, arguments, context) yield end def on_field? locations.include?(FIELD) end def on_fragment? locations.include?(FRAGMENT_SPREAD) && locations.include?(INLINE_FRAGMENT) end def on_operation? locations.include?(QUERY) && locations.include?(MUTATION) && locations.include?(SUBSCRIPTION) end end LOCATIONS = [ QUERY = :QUERY, MUTATION = :MUTATION, SUBSCRIPTION = :SUBSCRIPTION, FIELD = :FIELD, FRAGMENT_DEFINITION = :FRAGMENT_DEFINITION, FRAGMENT_SPREAD = :FRAGMENT_SPREAD, INLINE_FRAGMENT = :INLINE_FRAGMENT, SCHEMA = :SCHEMA, SCALAR = :SCALAR, OBJECT = :OBJECT, FIELD_DEFINITION = :FIELD_DEFINITION, ARGUMENT_DEFINITION = :ARGUMENT_DEFINITION, INTERFACE = :INTERFACE, UNION = :UNION, ENUM = :ENUM, ENUM_VALUE = :ENUM_VALUE, INPUT_OBJECT = :INPUT_OBJECT, INPUT_FIELD_DEFINITION = :INPUT_FIELD_DEFINITION, ] DEFAULT_DEPRECATION_REASON = 'No longer supported' LOCATION_DESCRIPTIONS = { QUERY: 'Location adjacent to a query operation.', MUTATION: 'Location adjacent to a mutation operation.', SUBSCRIPTION: 'Location adjacent to a subscription operation.', FIELD: 'Location adjacent to a field.', FRAGMENT_DEFINITION: 'Location adjacent to a fragment definition.', FRAGMENT_SPREAD: 'Location adjacent to a fragment spread.', INLINE_FRAGMENT: 'Location adjacent to an inline fragment.', SCHEMA: 'Location adjacent to a schema definition.', SCALAR: 'Location adjacent to a scalar definition.', OBJECT: 'Location adjacent to an object type definition.', FIELD_DEFINITION: 'Location adjacent to a field definition.', ARGUMENT_DEFINITION: 'Location adjacent to an argument definition.', INTERFACE: 'Location adjacent to an interface definition.', UNION: 'Location adjacent to a union definition.', ENUM: 'Location adjacent to an enum definition.', ENUM_VALUE: 'Location adjacent to an enum value definition.', INPUT_OBJECT: 'Location adjacent to an input object type definition.', INPUT_FIELD_DEFINITION: 'Location adjacent to an input object field definition.', } end end end graphql-ruby-1.11.10/lib/graphql/schema/directive/000077500000000000000000000000001414121453000217055ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/schema/directive/deprecated.rb000066400000000000000000000015561414121453000243410ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member class Deprecated < GraphQL::Schema::Directive description "Marks an element of a GraphQL schema as no longer supported." locations(GraphQL::Schema::Directive::FIELD_DEFINITION, GraphQL::Schema::Directive::ENUM_VALUE, GraphQL::Schema::Directive::ARGUMENT_DEFINITION, GraphQL::Schema::Directive::INPUT_FIELD_DEFINITION) reason_description = "Explains why this element was deprecated, usually also including a "\ "suggestion for how to access supported similar data. Formatted "\ "in [Markdown](https://daringfireball.net/projects/markdown/)." argument :reason, String, reason_description, default_value: Directive::DEFAULT_DEPRECATION_REASON, required: false default_directive true end end end end graphql-ruby-1.11.10/lib/graphql/schema/directive/feature.rb000066400000000000000000000054261414121453000236740ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member # An example directive to show how you might interact with the runtime. # # This directive might be used along with a server-side feature flag system like Flipper. # # With that system, you could use this directive to exclude parts of a query # if the current viewer doesn't have certain flags enabled. # (So, this flag would be for internal clients, like your iOS app, not third-party API clients.) # # To use it, you have to implement `.enabled?`, for example: # # @example Implementing the Feature directive # # app/graphql/directives/feature.rb # class Directives::Feature < GraphQL::Schema::Directive::Feature # def self.enabled?(flag_name, _obj, context) # # Translate some GraphQL data for Ruby: # flag_key = flag_name.underscore # current_user = context[:viewer] # # Check the feature flag however your app does it: # MyFeatureFlags.enabled?(current_user, flag_key) # end # end # # @example Flagging a part of the query # viewer { # # This field only runs if `.enabled?("recommendationEngine", obj, context)` # # returns true. Otherwise, it's treated as if it didn't exist. # recommendations @feature(flag: "recommendationEngine") { # name # rating # } # } class Feature < Schema::Directive description "Directs the executor to run this only if a certain server-side feature is enabled." locations( GraphQL::Schema::Directive::FIELD, GraphQL::Schema::Directive::FRAGMENT_SPREAD, GraphQL::Schema::Directive::INLINE_FRAGMENT ) argument :flag, String, required: true, description: "The name of the feature to check before continuing" # Implement the Directive API def self.include?(object, arguments, context) flag_name = arguments[:flag] self.enabled?(flag_name, object, context) end # Override this method in your app's subclass of this directive. # # @param flag_name [String] The client-provided string of a feature to check # @param object [GraphQL::Schema::Objct] The currently-evaluated GraphQL object instance # @param context [GraphQL::Query::Context] # @return [Boolean] If truthy, execution will continue def self.enabled?(flag_name, object, context) raise GraphQL::RequiredImplementationMissingError, "Implement `.enabled?(flag_name, object, context)` to return true or false for the feature flag (#{flag_name.inspect})" end end end end end graphql-ruby-1.11.10/lib/graphql/schema/directive/include.rb000066400000000000000000000012551414121453000236600ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member class Include < GraphQL::Schema::Directive description "Directs the executor to include this field or fragment only when the `if` argument is true." locations( GraphQL::Schema::Directive::FIELD, GraphQL::Schema::Directive::FRAGMENT_SPREAD, GraphQL::Schema::Directive::INLINE_FRAGMENT ) argument :if, Boolean, required: true, description: "Included when true." default_directive true def self.static_include?(args, ctx) !!args[:if] end end end end end graphql-ruby-1.11.10/lib/graphql/schema/directive/skip.rb000066400000000000000000000012271414121453000232020ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member class Skip < Schema::Directive description "Directs the executor to skip this field or fragment when the `if` argument is true." locations( GraphQL::Schema::Directive::FIELD, GraphQL::Schema::Directive::FRAGMENT_SPREAD, GraphQL::Schema::Directive::INLINE_FRAGMENT ) argument :if, Boolean, required: true, description: "Skipped when true." default_directive true def self.static_include?(args, ctx) !args[:if] end end end end end graphql-ruby-1.11.10/lib/graphql/schema/directive/transform.rb000066400000000000000000000031631414121453000242500ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member # An example directive to show how you might interact with the runtime. # # This directive takes the return value of the tagged part of the query, # and if the named transform is whitelisted and applies to the return value, # it's applied by calling a method with that name. # # @example Installing the directive # class MySchema < GraphQL::Schema # directive(GraphQL::Schema::Directive::Transform) # end # # @example Transforming strings # viewer { # username @transform(by: "upcase") # } class Transform < Schema::Directive description "Directs the executor to run named transform on the return value." locations( GraphQL::Schema::Directive::FIELD, ) argument :by, String, required: true, description: "The name of the transform to run if applicable" TRANSFORMS = [ "upcase", "downcase", # ?? ] # Implement the Directive API def self.resolve(object, arguments, context) path = context.namespace(:interpreter)[:current_path] return_value = yield transform_name = arguments[:by] if TRANSFORMS.include?(transform_name) && return_value.respond_to?(transform_name) return_value = return_value.public_send(transform_name) context.namespace(:interpreter)[:runtime].write_in_response(path, return_value) end end end end end end graphql-ruby-1.11.10/lib/graphql/schema/enum.rb000066400000000000000000000107751414121453000212320ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Extend this class to define GraphQL enums in your schema. # # By default, GraphQL enum values are translated into Ruby strings. # You can provide a custom value with the `value:` keyword. # # @example # # equivalent to # # enum PizzaTopping { # # MUSHROOMS # # ONIONS # # PEPPERS # # } # class PizzaTopping < GraphQL::Enum # value :MUSHROOMS # value :ONIONS # value :PEPPERS # end class Schema class Enum < GraphQL::Schema::Member extend GraphQL::Schema::Member::AcceptsDefinition extend GraphQL::Schema::Member::ValidatesInput class UnresolvedValueError < GraphQL::EnumType::UnresolvedValueError end class << self # Define a value for this enum # @param graphql_name [String, Symbol] the GraphQL value for this, usually `SCREAMING_CASE` # @param description [String], the GraphQL description for this value, present in documentation # @param value [Object], the translated Ruby value for this object (defaults to `graphql_name`) # @param deprecation_reason [String] if this object is deprecated, include a message here # @return [void] # @see {Schema::EnumValue} which handles these inputs by default def value(*args, **kwargs, &block) kwargs[:owner] = self value = enum_value_class.new(*args, **kwargs, &block) own_values[value.graphql_name] = value nil end # @return [Hash GraphQL::Schema::Enum::Value>] Possible values of this enum, keyed by name def values inherited_values = superclass <= GraphQL::Schema::Enum ? superclass.values : {} # Local values take precedence over inherited ones inherited_values.merge(own_values) end # @return [GraphQL::EnumType] def to_graphql enum_type = GraphQL::EnumType.new enum_type.name = graphql_name enum_type.description = description enum_type.introspection = introspection enum_type.ast_node = ast_node values.each do |name, val| enum_type.add_value(val.to_graphql) end enum_type.metadata[:type_class] = self enum_type end # @return [Class] for handling `value(...)` inputs and building `GraphQL::Enum::EnumValue`s out of them def enum_value_class(new_enum_value_class = nil) if new_enum_value_class @enum_value_class = new_enum_value_class elsif defined?(@enum_value_class) && @enum_value_class @enum_value_class else superclass <= GraphQL::Schema::Enum ? superclass.enum_value_class : nil end end def kind GraphQL::TypeKinds::ENUM end def validate_non_null_input(value_name, ctx) result = GraphQL::Query::InputValidationResult.new allowed_values = ctx.warden.enum_values(self) matching_value = allowed_values.find { |v| v.graphql_name == value_name } if matching_value.nil? result.add_problem("Expected #{GraphQL::Language.serialize(value_name)} to be one of: #{allowed_values.map(&:graphql_name).join(', ')}") end result end def coerce_result(value, ctx) warden = ctx.warden all_values = warden ? warden.enum_values(self) : values.each_value enum_value = all_values.find { |val| val.value == value } if enum_value enum_value.graphql_name else raise(self::UnresolvedValueError, "Can't resolve enum #{graphql_name} for #{value.inspect}") end end def coerce_input(value_name, ctx) all_values = ctx.warden ? ctx.warden.enum_values(self) : values.each_value if v = all_values.find { |val| val.graphql_name == value_name } v.value elsif v = all_values.find { |val| val.value == value_name } # this is for matching default values, which are "inputs", but they're # the Ruby value, not the GraphQL string. v.value else nil end end def inherited(child_class) child_class.const_set(:UnresolvedValueError, Class.new(Schema::Enum::UnresolvedValueError)) super end private def own_values @own_values ||= {} end end enum_value_class(GraphQL::Schema::EnumValue) end end end graphql-ruby-1.11.10/lib/graphql/schema/enum_value.rb000066400000000000000000000051711414121453000224200ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # A possible value for an {Enum}. # # You can extend this class to customize enum values in your schema. # # @example custom enum value class # # define a custom class: # class CustomEnumValue < GraphQL::Schema::EnumValue # def initialize(*args) # # arguments to `value(...)` in Enum classes are passed here # super # end # # def to_graphql # enum_value = super # # customize the derived GraphQL::EnumValue here # enum_value # end # end # # class BaseEnum < GraphQL::Schema::Enum # # use it for these enums: # enum_value_class CustomEnumValue # end class EnumValue < GraphQL::Schema::Member include GraphQL::Schema::Member::CachedGraphQLDefinition include GraphQL::Schema::Member::AcceptsDefinition include GraphQL::Schema::Member::HasPath include GraphQL::Schema::Member::HasAstNode attr_reader :graphql_name # @return [Class] The enum type that owns this value attr_reader :owner # @return [String] Explains why this value was deprecated (if present, this will be marked deprecated in introspection) attr_accessor :deprecation_reason def initialize(graphql_name, desc = nil, owner:, ast_node: nil, description: nil, value: nil, deprecation_reason: nil, &block) @graphql_name = graphql_name.to_s GraphQL::NameValidator.validate!(@graphql_name) @description = desc || description @value = value.nil? ? @graphql_name : value @deprecation_reason = deprecation_reason @owner = owner @ast_node = ast_node if block_given? instance_eval(&block) end end def description(new_desc = nil) if new_desc @description = new_desc end @description end def value(new_val = nil) unless new_val.nil? @value = new_val end @value end # @return [GraphQL::EnumType::EnumValue] A runtime-ready object derived from this object def to_graphql enum_value = GraphQL::EnumType::EnumValue.new enum_value.name = @graphql_name enum_value.description = @description enum_value.value = @value enum_value.deprecation_reason = @deprecation_reason enum_value.metadata[:type_class] = self enum_value.ast_node = ast_node enum_value end def visible?(_ctx); true; end def accessible?(_ctx); true; end def authorized?(_ctx); true; end end end end graphql-ruby-1.11.10/lib/graphql/schema/field.rb000066400000000000000000000755351414121453000213560ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../object.rb require "graphql/schema/field/connection_extension" require "graphql/schema/field/scope_extension" module GraphQL class Schema class Field if !String.method_defined?(:-@) using GraphQL::StringDedupBackport end include GraphQL::Schema::Member::CachedGraphQLDefinition include GraphQL::Schema::Member::AcceptsDefinition include GraphQL::Schema::Member::HasArguments include GraphQL::Schema::Member::HasAstNode include GraphQL::Schema::Member::HasPath extend GraphQL::Schema::FindInheritedValue include GraphQL::Schema::FindInheritedValue::EmptyObjects # @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided attr_reader :name alias :graphql_name :name attr_writer :description # @return [String, nil] If present, the field is marked as deprecated with this documentation attr_accessor :deprecation_reason # @return [Symbol] Method or hash key on the underlying object to look up attr_reader :method_sym # @return [String] Method or hash key on the underlying object to look up attr_reader :method_str # @return [Symbol] The method on the type to look up attr_reader :resolver_method # @return [Class] The thing this field was defined on (type, mutation, resolver) attr_accessor :owner # @return [Class] The GraphQL type this field belongs to. (For fields defined on mutations, it's the payload type) def owner_type @owner_type ||= if owner < GraphQL::Schema::Mutation owner.payload_type else owner end end # @return [Symbol] the original name of the field, passed in by the user attr_reader :original_name # @return [Class, nil] The {Schema::Resolver} this field was derived from, if there is one def resolver @resolver_class end # @return [Boolean] Is this field a predefined introspection field? def introspection? @introspection end alias :mutation :resolver # @return [Boolean] Apply tracing to this field? (Default: skip scalars, this is the override value) attr_reader :trace # @return [String, nil] attr_accessor :subscription_scope # Create a field instance from a list of arguments, keyword arguments, and a block. # # This method implements prioritization between the `resolver` or `mutation` defaults # and the local overrides via other keywords. # # It also normalizes positional arguments into keywords for {Schema::Field#initialize}. # @param resolver [Class] A {GraphQL::Schema::Resolver} class to use for field configuration # @param mutation [Class] A {GraphQL::Schema::Mutation} class to use for field configuration # @param subscription [Class] A {GraphQL::Schema::Subscription} class to use for field configuration # @return [GraphQL::Schema:Field] an instance of `self # @see {.initialize} for other options def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block) if kwargs[:field] if kwargs[:field] == GraphQL::Relay::Node.field warn("Legacy-style `GraphQL::Relay::Node.field` is being added to a class-based type. See `GraphQL::Types::Relay::NodeField` for a replacement.") return GraphQL::Types::Relay::NodeField elsif kwargs[:field] == GraphQL::Relay::Node.plural_field warn("Legacy-style `GraphQL::Relay::Node.plural_field` is being added to a class-based type. See `GraphQL::Types::Relay::NodesField` for a replacement.") return GraphQL::Types::Relay::NodesField end end if (parent_config = resolver || mutation || subscription) # Get the parent config, merge in local overrides kwargs = parent_config.field_options.merge(kwargs) # Add a reference to that parent class kwargs[:resolver_class] = parent_config end if name kwargs[:name] = name end if !type.nil? if type.is_a?(GraphQL::Field) raise ArgumentError, "A GraphQL::Field was passed as the second argument, use the `field:` keyword for this instead." end if desc if kwargs[:description] raise ArgumentError, "Provide description as a positional argument or `description:` keyword, but not both (#{desc.inspect}, #{kwargs[:description].inspect})" end kwargs[:description] = desc kwargs[:type] = type elsif (kwargs[:field] || kwargs[:function] || resolver || mutation) && type.is_a?(String) # The return type should be copied from `field` or `function`, and the second positional argument is the description kwargs[:description] = type else kwargs[:type] = type end end new(**kwargs, &block) end # Can be set with `connection: true|false` or inferred from a type name ending in `*Connection` # @return [Boolean] if true, this field will be wrapped with Relay connection behavior def connection? if @connection.nil? # Provide default based on type name return_type_name = if (contains_type = @field || @function) Member::BuildType.to_type_name(contains_type.type) elsif @return_type_expr Member::BuildType.to_type_name(@return_type_expr) else # As a last ditch, try to force loading the return type: type.unwrap.name end @connection = return_type_name.end_with?("Connection") else @connection end end # @return [Boolean] if true, the return type's `.scope_items` method will be applied to this field's return value def scoped? if !@scope.nil? # The default was overridden @scope else @return_type_expr && (@return_type_expr.is_a?(Array) || (@return_type_expr.is_a?(String) && @return_type_expr.include?("[")) || connection?) end end # This extension is applied to fields when {#connection?} is true. # # You can override it in your base field definition. # @return [Class] A {FieldExtension} subclass for implementing pagination behavior. # @example Configuring a custom extension # class Types::BaseField < GraphQL::Schema::Field # connection_extension(MyCustomExtension) # end def self.connection_extension(new_extension_class = nil) if new_extension_class @connection_extension = new_extension_class else @connection_extension ||= find_inherited_value(:connection_extension, ConnectionExtension) end end # @return Boolean attr_reader :relay_node_field # @return [Boolean] Should we warn if this field's name conflicts with a built-in method? def method_conflict_warning? @method_conflict_warning end # @param name [Symbol] The underscore-cased version of this field name (will be camelized for the GraphQL API) # @param type [Class, GraphQL::BaseType, Array] The return type of this field # @param owner [Class] The type that this field belongs to # @param null [Boolean] `true` if this field may return `null`, `false` if it is never `null` # @param description [String] Field description # @param deprecation_reason [String] If present, the field is marked "deprecated" with this message # @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`) # @param hash_key [String, Symbol] The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`) # @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`) # @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name # @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added. # @param max_page_size [Integer, nil] For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results. # @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__` # @param resolve [<#call(obj, args, ctx)>] **deprecated** for compatibility with <1.8.0 # @param field [GraphQL::Field, GraphQL::Schema::Field] **deprecated** for compatibility with <1.8.0 # @param function [GraphQL::Function] **deprecated** for compatibility with <1.8.0 # @param resolver_class [Class] (Private) A {Schema::Resolver} which this field was derived from. Use `resolver:` to create a field with a resolver. # @param arguments [{String=>GraphQL::Schema::Argument, Hash}] Arguments for this field (may be added in the block, also) # @param camelize [Boolean] If true, the field name will be camelized when building the schema # @param complexity [Numeric] When provided, set the complexity for this field # @param scope [Boolean] If true, the return type's `.scope_items` method will be called on the return value # @param subscription_scope [Symbol, String] A key in `context` which will be used to scope subscription payloads # @param extensions [Array Object>>] Named extensions to apply to this field (see also {#extension}) # @param trace [Boolean] If true, a {GraphQL::Tracing} tracer will measure this scalar field # @param broadcastable [Boolean] Whether or not this field can be distributed in subscription broadcasts # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, &definition_block) if name.nil? raise ArgumentError, "missing first `name` argument or keyword `name:`" end if !(field || function || resolver_class) if type.nil? raise ArgumentError, "missing second `type` argument or keyword `type:`" end if null.nil? raise ArgumentError, "missing keyword argument null:" end end if (field || function || resolve) && extras.any? raise ArgumentError, "keyword `extras:` may only be used with method-based resolve and class-based field such as mutation class, please remove `field:`, `function:` or `resolve:`" end @original_name = name name_s = -name.to_s @underscored_name = -Member::BuildType.underscore(name_s) @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s) @description = description if field.is_a?(GraphQL::Schema::Field) raise ArgumentError, "Instead of passing a field as `field:`, use `add_field(field)` to add an already-defined field." else @field = field end @function = function @resolve = resolve @deprecation_reason = deprecation_reason if method && hash_key raise ArgumentError, "Provide `method:` _or_ `hash_key:`, not both. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}`)" end if resolver_method if method raise ArgumentError, "Provide `method:` _or_ `resolver_method:`, not both. (called with: `method: #{method.inspect}, resolver_method: #{resolver_method.inspect}`)" end if hash_key raise ArgumentError, "Provide `hash_key:` _or_ `resolver_method:`, not both. (called with: `hash_key: #{hash_key.inspect}, resolver_method: #{resolver_method.inspect}`)" end end # TODO: I think non-string/symbol hash keys are wrongly normalized (eg `1` will not work) method_name = method || hash_key || name_s resolver_method ||= name_s.to_sym @method_str = -method_name.to_s @method_sym = method_name.to_sym @resolver_method = resolver_method @complexity = complexity @return_type_expr = type @return_type_null = null @connection = connection @has_max_page_size = max_page_size != :not_given @max_page_size = max_page_size == :not_given ? nil : max_page_size @introspection = introspection @extras = extras @broadcastable = broadcastable @resolver_class = resolver_class @scope = scope @trace = trace @relay_node_field = relay_node_field @relay_nodes_field = relay_nodes_field @ast_node = ast_node @method_conflict_warning = method_conflict_warning arguments.each do |name, arg| if arg.is_a?(Hash) argument(name: name, **arg) else add_argument(arg) end end @owner = owner @subscription_scope = subscription_scope # Do this last so we have as much context as possible when initializing them: @extensions = EMPTY_ARRAY if extensions.any? self.extensions(extensions) end # This should run before connection extension, # but should it run after the definition block? if scoped? self.extension(ScopeExtension) end # The problem with putting this after the definition_block # is that it would override arguments if connection? && connection_extension self.extension(connection_extension) end if definition_block if definition_block.arity == 1 yield self else instance_eval(&definition_block) end end end # If true, subscription updates with this field can be shared between viewers # @return [Boolean, nil] # @see GraphQL::Subscriptions::BroadcastAnalyzer def broadcastable? @broadcastable end # @param text [String] # @return [String] def description(text = nil) if text @description = text else @description end end # Read extension instances from this field, # or add new classes/options to be initialized on this field. # Extensions are executed in the order they are added. # # @example adding an extension # extensions([MyExtensionClass]) # # @example adding multiple extensions # extensions([MyExtensionClass, AnotherExtensionClass]) # # @example adding an extension with options # extensions([MyExtensionClass, { AnotherExtensionClass => { filter: true } }]) # # @param extensions [Array Object>>] Add extensions to this field. For hash elements, only the first key/value is used. # @return [Array] extensions to apply to this field def extensions(new_extensions = nil) if new_extensions.nil? # Read the value @extensions else if @extensions.frozen? @extensions = @extensions.dup end new_extensions.each do |extension| if extension.is_a?(Hash) extension = extension.to_a[0] extension_class, options = *extension @extensions << extension_class.new(field: self, options: options) else extension_class = extension @extensions << extension_class.new(field: self, options: nil) end end end end # Add `extension` to this field, initialized with `options` if provided. # # @example adding an extension # extension(MyExtensionClass) # # @example adding an extension with options # extension(MyExtensionClass, filter: true) # # @param extension [Class] subclass of {Schema::Fieldextension} # @param options [Object] if provided, given as `options:` when initializing `extension`. def extension(extension, options = nil) extensions([{extension => options}]) end # Read extras (as symbols) from this field, # or add new extras to be opted into by this field's resolver. # # @param new_extras [Array] Add extras to this field # @return [Array] def extras(new_extras = nil) if new_extras.nil? # Read the value @extras else if @extras.frozen? @extras = @extras.dup end # Append to the set of extras on this field @extras.concat(new_extras) end end def complexity(new_complexity = nil) case new_complexity when Proc if new_complexity.parameters.size != 3 fail( "A complexity proc should always accept 3 parameters: ctx, args, child_complexity. "\ "E.g.: complexity ->(ctx, args, child_complexity) { child_complexity * args[:limit] }" ) else @complexity = new_complexity end when Numeric @complexity = new_complexity when nil @complexity else raise("Invalid complexity: #{new_complexity.inspect} on #{@name}") end end # @return [Boolean] True if this field's {#max_page_size} should override the schema default. def has_max_page_size? @has_max_page_size end # @return [Integer, nil] Applied to connections if {#has_max_page_size?} attr_reader :max_page_size # @return [GraphQL::Field] def to_graphql field_defn = if @field @field.dup elsif @function GraphQL::Function.build_field(@function) else GraphQL::Field.new end field_defn.name = @name if @return_type_expr field_defn.type = -> { type } end if @description field_defn.description = @description end if @deprecation_reason field_defn.deprecation_reason = @deprecation_reason end if @resolver_class if @resolver_class < GraphQL::Schema::Mutation field_defn.mutation = @resolver_class end field_defn.metadata[:resolver] = @resolver_class end if !@trace.nil? field_defn.trace = @trace end if @relay_node_field field_defn.relay_node_field = @relay_node_field end if @relay_nodes_field field_defn.relay_nodes_field = @relay_nodes_field end field_defn.resolve = self.method(:resolve_field) field_defn.connection = connection? field_defn.connection_max_page_size = max_page_size field_defn.introspection = @introspection field_defn.complexity = @complexity field_defn.subscription_scope = @subscription_scope field_defn.ast_node = ast_node arguments.each do |name, defn| arg_graphql = defn.to_graphql field_defn.arguments[arg_graphql.name] = arg_graphql end # Support a passed-in proc, one way or another @resolve_proc = if @resolve @resolve elsif @function @function elsif @field @field.resolve_proc end # Ok, `self` isn't a class, but this is for consistency with the classes field_defn.metadata[:type_class] = self field_defn.arguments_class = GraphQL::Query::Arguments.construct_arguments_class(field_defn) field_defn end attr_writer :type def type @type ||= if @function Member::BuildType.parse_type(@function.type, null: false) elsif @field Member::BuildType.parse_type(@field.type, null: false) else Member::BuildType.parse_type(@return_type_expr, null: @return_type_null) end rescue GraphQL::Schema::InvalidDocumentError => err # Let this propagate up raise err rescue StandardError => err raise ArgumentError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: (#{err.class}) #{err.message}", err.backtrace end def visible?(context) if @resolver_class @resolver_class.visible?(context) else true end end def accessible?(context) if @resolver_class @resolver_class.accessible?(context) else true end end def authorized?(object, args, context) if @resolver_class # The resolver will check itself during `resolve()` @resolver_class.authorized?(object, context) else # Faster than `.any?` arguments.each_value do |arg| if args.key?(arg.keyword) && !arg.authorized?(object, args[arg.keyword], context) return false end end true end end # Implement {GraphQL::Field}'s resolve API. # # Eventually, we might hook up field instances to execution in another way. TBD. # @see #resolve for how the interpreter hooks up to it def resolve_field(obj, args, ctx) ctx.schema.after_lazy(obj) do |after_obj| # First, apply auth ... query_ctx = ctx.query.context # Some legacy fields can have `nil` here, not exactly sure why. # @see https://github.com/rmosolgo/graphql-ruby/issues/1990 before removing inner_obj = after_obj && after_obj.object ctx.schema.after_lazy(to_ruby_args(after_obj, args, ctx)) do |ruby_args| if authorized?(inner_obj, ruby_args, query_ctx) # Then if it passed, resolve the field if @resolve_proc # Might be nil, still want to call the func in that case with_extensions(inner_obj, ruby_args, query_ctx) do |extended_obj, extended_args| # Pass the GraphQL args here for compatibility: @resolve_proc.call(extended_obj, args, ctx) end else public_send_field(after_obj, ruby_args, query_ctx) end else err = GraphQL::UnauthorizedFieldError.new(object: inner_obj, type: obj.class, context: ctx, field: self) query_ctx.schema.unauthorized_field(err) end end end end # This method is called by the interpreter for each field. # You can extend it in your base field classes. # @param object [GraphQL::Schema::Object] An instance of some type class, wrapping an application object # @param args [Hash] A symbol-keyed hash of Ruby keyword arguments. (Empty if no args) # @param ctx [GraphQL::Query::Context] def resolve(object, args, ctx) if @resolve_proc raise "Can't run resolve proc for #{path} when using GraphQL::Execution::Interpreter" end begin # Unwrap the GraphQL object to get the application object. application_object = object.object ctx.schema.after_lazy(self.authorized?(application_object, args, ctx)) do |is_authorized| if is_authorized public_send_field(object, args, ctx) else err = GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self) ctx.schema.unauthorized_field(err) end end rescue GraphQL::UnauthorizedFieldError => err err.field ||= self ctx.schema.unauthorized_field(err) rescue GraphQL::UnauthorizedError => err ctx.schema.unauthorized_object(err) end rescue GraphQL::ExecutionError => err err end # @param ctx [GraphQL::Query::Context::FieldResolutionContext] def fetch_extra(extra_name, ctx) if extra_name != :path && extra_name != :ast_node && respond_to?(extra_name) self.public_send(extra_name) elsif ctx.respond_to?(extra_name) ctx.public_send(extra_name) else raise GraphQL::RequiredImplementationMissingError, "Unknown field extra for #{self.path}: #{extra_name.inspect}" end end private NO_ARGS = {}.freeze # Convert a GraphQL arguments instance into a Ruby-style hash. # # @param obj [GraphQL::Schema::Object] The object where this field is being resolved # @param graphql_args [GraphQL::Query::Arguments] # @param field_ctx [GraphQL::Query::Context::FieldResolutionContext] # @return [Hash Any>] def to_ruby_args(obj, graphql_args, field_ctx) if graphql_args.any? || @extras.any? # Splat the GraphQL::Arguments to Ruby keyword arguments ruby_kwargs = graphql_args.to_kwargs maybe_lazies = [] # Apply any `prepare` methods. Not great code organization, can this go somewhere better? arguments.each do |name, arg_defn| ruby_kwargs_key = arg_defn.keyword if ruby_kwargs.key?(ruby_kwargs_key) loads = arg_defn.loads value = ruby_kwargs[ruby_kwargs_key] loaded_value = if loads && !arg_defn.from_resolver? if arg_defn.type.list? loaded_values = value.map { |val| load_application_object(arg_defn, loads, val, field_ctx.query.context) } field_ctx.schema.after_any_lazies(loaded_values) { |result| result } else load_application_object(arg_defn, loads, value, field_ctx.query.context) end elsif arg_defn.type.list? && value.is_a?(Array) field_ctx.schema.after_any_lazies(value, &:itself) else value end maybe_lazies << field_ctx.schema.after_lazy(loaded_value) do |loaded_value| prepared_value = if arg_defn.prepare arg_defn.prepare_value(obj, loaded_value) else loaded_value end ruby_kwargs[ruby_kwargs_key] = prepared_value end end end @extras.each do |extra_arg| ruby_kwargs[extra_arg] = fetch_extra(extra_arg, field_ctx) end field_ctx.schema.after_any_lazies(maybe_lazies) do ruby_kwargs end else NO_ARGS end end def public_send_field(unextended_obj, unextended_ruby_kwargs, query_ctx) with_extensions(unextended_obj, unextended_ruby_kwargs, query_ctx) do |obj, ruby_kwargs| if @resolver_class if obj.is_a?(GraphQL::Schema::Object) obj = obj.object end obj = @resolver_class.new(object: obj, context: query_ctx, field: self) end # Find a way to resolve this field, checking: # # - A method on the type instance; # - Hash keys, if the wrapped object is a hash; # - A method on the wrapped object; # - Or, raise not implemented. # if obj.respond_to?(@resolver_method) # Call the method with kwargs, if there are any if ruby_kwargs.any? obj.public_send(@resolver_method, **ruby_kwargs) else obj.public_send(@resolver_method) end elsif obj.object.is_a?(Hash) inner_object = obj.object if inner_object.key?(@method_sym) inner_object[@method_sym] else inner_object[@method_str] end elsif obj.object.respond_to?(@method_sym) if ruby_kwargs.any? obj.object.public_send(@method_sym, **ruby_kwargs) else obj.object.public_send(@method_sym) end else raise <<-ERR Failed to implement #{@owner.graphql_name}.#{@name}, tried: - `#{obj.class}##{@resolver_method}`, which did not exist - `#{obj.object.class}##{@method_sym}`, which did not exist - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash To implement this field, define one of the methods above (and check for typos) ERR end end end # Wrap execution with hooks. # Written iteratively to avoid big stack traces. # @return [Object] Whatever the def with_extensions(obj, args, ctx) if @extensions.empty? yield(obj, args) else # This is a hack to get the _last_ value for extended obj and args, # in case one of the extensions doesn't `yield`. # (There's another implementation that uses multiple-return, but I'm wary of the perf cost of the extra arrays) extended = { args: args, obj: obj, memos: nil } value = run_extensions_before_resolve(obj, args, ctx, extended) do |obj, args| yield(obj, args) end extended_obj = extended[:obj] extended_args = extended[:args] memos = extended[:memos] || EMPTY_HASH ctx.schema.after_lazy(value) do |resolved_value| idx = 0 @extensions.each do |ext| memo = memos[idx] # TODO after_lazy? resolved_value = ext.after_resolve(object: extended_obj, arguments: extended_args, context: ctx, value: resolved_value, memo: memo) idx += 1 end resolved_value end end end def run_extensions_before_resolve(obj, args, ctx, extended, idx: 0) extension = @extensions[idx] if extension extension.resolve(object: obj, arguments: args, context: ctx) do |extended_obj, extended_args, memo| if memo memos = extended[:memos] ||= {} memos[idx] = memo end extended[:obj] = extended_obj extended[:args] = extended_args run_extensions_before_resolve(extended_obj, extended_args, ctx, extended, idx: idx + 1) { |o, a| yield(o, a) } end else yield(obj, args) end end end end end graphql-ruby-1.11.10/lib/graphql/schema/field/000077500000000000000000000000001414121453000210125ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/schema/field/connection_extension.rb000066400000000000000000000063021414121453000255730ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Field class ConnectionExtension < GraphQL::Schema::FieldExtension def apply field.argument :after, "String", "Returns the elements in the list that come after the specified cursor.", required: false field.argument :before, "String", "Returns the elements in the list that come before the specified cursor.", required: false field.argument :first, "Int", "Returns the first _n_ elements from the list.", required: false field.argument :last, "Int", "Returns the last _n_ elements from the list.", required: false end # Remove pagination args before passing it to a user method def resolve(object:, arguments:, context:) next_args = arguments.dup next_args.delete(:first) next_args.delete(:last) next_args.delete(:before) next_args.delete(:after) yield(object, next_args, arguments) end def after_resolve(value:, object:, arguments:, context:, memo:) original_arguments = memo # rename some inputs to avoid conflicts inside the block maybe_lazy = value value = nil context.schema.after_lazy(maybe_lazy) do |resolved_value| value = resolved_value if value.is_a? GraphQL::ExecutionError # This isn't even going to work because context doesn't have ast_node anymore context.add_error(value) nil elsif value.nil? nil elsif value.is_a?(GraphQL::Pagination::Connection) # update the connection with some things that may not have been provided value.context ||= context value.parent ||= object.object value.first_value ||= original_arguments[:first] value.after_value ||= original_arguments[:after] value.last_value ||= original_arguments[:last] value.before_value ||= original_arguments[:before] if field.has_max_page_size? && !value.has_max_page_size_override? value.max_page_size = field.max_page_size end if context.schema.new_connections? && (custom_t = context.schema.connections.edge_class_for_field(@field)) value.edge_class = custom_t end value elsif context.schema.new_connections? wrappers = context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers context.schema.connections.wrap(field, object.object, value, original_arguments, context, wrappers: wrappers) else if object.is_a?(GraphQL::Schema::Object) object = object.object end connection_class = GraphQL::Relay::BaseConnection.connection_for_nodes(value) connection_class.new( value, original_arguments, field: field, max_page_size: field.max_page_size, parent: object, context: context, ) end end end end end end end graphql-ruby-1.11.10/lib/graphql/schema/field/scope_extension.rb000066400000000000000000000010051414121453000245400ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Field class ScopeExtension < GraphQL::Schema::FieldExtension def after_resolve(object:, arguments:, context:, value:, memo:) if value.nil? value else ret_type = @field.type.unwrap if ret_type.respond_to?(:scope_items) ret_type.scope_items(value, context) else value end end end end end end end graphql-ruby-1.11.10/lib/graphql/schema/field_extension.rb000066400000000000000000000057031414121453000234400ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Extend this class to make field-level customizations to resolve behavior. # # When a extension is added to a field with `extension(MyExtension)`, a `MyExtension` instance # is created, and its hooks are applied whenever that field is called. # # The instance is frozen so that instance variables aren't modified during query execution, # which could cause all kinds of issues due to race conditions. class FieldExtension # @return [GraphQL::Schema::Field] attr_reader :field # @return [Object] attr_reader :options # Called when the extension is mounted with `extension(name, options)`. # The instance is frozen to avoid improper use of state during execution. # @param field [GraphQL::Schema::Field] The field where this extension was mounted # @param options [Object] The second argument to `extension`, or `{}` if nothing was passed. def initialize(field:, options:) @field = field @options = options || {} apply freeze end # Called when this extension is attached to a field. # The field definition may be extended during this method. # @return [void] def apply end # Called before resolving {#field}. It should either: # # - `yield` values to continue execution; OR # - return something else to shortcut field execution. # # Whatever this method returns will be used for execution. # # @param object [Object] The object the field is being resolved on # @param arguments [Hash] Ruby keyword arguments for resolving this field # @param context [Query::Context] the context for this query # @yieldparam object [Object] The object to continue resolving the field on # @yieldparam arguments [Hash] The keyword arguments to continue resolving with # @yieldparam memo [Object] Any extension-specific value which will be passed to {#after_resolve} later # @return [Object] The return value for this field. def resolve(object:, arguments:, context:) yield(object, arguments, nil) end # Called after {#field} was resolved, and after any lazy values (like `Promise`s) were synced, # but before the value was added to the GraphQL response. # # Whatever this hook returns will be used as the return value. # # @param object [Object] The object the field is being resolved on # @param arguments [Hash] Ruby keyword arguments for resolving this field # @param context [Query::Context] the context for this query # @param value [Object] Whatever the field previously returned # @param memo [Object] The third value yielded by {#resolve}, or `nil` if there wasn't one # @return [Object] The return value for this field. def after_resolve(object:, arguments:, context:, value:, memo:) value end end end end graphql-ruby-1.11.10/lib/graphql/schema/find_inherited_value.rb000066400000000000000000000014541414121453000244270ustar00rootroot00000000000000module GraphQL class Schema module FindInheritedValue module EmptyObjects EMPTY_HASH = {}.freeze EMPTY_ARRAY = [].freeze end def self.extended(child_cls) child_cls.singleton_class.include(EmptyObjects) end def self.included(child_cls) child_cls.include(EmptyObjects) end private def find_inherited_value(method_name, default_value = nil) if self.is_a?(Class) superclass.respond_to?(method_name, true) ? superclass.send(method_name) : default_value else ancestors[1..-1].each do |ancestor| if ancestor.respond_to?(method_name, true) return ancestor.send(method_name) end end default_value end end end end end graphql-ruby-1.11.10/lib/graphql/schema/finder.rb000066400000000000000000000107271414121453000215320ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Find schema members using string paths # # @example Finding object types # MySchema.find("SomeObjectType") # # @example Finding fields # MySchema.find("SomeObjectType.myField") # # @example Finding arguments # MySchema.find("SomeObjectType.myField.anArgument") # # @example Finding directives # MySchema.find("@include") # class Finder class MemberNotFoundError < ArgumentError; end def initialize(schema) @schema = schema end def find(path) path = path.split(".") type_or_directive = path.shift if type_or_directive.start_with?("@") directive = schema.directives[type_or_directive[1..-1]] if directive.nil? raise MemberNotFoundError, "Could not find directive `#{type_or_directive}` in schema." end return directive if path.empty? find_in_directive(directive, path: path) else type = schema.get_type(type_or_directive) if type.nil? raise MemberNotFoundError, "Could not find type `#{type_or_directive}` in schema." end return type if path.empty? find_in_type(type, path: path) end end private attr_reader :schema def find_in_directive(directive, path:) argument_name = path.shift argument = directive.arguments[argument_name] if argument.nil? raise MemberNotFoundError, "Could not find argument `#{argument_name}` on directive #{directive}." end argument end def find_in_type(type, path:) case type.kind.name when "OBJECT" find_in_fields_type(type, kind: "object", path: path) when "INTERFACE" find_in_fields_type(type, kind: "interface", path: path) when "INPUT_OBJECT" find_in_input_object(type, path: path) when "UNION" # Error out if path that was provided is too long # i.e UnionType.PossibleType.aField # Use PossibleType.aField instead. if invalid = path.first raise MemberNotFoundError, "Cannot select union possible type `#{invalid}`. Select the type directly instead." end when "ENUM" find_in_enum_type(type, path: path) else raise "Unexpected find_in_type: #{type.inspect} (#{path})" end end def find_in_fields_type(type, kind:, path:) field_name = path.shift field = schema.get_field(type, field_name) if field.nil? raise MemberNotFoundError, "Could not find field `#{field_name}` on #{kind} type `#{type.graphql_name}`." end return field if path.empty? find_in_field(field, path: path) end def find_in_field(field, path:) argument_name = path.shift argument = field.arguments[argument_name] if argument.nil? raise MemberNotFoundError, "Could not find argument `#{argument_name}` on field `#{field.name}`." end # Error out if path that was provided is too long # i.e Type.field.argument.somethingBad if invalid = path.first raise MemberNotFoundError, "Cannot select member `#{invalid}` on a field." end argument end def find_in_input_object(input_object, path:) field_name = path.shift input_field = input_object.arguments[field_name] if input_field.nil? raise MemberNotFoundError, "Could not find input field `#{field_name}` on input object type `#{input_object.graphql_name}`." end # Error out if path that was provided is too long # i.e InputType.inputField.bad if invalid = path.first raise MemberNotFoundError, "Cannot select member `#{invalid}` on an input field." end input_field end def find_in_enum_type(enum_type, path:) value_name = path.shift enum_value = enum_type.values[value_name] if enum_value.nil? raise MemberNotFoundError, "Could not find enum value `#{value_name}` on enum type `#{enum_type.graphql_name}`." end # Error out if path that was provided is too long # i.e Enum.VALUE.wat if invalid = path.first raise MemberNotFoundError, "Cannot select member `#{invalid}` on an enum value." end enum_value end end end end graphql-ruby-1.11.10/lib/graphql/schema/input_object.rb000066400000000000000000000200351414121453000227410ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class InputObject < GraphQL::Schema::Member extend GraphQL::Schema::Member::AcceptsDefinition extend Forwardable extend GraphQL::Schema::Member::HasArguments extend GraphQL::Schema::Member::HasArguments::ArgumentObjectLoader extend GraphQL::Schema::Member::ValidatesInput include GraphQL::Dig def initialize(arguments = nil, ruby_kwargs: nil, context:, defaults_used:) @context = context if ruby_kwargs @ruby_style_hash = ruby_kwargs @arguments = arguments else @arguments = self.class.arguments_class.new(arguments, context: context, defaults_used: defaults_used) # Symbolized, underscored hash: @ruby_style_hash = @arguments.to_kwargs end # Apply prepares, not great to have it duplicated here. maybe_lazies = [] self.class.arguments.each_value do |arg_defn| ruby_kwargs_key = arg_defn.keyword if @ruby_style_hash.key?(ruby_kwargs_key) loads = arg_defn.loads # Resolvers do this loading themselves; # With the interpreter, it's done during `coerce_arguments` if loads && !arg_defn.from_resolver? && !context.interpreter? value = @ruby_style_hash[ruby_kwargs_key] loaded_value = if arg_defn.type.list? value.map { |val| load_application_object(arg_defn, loads, val, context) } else load_application_object(arg_defn, loads, value, context) end maybe_lazies << context.schema.after_lazy(loaded_value) do |loaded_value| @ruby_style_hash[ruby_kwargs_key] = loaded_value end end # Weirdly, procs are applied during coercion, but not methods. # Probably because these methods require a `self`. if arg_defn.prepare.is_a?(Symbol) || context.nil? || !context.interpreter? @ruby_style_hash[ruby_kwargs_key] = arg_defn.prepare_value(self, @ruby_style_hash[ruby_kwargs_key]) end end end @maybe_lazies = maybe_lazies end # @return [GraphQL::Query::Context] The context for this query attr_reader :context # @return [GraphQL::Query::Arguments, GraphQL::Execution::Interpereter::Arguments] The underlying arguments instance attr_reader :arguments # Ruby-like hash behaviors, read-only def_delegators :@ruby_style_hash, :keys, :values, :each, :map, :any?, :empty? def to_h @ruby_style_hash.inject({}) do |h, (key, value)| h.merge(key => unwrap_value(value)) end end def to_hash to_h end def prepare if context context.schema.after_any_lazies(@maybe_lazies) do self end else self end end def unwrap_value(value) case value when Array value.map { |item| unwrap_value(item) } when Hash value.inject({}) do |h, (key, value)| h.merge(key => unwrap_value(value)) end when InputObject value.to_h else value end end # Lookup a key on this object, it accepts new-style underscored symbols # Or old-style camelized identifiers. # @param key [Symbol, String] def [](key) if @ruby_style_hash.key?(key) @ruby_style_hash[key] elsif @arguments @arguments[key] else nil end end def key?(key) @ruby_style_hash.key?(key) || (@arguments && @arguments.key?(key)) || false end # A copy of the Ruby-style hash def to_kwargs @ruby_style_hash.dup end class << self # @return [Class] attr_accessor :arguments_class def argument(*args, **kwargs, &block) argument_defn = super(*args, **kwargs, &block) # Add a method access method_name = argument_defn.keyword class_eval <<-RUBY, __FILE__, __LINE__ def #{method_name} self[#{method_name.inspect}] end RUBY end def to_graphql type_defn = GraphQL::InputObjectType.new type_defn.name = graphql_name type_defn.description = description type_defn.metadata[:type_class] = self type_defn.mutation = mutation type_defn.ast_node = ast_node arguments.each do |name, arg| type_defn.arguments[arg.graphql_definition.name] = arg.graphql_definition end # Make a reference to a classic-style Arguments class self.arguments_class = GraphQL::Query::Arguments.construct_arguments_class(type_defn) # But use this InputObject class at runtime type_defn.arguments_class = self type_defn end def kind GraphQL::TypeKinds::INPUT_OBJECT end # @api private INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object responding to `to_h` or `to_unsafe_h`." def validate_non_null_input(input, ctx) result = GraphQL::Query::InputValidationResult.new warden = ctx.warden if input.is_a?(Array) result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) }) return result end input = begin input.to_h rescue begin # Handle ActionController::Parameters: input.to_unsafe_h rescue # We're not sure it'll act like a hash, so reject it: result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) }) return result end end # Inject missing required arguments missing_required_inputs = self.arguments.reduce({}) do |m, (argument_name, argument)| if !input.key?(argument_name) && argument.type.non_null? && warden.get_argument(self, argument_name) m[argument_name] = nil end m end input.merge(missing_required_inputs).each do |argument_name, value| argument = warden.get_argument(self, argument_name) # Items in the input that are unexpected unless argument result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name]) next end # Items in the input that are expected, but have invalid values argument_result = argument.type.validate_input(value, ctx) result.merge_result!(argument_name, argument_result) unless argument_result.valid? end result end def coerce_input(value, ctx) if value.nil? return nil end arguments = coerce_arguments(nil, value, ctx) ctx.schema.after_lazy(arguments) do |resolved_arguments| input_obj_instance = self.new(resolved_arguments, ruby_kwargs: resolved_arguments.keyword_arguments, context: ctx, defaults_used: nil) input_obj_instance.prepare end end # It's funny to think of a _result_ of an input object. # This is used for rendering the default value in introspection responses. def coerce_result(value, ctx) # Allow the application to provide values as :symbols, and convert them to the strings value = value.reduce({}) { |memo, (k, v)| memo[k.to_s] = v; memo } result = {} arguments.each do |input_key, input_field_defn| input_value = value[input_key] if value.key?(input_key) result[input_key] = if input_value.nil? nil else input_field_defn.type.coerce_result(input_value, ctx) end end end result end end end end end graphql-ruby-1.11.10/lib/graphql/schema/interface.rb000066400000000000000000000126671414121453000222300ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema module Interface include GraphQL::Schema::Member::GraphQLTypeNames module DefinitionMethods include GraphQL::Schema::Member::CachedGraphQLDefinition include GraphQL::Relay::TypeExtensions include GraphQL::Schema::Member::BaseDSLMethods # ConfigurationExtension's responsibilities are in `def included` below include GraphQL::Schema::Member::TypeSystemHelpers include GraphQL::Schema::Member::HasFields include GraphQL::Schema::Member::HasPath include GraphQL::Schema::Member::RelayShortcuts include GraphQL::Schema::Member::Scoped include GraphQL::Schema::Member::HasAstNode include GraphQL::Schema::Member::HasUnresolvedTypeError # Methods defined in this block will be: # - Added as class methods to this interface # - Added as class methods to all child interfaces def definition_methods(&block) self::DefinitionMethods.module_eval(&block) end # @see {Schema::Warden} hides interfaces without visible implementations def visible?(context) true end # The interface is accessible if any of its possible types are accessible def accessible?(context) context.schema.possible_types(self, context).each do |type| if context.schema.accessible?(type, context) return true end end false end def type_membership_class(membership_class = nil) if membership_class @type_membership_class = membership_class else @type_membership_class || find_inherited_value(:type_membership_class, GraphQL::Schema::TypeMembership) end end # Here's the tricky part. Make sure behavior keeps making its way down the inheritance chain. def included(child_class) if !child_class.is_a?(Class) # In this case, it's been included into another interface. # This is how interface inheritance is implemented # We need this before we can call `own_interfaces` child_class.extend(Schema::Interface::DefinitionMethods) child_class.type_membership_class(self.type_membership_class) child_class.own_interfaces << self child_class.interfaces.reverse_each do |interface_defn| child_class.extend(interface_defn::DefinitionMethods) end # Use an instance variable to tell whether it's been included previously or not; # You can't use constant detection because constants are brought into scope # by `include`, which has already happened at this point. if !child_class.instance_variable_defined?(:@_definition_methods) defn_methods_module = Module.new child_class.instance_variable_set(:@_definition_methods, defn_methods_module) child_class.const_set(:DefinitionMethods, defn_methods_module) child_class.extend(child_class::DefinitionMethods) end child_class.introspection(introspection) child_class.description(description) if overridden_graphql_name child_class.graphql_name(overridden_graphql_name) end # If interfaces are mixed into each other, only define this class once if !child_class.const_defined?(:UnresolvedTypeError, false) add_unresolved_type_error(child_class) end elsif child_class < GraphQL::Schema::Object # This is being included into an object type, make sure it's using `implements(...)` backtrace_line = caller(0, 10).find { |line| line.include?("schema/object.rb") && line.include?("in `implements'")} if !backtrace_line raise "Attach interfaces using `implements(#{self})`, not `include(#{self})`" end end super end def orphan_types(*types) if types.any? @orphan_types = types else all_orphan_types = @orphan_types || [] all_orphan_types += super if defined?(super) all_orphan_types.uniq end end def to_graphql type_defn = GraphQL::InterfaceType.new type_defn.name = graphql_name type_defn.description = description type_defn.orphan_types = orphan_types type_defn.type_membership_class = self.type_membership_class type_defn.ast_node = ast_node fields.each do |field_name, field_inst| field_defn = field_inst.graphql_definition type_defn.fields[field_defn.name] = field_defn end type_defn.metadata[:type_class] = self if respond_to?(:resolve_type) type_defn.resolve_type = method(:resolve_type) end type_defn end def kind GraphQL::TypeKinds::INTERFACE end protected def own_interfaces @own_interfaces ||= [] end def interfaces own_interfaces + (own_interfaces.map { |i| i.own_interfaces }).flatten end end # Extend this _after_ `DefinitionMethods` is defined, so it will be used extend GraphQL::Schema::Member::AcceptsDefinition extend DefinitionMethods def unwrap self end end end end graphql-ruby-1.11.10/lib/graphql/schema/introspection_system.rb000066400000000000000000000126111414121453000245610ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class IntrospectionSystem attr_reader :types, :possible_types def initialize(schema) @schema = schema @class_based = !!@schema.is_a?(Class) @built_in_namespace = GraphQL::Introspection @custom_namespace = if @class_based schema.introspection || @built_in_namespace else schema.introspection_namespace || @built_in_namespace end type_defns = [ load_constant(:SchemaType), load_constant(:TypeType), load_constant(:FieldType), load_constant(:DirectiveType), load_constant(:EnumValueType), load_constant(:InputValueType), load_constant(:TypeKindEnum), load_constant(:DirectiveLocationEnum) ] @types = {} @possible_types = {} type_defns.each do |t| @types[t.graphql_name] = t @possible_types[t.graphql_name] = [t] end @entry_point_fields = if schema.disable_introspection_entry_points? {} else entry_point_fields = get_fields_from_class(class_sym: :EntryPoints) entry_point_fields.delete('__schema') if schema.disable_schema_introspection_entry_point? entry_point_fields.delete('__type') if schema.disable_type_introspection_entry_point? entry_point_fields end @dynamic_fields = get_fields_from_class(class_sym: :DynamicFields) end def entry_points @entry_point_fields.values end def entry_point(name:) @entry_point_fields[name] end def dynamic_fields @dynamic_fields.values end def dynamic_field(name:) @dynamic_fields[name] end # The introspection system is prepared with a bunch of LateBoundTypes. # Replace those with the objects that they refer to, since LateBoundTypes # aren't handled at runtime. # # @api private # @return void def resolve_late_bindings @types.each do |name, t| if t.kind.fields? t.fields.each do |_name, field_defn| field_defn.type = resolve_late_binding(field_defn.type) end end end @entry_point_fields.each do |name, f| f.type = resolve_late_binding(f.type) end @dynamic_fields.each do |name, f| f.type = resolve_late_binding(f.type) end nil end private def resolve_late_binding(late_bound_type) case late_bound_type when GraphQL::Schema::LateBoundType @schema.get_type(late_bound_type.name) when GraphQL::Schema::List, GraphQL::ListType resolve_late_binding(late_bound_type.of_type).to_list_type when GraphQL::Schema::NonNull, GraphQL::NonNullType resolve_late_binding(late_bound_type.of_type).to_non_null_type when Module # It's a normal type -- no change required late_bound_type else raise "Invariant: unexpected type: #{late_bound_type} (#{late_bound_type.class})" end end def load_constant(class_name) const = @custom_namespace.const_get(class_name) if @class_based dup_type_class(const) else # Use `.to_graphql` to get a freshly-made version, not shared between schemas const.to_graphql end rescue NameError # Dup the built-in so that the cached fields aren't shared dup_type_class(@built_in_namespace.const_get(class_name)) end def get_fields_from_class(class_sym:) object_type_defn = load_constant(class_sym) if object_type_defn.is_a?(Module) object_type_defn.fields else extracted_field_defns = {} object_class = object_type_defn.metadata[:type_class] object_type_defn.all_fields.each do |field_defn| inner_resolve = field_defn.resolve_proc resolve_with_instantiate = PerFieldProxyResolve.new(object_class: object_class, inner_resolve: inner_resolve) extracted_field_defns[field_defn.name] = field_defn.redefine(resolve: resolve_with_instantiate) end extracted_field_defns end end # This is probably not 100% robust -- but it has to be good enough to avoid modifying the built-in introspection types def dup_type_class(type_class) type_name = type_class.graphql_name Class.new(type_class) do # This won't be inherited like other things will graphql_name(type_name) if type_class.kind.fields? type_class.fields.each do |_name, field_defn| dup_field = field_defn.dup dup_field.owner = self add_field(dup_field) end end end end class PerFieldProxyResolve def initialize(object_class:, inner_resolve:) @object_class = object_class @inner_resolve = inner_resolve end def call(obj, args, ctx) query_ctx = ctx.query.context # Remove the QueryType wrapper if obj.is_a?(GraphQL::Schema::Object) obj = obj.object end wrapped_object = @object_class.authorized_new(obj, query_ctx) @inner_resolve.call(wrapped_object, args, ctx) end end end end end graphql-ruby-1.11.10/lib/graphql/schema/invalid_type_error.rb000066400000000000000000000001721414121453000241540ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class InvalidTypeError < GraphQL::Error end end end graphql-ruby-1.11.10/lib/graphql/schema/late_bound_type.rb000066400000000000000000000013451414121453000234340ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # A stand-in for a type which will be resolved in a given schema, by name. # TODO: support argument types too, make this a public API somehow # @api Private class LateBoundType attr_reader :name alias :graphql_name :name def initialize(local_name) @name = local_name end def unwrap self end def to_non_null_type @to_non_null_type ||= GraphQL::NonNullType.new(of_type: self) end def to_list_type @to_list_type ||= GraphQL::ListType.new(of_type: self) end def inspect "#" end alias :to_s :inspect end end end graphql-ruby-1.11.10/lib/graphql/schema/list.rb000066400000000000000000000034321414121453000212310ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Represents a list type in the schema. # Wraps a {Schema::Member} as a list type. # @see {Schema::Member::TypeSystemHelpers#to_list_type} class List < GraphQL::Schema::Wrapper include Schema::Member::ValidatesInput def to_graphql @of_type.graphql_definition.to_list_type end # @return [GraphQL::TypeKinds::LIST] def kind GraphQL::TypeKinds::LIST end # @return [true] def list? true end def to_type_signature "[#{@of_type.to_type_signature}]" end # This is for introspection, where it's expected the name will be `null` def graphql_name nil end # Also for implementing introspection def description nil end def coerce_result(value, ctx) value.map { |i| i.nil? ? nil : of_type.coerce_result(i, ctx) } end def coerce_input(value, ctx) if value.nil? nil else coerced = ensure_array(value).map { |item| item.nil? ? item : of_type.coerce_input(item, ctx) } ctx.schema.after_any_lazies(coerced, &:itself) end end def validate_non_null_input(value, ctx) result = GraphQL::Query::InputValidationResult.new ensure_array(value).each_with_index do |item, index| item_result = of_type.validate_input(item, ctx) if !item_result.valid? result.merge_result!(index, item_result) end end result end private def ensure_array(value) # `Array({ a: 1 })` makes `[[:a, 1]]`, so do it manually if value.is_a?(Array) value else [value] end end end end end graphql-ruby-1.11.10/lib/graphql/schema/loader.rb000066400000000000000000000174301414121453000215270ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # You can use the result of {GraphQL::Introspection::INTROSPECTION_QUERY} # to make a schema. This schema is missing some important details like # `resolve` functions, but it does include the full type system, # so you can use it to validate queries. # # @see GraphQL::Schema.from_introspection for a public API module Loader extend self # Create schema with the result of an introspection query. # @param introspection_result [Hash] A response from {GraphQL::Introspection::INTROSPECTION_QUERY} # @return [Class] the schema described by `input` def load(introspection_result) schema = introspection_result.fetch("data").fetch("__schema") types = {} type_resolver = ->(type) { resolve_type(types, type) } schema.fetch("types").each do |type| next if type.fetch("name").start_with?("__") type_object = define_type(type, type_resolver) types[type["name"]] = type_object end directives = [] schema.fetch("directives", []).each do |directive| next if GraphQL::Schema.default_directives.include?(directive.fetch("name")) directives << define_directive(directive, type_resolver) end Class.new(GraphQL::Schema) do orphan_types(types.values) directives(directives) def self.resolve_type(*) raise(GraphQL::RequiredImplementationMissingError, "This schema was loaded from string, so it can't resolve types for objects") end [:query, :mutation, :subscription].each do |root| type = schema["#{root}Type"] if type type_defn = types.fetch(type.fetch("name")) self.public_send(root, type_defn) end end end end NullScalarCoerce = ->(val, _ctx) { val } class << self private def resolve_type(types, type) case kind = type.fetch("kind") when "ENUM", "INTERFACE", "INPUT_OBJECT", "OBJECT", "SCALAR", "UNION" type_name = type.fetch("name") type = types[type_name] || Schema::BUILT_IN_TYPES[type_name] if type.nil? GraphQL::Schema::LateBoundType.new(type_name) else type end when "LIST" Schema::List.new(resolve_type(types, type.fetch("ofType"))) when "NON_NULL" Schema::NonNull.new(resolve_type(types, type.fetch("ofType"))) else fail GraphQL::RequiredImplementationMissingError, "#{kind} not implemented" end end def extract_default_value(default_value_str, input_value_ast) case input_value_ast when String, Integer, Float, TrueClass, FalseClass input_value_ast when GraphQL::Language::Nodes::Enum input_value_ast.name when GraphQL::Language::Nodes::NullValue nil when GraphQL::Language::Nodes::InputObject input_value_ast.to_h when Array input_value_ast.map { |element| extract_default_value(default_value_str, element) } else raise( "Encountered unexpected type when loading default value. "\ "input_value_ast.class is #{input_value_ast.class} "\ "default_value is #{default_value_str}" ) end end def define_type(type, type_resolver) loader = self case type.fetch("kind") when "ENUM" Class.new(GraphQL::Schema::Enum) do graphql_name(type["name"]) description(type["description"]) type["enumValues"].each do |enum_value| value( enum_value["name"], description: enum_value["description"], deprecation_reason: enum_value["deprecationReason"], ) end end when "INTERFACE" Module.new do include GraphQL::Schema::Interface graphql_name(type["name"]) description(type["description"]) loader.build_fields(self, type["fields"] || [], type_resolver) end when "INPUT_OBJECT" Class.new(GraphQL::Schema::InputObject) do graphql_name(type["name"]) description(type["description"]) loader.build_arguments(self, type["inputFields"], type_resolver) end when "OBJECT" Class.new(GraphQL::Schema::Object) do graphql_name(type["name"]) description(type["description"]) if type["interfaces"] type["interfaces"].each do |interface_type| implements(type_resolver.call(interface_type)) end end loader.build_fields(self, type["fields"], type_resolver) end when "SCALAR" type_name = type.fetch("name") if (builtin = GraphQL::Schema::BUILT_IN_TYPES[type_name]) builtin else Class.new(GraphQL::Schema::Scalar) do graphql_name(type["name"]) description(type["description"]) end end when "UNION" Class.new(GraphQL::Schema::Union) do graphql_name(type["name"]) description(type["description"]) possible_types(*(type["possibleTypes"].map { |pt| type_resolver.call(pt) })) end else fail GraphQL::RequiredImplementationMissingError, "#{type["kind"]} not implemented" end end def define_directive(directive, type_resolver) loader = self Class.new(GraphQL::Schema::Directive) do graphql_name(directive["name"]) description(directive["description"]) locations(*directive["locations"].map(&:to_sym)) loader.build_arguments(self, directive["args"], type_resolver) end end public def build_fields(type_defn, fields, type_resolver) loader = self fields.each do |field_hash| type_defn.field( field_hash["name"], type: type_resolver.call(field_hash["type"]), description: field_hash["description"], deprecation_reason: field_hash["deprecationReason"], null: true, camelize: false, ) do if field_hash["args"].any? loader.build_arguments(self, field_hash["args"], type_resolver) end end end end def build_arguments(arg_owner, args, type_resolver) args.each do |arg| kwargs = { type: type_resolver.call(arg["type"]), description: arg["description"], deprecation_reason: arg["deprecationReason"], required: false, method_access: false, camelize: false, } if arg["defaultValue"] default_value_str = arg["defaultValue"] dummy_query_str = "query getStuff($var: InputObj = #{default_value_str}) { __typename }" # Returns a `GraphQL::Language::Nodes::Document`: dummy_query_ast = GraphQL.parse(dummy_query_str) # Reach into the AST for the default value: input_value_ast = dummy_query_ast.definitions.first.variables.first.default_value kwargs[:default_value] = extract_default_value(default_value_str, input_value_ast) end arg_owner.argument(arg["name"], **kwargs) end end end end end end graphql-ruby-1.11.10/lib/graphql/schema/member.rb000066400000000000000000000024441414121453000215270ustar00rootroot00000000000000# frozen_string_literal: true require 'graphql/schema/member/accepts_definition' require 'graphql/schema/member/base_dsl_methods' require 'graphql/schema/member/cached_graphql_definition' require 'graphql/schema/member/graphql_type_names' require 'graphql/schema/member/has_ast_node' require 'graphql/schema/member/has_path' require 'graphql/schema/member/has_unresolved_type_error' require 'graphql/schema/member/relay_shortcuts' require 'graphql/schema/member/scoped' require 'graphql/schema/member/type_system_helpers' require 'graphql/schema/member/validates_input' require "graphql/relay/type_extensions" module GraphQL class Schema # The base class for things that make up the schema, # eg objects, enums, scalars. # # @api private class Member include GraphQLTypeNames extend CachedGraphQLDefinition extend GraphQL::Relay::TypeExtensions extend BaseDSLMethods extend BaseDSLMethods::ConfigurationExtension introspection(false) extend TypeSystemHelpers extend Scoped extend RelayShortcuts extend HasPath extend HasAstNode end end end require 'graphql/schema/member/has_arguments' require 'graphql/schema/member/has_fields' require 'graphql/schema/member/instrumentation' require 'graphql/schema/member/build_type' graphql-ruby-1.11.10/lib/graphql/schema/member/000077500000000000000000000000001414121453000211765ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/schema/member/accepts_definition.rb000066400000000000000000000123521414121453000253600ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member # Support for legacy `accepts_definitions` functions. # # Keep the legacy handler hooked up. Class-based types and fields # will call those legacy handlers during their `.to_graphql` # methods. # # This can help out while transitioning from one to the other. # Eventually, `GraphQL::{X}Type` objects will be removed entirely, # But this can help during the transition. # # @example Applying a function to base object class # # Here's the legacy-style config, which we're calling back to: # GraphQL::ObjectType.accepts_definition({ # permission_level: ->(defn, value) { defn.metadata[:permission_level] = value } # }) # # class BaseObject < GraphQL::Schema::Object # # Setup a named pass-through to the legacy config functions # accepts_definition :permission_level # end # # class Account < BaseObject # # This value will be passed to the legacy handler: # permission_level 1 # end # # # The class gets a reader method which returns the args, # # only marginally useful. # Account.permission_level # => [1] # # # The legacy handler is called, as before: # Account.graphql_definition.metadata[:permission_level] # => 1 module AcceptsDefinition def self.included(child) child.extend(AcceptsDefinitionDefinitionMethods) child.prepend(ToGraphQLExtension) child.prepend(InitializeExtension) end def self.extended(child) if defined?(child::DefinitionMethods) child::DefinitionMethods.include(AcceptsDefinitionDefinitionMethods) child::DefinitionMethods.prepend(ToGraphQLExtension) else child.extend(AcceptsDefinitionDefinitionMethods) # I tried to use `super`, but super isn't quite right # since the method is defined in the same class itself, # not the superclass child.class_eval do class << self prepend(ToGraphQLExtension) end end end end module AcceptsDefinitionDefinitionMethods def accepts_definition(name) own_accepts_definition_methods << name ivar_name = "@#{name}_args" if self.is_a?(Class) define_singleton_method(name) do |*args| if args.any? instance_variable_set(ivar_name, args) end instance_variable_get(ivar_name) || (superclass.respond_to?(name) ? superclass.public_send(name) : nil) end define_method(name) do |*args| if args.any? instance_variable_set(ivar_name, args) end instance_variable_get(ivar_name) end else # Special handling for interfaces, define it here # so it's appropriately passed down self::DefinitionMethods.module_eval do define_method(name) do |*args| if args.any? instance_variable_set(ivar_name, args) end instance_variable_get(ivar_name) || ((int = interfaces.first { |i| i.respond_to?()}) && int.public_send(name)) end end end end def accepts_definition_methods inherited_methods = if self.is_a?(Class) superclass.respond_to?(:accepts_definition_methods) ? superclass.accepts_definition_methods : [] elsif self.is_a?(Module) m = [] ancestors.each do |a| if a.respond_to?(:own_accepts_definition_methods) m.concat(a.own_accepts_definition_methods) end end m else self.class.accepts_definition_methods end own_accepts_definition_methods + inherited_methods end def own_accepts_definition_methods @own_accepts_definition_methods ||= [] end end module ToGraphQLExtension def to_graphql defn = super accepts_definition_methods.each do |method_name| value = public_send(method_name) if !value.nil? defn = defn.redefine { public_send(method_name, *value) } end end defn end end module InitializeExtension def initialize(*args, **kwargs, &block) self.class.accepts_definition_methods.each do |method_name| if kwargs.key?(method_name) value = kwargs.delete(method_name) if !value.is_a?(Array) value = [value] end instance_variable_set("@#{method_name}_args", value) end end super(*args, **kwargs, &block) end def accepts_definition_methods self.class.accepts_definition_methods end end end end end end graphql-ruby-1.11.10/lib/graphql/schema/member/base_dsl_methods.rb000066400000000000000000000100611414121453000250200ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/schema/find_inherited_value" module GraphQL class Schema class Member # DSL methods shared by lots of things in the GraphQL Schema. # @api private # @see Classes that extend this, eg {GraphQL::Schema::Object} module BaseDSLMethods include GraphQL::Schema::FindInheritedValue # Call this with a new name to override the default name for this schema member; OR # call it without an argument to get the name of this schema member # # The default name is implemented in default_graphql_name # @param new_name [String] # @return [String] def graphql_name(new_name = nil) if new_name @graphql_name = new_name else overridden_graphql_name || default_graphql_name end end def overridden_graphql_name defined?(@graphql_name) ? @graphql_name : nil end # Just a convenience method to point out that people should use graphql_name instead def name(new_name = nil) return super() if new_name.nil? fail( "The new name override method is `graphql_name`, not `name`. Usage: "\ "graphql_name \"#{new_name}\"" ) end # Call this method to provide a new description; OR # call it without an argument to get the description # @param new_description [String] # @return [String] def description(new_description = nil) if new_description @description = new_description elsif defined?(@description) @description else nil end end # This pushes some configurations _down_ the inheritance tree, # in order to prevent repetitive lookups at runtime. module ConfigurationExtension def inherited(child_class) child_class.introspection(introspection) child_class.description(description) if overridden_graphql_name child_class.graphql_name(overridden_graphql_name) end super end end # @return [Boolean] If true, this object is part of the introspection system def introspection(new_introspection = nil) if !new_introspection.nil? @introspection = new_introspection elsif defined?(@introspection) @introspection else false end end def introspection? introspection end # The mutation this type was derived from, if it was derived from a mutation # @return [Class] def mutation(mutation_class = nil) if mutation_class @mutation = mutation_class elsif defined?(@mutation) @mutation else nil end end # @return [GraphQL::BaseType] Convert this type to a legacy-style object. def to_graphql raise GraphQL::RequiredImplementationMissingError end alias :unwrap :itself # Creates the default name for a schema member. # The default name is the Ruby constant name, # without any namespaces and with any `-Type` suffix removed def default_graphql_name @default_graphql_name ||= begin raise GraphQL::RequiredImplementationMissingError, 'Anonymous class should declare a `graphql_name`' if name.nil? name.split("::").last.sub(/Type\Z/, "") end end def visible?(context) if @mutation @mutation.visible?(context) else true end end def accessible?(context) if @mutation @mutation.accessible?(context) else true end end def authorized?(object, context) if @mutation @mutation.authorized?(object, context) else true end end end end end end graphql-ruby-1.11.10/lib/graphql/schema/member/build_type.rb000066400000000000000000000143721414121453000236720ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member # @api private module BuildType if !String.method_defined?(:match?) using GraphQL::StringMatchBackport end LIST_TYPE_ERROR = "Use an array of [T] or [T, null: true] for list types; other arrays are not supported" module_function # @param type_expr [String, Class, GraphQL::BaseType] # @return [GraphQL::BaseType] def parse_type(type_expr, null:) list_type = false return_type = case type_expr when String case type_expr when "String" GraphQL::Types::String when "Int", "Integer" GraphQL::Types::Int when "Float" GraphQL::Types::Float when "Boolean" GraphQL::Types::Boolean when "ID" GraphQL::Types::ID when /\A\[.*\]\Z/ list_type = true # List members are required by default parse_type(type_expr[1..-2], null: false) when /.*!\Z/ null = false parse_type(type_expr[0..-2], null: true) else maybe_type = constantize(type_expr) case maybe_type when GraphQL::BaseType maybe_type when Module # This is a way to check that it's the right kind of module: if maybe_type.respond_to?(:graphql_definition) maybe_type else raise ArgumentError, "Unexpected class/module found for GraphQL type: #{type_expr} (must be type definition class/module)" end end end when GraphQL::BaseType, GraphQL::Schema::LateBoundType type_expr when Array case type_expr.length when 1 list_type = true # List members are required by default parse_type(type_expr.first, null: false) when 2 inner_type, nullable_option = type_expr if nullable_option.keys != [:null] || nullable_option.values != [true] raise ArgumentError, LIST_TYPE_ERROR end list_type = true parse_type(inner_type, null: true) else raise ArgumentError, LIST_TYPE_ERROR end when GraphQL::Schema::NonNull, GraphQL::Schema::List type_expr when Module # This is a way to check that it's the right kind of module: if type_expr.respond_to?(:graphql_definition) type_expr else # Eg `String` => GraphQL::STRING_TYPE parse_type(type_expr.name, null: true) end when Proc parse_type(type_expr.call, null: true) when false raise ArgumentError, "Received `false` instead of a type, maybe a `!` should be replaced with `null: true` (for fields) or `required: true` (for arguments)" end if return_type.nil? raise "Unexpected type input: #{type_expr.inspect} (#{type_expr.class})" end # Apply list_type first, that way the # .to_non_null_type applies to the list type, not the inner type if list_type return_type = return_type.to_list_type end if !null return_type = return_type.to_non_null_type end return_type end def to_type_name(something) case something when GraphQL::BaseType, GraphQL::Schema::LateBoundType something.unwrap.name when Array to_type_name(something.first) when Module if something.respond_to?(:graphql_name) something.graphql_name else to_type_name(something.name) end when String something.gsub(/\]\[\!/, "").split("::").last when GraphQL::Schema::NonNull, GraphQL::Schema::List to_type_name(something.unwrap) else raise "Unhandled to_type_name input: #{something} (#{something.class})" end end def camelize(string) return string unless string.include?("_") camelized = string.split('_').map(&:capitalize).join camelized[0] = camelized[0].downcase if (match_data = string.match(/\A(_+)/)) camelized = "#{match_data[0]}#{camelized}" end camelized end # Resolves constant from string (based on Rails `ActiveSupport::Inflector.constantize`) def constantize(string) names = string.split('::') # Trigger a built-in NameError exception including the ill-formed constant in the message. Object.const_get(string) if names.empty? # Remove the first blank element in case of '::ClassName' notation. names.shift if names.size > 1 && names.first.empty? names.inject(Object) do |constant, name| if constant == Object constant.const_get(name) else candidate = constant.const_get(name) next candidate if constant.const_defined?(name, false) next candidate unless Object.const_defined?(name) # Go down the ancestors to check if it is owned directly. The check # stops when we reach Object or the end of ancestors tree. constant = constant.ancestors.inject do |const, ancestor| break const if ancestor == Object break ancestor if ancestor.const_defined?(name, false) const end # Owner is in Object, so raise. constant.const_get(name, false) end end end def underscore(string) if string.match?(/\A[a-z_]+\Z/) return string end string2 = string.dup string2.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') # URLDecoder -> URL_Decoder string2.gsub!(/([a-z\d])([A-Z])/,'\1_\2') # someThing -> some_Thing string2.downcase! string2 end end end end end graphql-ruby-1.11.10/lib/graphql/schema/member/cached_graphql_definition.rb000066400000000000000000000016671414121453000266720ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member # Adds a layer of caching over user-supplied `.to_graphql` methods. # Users override `.to_graphql`, but all runtime code should use `.graphql_definition`. # @api private # @see concrete classes that extend this, eg {Schema::Object} module CachedGraphQLDefinition # A cached result of {.to_graphql}. # It's cached here so that user-overridden {.to_graphql} implementations # are also cached def graphql_definition @graphql_definition ||= to_graphql end # This is for a common interface with .define-based types def type_class self end # Wipe out the cached graphql_definition so that `.to_graphql` will be called again. def initialize_copy(original) super @graphql_definition = nil end end end end end graphql-ruby-1.11.10/lib/graphql/schema/member/graphql_type_names.rb000066400000000000000000000007311414121453000254060ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member # These constants are interpreted as GraphQL types when defining fields or arguments # # @example # field :is_draft, Boolean, null: false # field :id, ID, null: false # field :score, Int, null: false # # @api private module GraphQLTypeNames Boolean = "Boolean" ID = "ID" Int = "Int" end end end end graphql-ruby-1.11.10/lib/graphql/schema/member/has_arguments.rb000066400000000000000000000220541414121453000243660ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module HasArguments def self.included(cls) cls.extend(ArgumentClassAccessor) cls.include(ArgumentObjectLoader) end def self.extended(cls) cls.extend(ArgumentClassAccessor) cls.include(ArgumentObjectLoader) end # @see {GraphQL::Schema::Argument#initialize} for parameters # @return [GraphQL::Schema::Argument] An instance of {arguments_class}, created from `*args` def argument(*args, **kwargs, &block) kwargs[:owner] = self loads = kwargs[:loads] if loads name = args[0] name_as_string = name.to_s inferred_arg_name = case name_as_string when /_id$/ name_as_string.sub(/_id$/, "").to_sym when /_ids$/ name_as_string.sub(/_ids$/, "") .sub(/([^s])$/, "\\1s") .to_sym else name end kwargs[:as] ||= inferred_arg_name end arg_defn = self.argument_class.new(*args, **kwargs, &block) add_argument(arg_defn) end # Register this argument with the class. # @param arg_defn [GraphQL::Schema::Argument] # @return [GraphQL::Schema::Argument] def add_argument(arg_defn) @own_arguments ||= {} own_arguments[arg_defn.name] = arg_defn arg_defn end # @return [Hash GraphQL::Schema::Argument] Arguments defined on this thing, keyed by name. Includes inherited definitions def arguments inherited_arguments = ((self.is_a?(Class) && superclass.respond_to?(:arguments)) ? superclass.arguments : nil) # Local definitions override inherited ones if inherited_arguments inherited_arguments.merge(own_arguments) else own_arguments end end # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name. def get_argument(argument_name) a = own_arguments[argument_name] if a || !self.is_a?(Class) a else for ancestor in ancestors if ancestor.respond_to?(:own_arguments) && a = ancestor.own_arguments[argument_name] return a end end nil end end # @param new_arg_class [Class] A class to use for building argument definitions def argument_class(new_arg_class = nil) self.class.argument_class(new_arg_class) end # @api private # @param values [Hash] # @param context [GraphQL::Query::Context] # @return [Hash, Execution::Lazy] def coerce_arguments(parent_object, values, context) # Cache this hash to avoid re-merging it arg_defns = self.arguments if arg_defns.empty? GraphQL::Execution::Interpreter::Arguments.new(argument_values: nil) else argument_values = {} arg_lazies = arg_defns.map do |arg_name, arg_defn| arg_key = arg_defn.keyword has_value = false default_used = false if values.key?(arg_name) has_value = true value = values[arg_name] elsif values.key?(arg_key) has_value = true value = values[arg_key] elsif arg_defn.default_value? has_value = true value = arg_defn.default_value default_used = true end if has_value loads = arg_defn.loads loaded_value = nil if loads && !arg_defn.from_resolver? loaded_value = if arg_defn.type.list? loaded_values = value.map { |val| load_application_object(arg_defn, loads, val, context) } context.schema.after_any_lazies(loaded_values) { |result| result } else load_application_object(arg_defn, loads, value, context) end end coerced_value = if loaded_value loaded_value else context.schema.error_handler.with_error_handling(context) do arg_defn.type.coerce_input(value, context) end end context.schema.after_lazy(coerced_value) do |coerced_value| prepared_value = context.schema.error_handler.with_error_handling(context) do arg_defn.prepare_value(parent_object, coerced_value, context: context) end # TODO code smell to access such a deeply-nested constant in a distant module argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new( value: prepared_value, definition: arg_defn, default_used: default_used, ) end end end context.schema.after_any_lazies(arg_lazies) do GraphQL::Execution::Interpreter::Arguments.new( argument_values: argument_values, ) end end end def arguments_statically_coercible? return @arguments_statically_coercible if defined?(@arguments_statically_coercible) @arguments_statically_coercible = arguments.each_value.all?(&:statically_coercible?) end module ArgumentClassAccessor def argument_class(new_arg_class = nil) if new_arg_class @argument_class = new_arg_class elsif defined?(@argument_class) && @argument_class @argument_class else superclass.respond_to?(:argument_class) ? superclass.argument_class : GraphQL::Schema::Argument end end end module ArgumentObjectLoader # Look up the corresponding object for a provided ID. # By default, it uses Relay-style {Schema.object_from_id}, # override this to find objects another way. # # @param type [Class, Module] A GraphQL type definition # @param id [String] A client-provided to look up # @param context [GraphQL::Query::Context] the current context def object_from_id(type, id, context) context.schema.object_from_id(id, context) end def load_application_object(argument, lookup_as_type, id, context) # See if any object can be found for this ID if id.nil? return nil end loaded_application_object = object_from_id(lookup_as_type, id, context) context.schema.after_lazy(loaded_application_object) do |application_object| if application_object.nil? err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object) load_application_object_failed(err) end # Double-check that the located object is actually of this type # (Don't want to allow arbitrary access to objects this way) resolved_application_object_type = context.schema.resolve_type(lookup_as_type, application_object, context) context.schema.after_lazy(resolved_application_object_type) do |application_object_type| possible_object_types = context.warden.possible_types(lookup_as_type) if !possible_object_types.include?(application_object_type) err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object) load_application_object_failed(err) else # This object was loaded successfully # and resolved to the right type, # now apply the `.authorized?` class method if there is one if (class_based_type = application_object_type.type_class) context.schema.after_lazy(class_based_type.authorized?(application_object, context)) do |authed| if authed application_object else raise GraphQL::UnauthorizedError.new( object: application_object, type: class_based_type, context: context, ) end end else application_object end end end end end def load_application_object_failed(err) raise err end end NO_ARGUMENTS = {}.freeze def own_arguments @own_arguments || NO_ARGUMENTS end end end end end graphql-ruby-1.11.10/lib/graphql/schema/member/has_ast_node.rb000066400000000000000000000007541414121453000241600ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module HasAstNode # If this schema was parsed from a `.graphql` file (or other SDL), # this is the AST node that defined this part of the schema. def ast_node(new_ast_node = nil) if new_ast_node @ast_node = new_ast_node elsif defined?(@ast_node) @ast_node else nil end end end end end end graphql-ruby-1.11.10/lib/graphql/schema/member/has_fields.rb000066400000000000000000000110121414121453000236170ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member # Shared code for Object and Interface module HasFields # Add a field to this object or interface with the given definition # @see {GraphQL::Schema::Field#initialize} for method signature # @return [GraphQL::Schema::Field] def field(*args, **kwargs, &block) field_defn = field_class.from_options(*args, owner: self, **kwargs, &block) add_field(field_defn) field_defn end # @return [Hash GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields def fields # Local overrides take precedence over inherited fields all_fields = {} ancestors.reverse_each do |ancestor| if ancestor.respond_to?(:own_fields) all_fields.merge!(ancestor.own_fields) end end all_fields end def get_field(field_name) if (f = own_fields[field_name]) f else for ancestor in ancestors if ancestor.respond_to?(:own_fields) && f = ancestor.own_fields[field_name] return f end end nil end end # A list of Ruby keywords. # # @api private RUBY_KEYWORDS = [:class, :module, :def, :undef, :begin, :rescue, :ensure, :end, :if, :unless, :then, :elsif, :else, :case, :when, :while, :until, :for, :break, :next, :redo, :retry, :in, :do, :return, :yield, :super, :self, :nil, :true, :false, :and, :or, :not, :alias, :defined?, :BEGIN, :END, :__LINE__, :__FILE__] # A list of GraphQL-Ruby keywords. # # @api private GRAPHQL_RUBY_KEYWORDS = [:context, :object, :raw_value] # A list of field names that we should advise users to pick a different # resolve method name. # # @api private CONFLICT_FIELD_NAMES = Set.new(GRAPHQL_RUBY_KEYWORDS + RUBY_KEYWORDS + Object.instance_methods) # Register this field with the class, overriding a previous one if needed. # @param field_defn [GraphQL::Schema::Field] # @return [void] def add_field(field_defn, method_conflict_warning: field_defn.method_conflict_warning?) # Check that `field_defn.original_name` equals `resolver_method` and `method_sym` -- # that shows that no override value was given manually. if method_conflict_warning && CONFLICT_FIELD_NAMES.include?(field_defn.resolver_method) && field_defn.original_name == field_defn.resolver_method && field_defn.original_name == field_defn.method_sym warn(conflict_field_name_warning(field_defn)) end own_fields[field_defn.name] = field_defn nil end # @return [Class] The class to initialize when adding fields to this kind of schema member def field_class(new_field_class = nil) if new_field_class @field_class = new_field_class elsif defined?(@field_class) && @field_class @field_class elsif self.is_a?(Class) superclass.respond_to?(:field_class) ? superclass.field_class : GraphQL::Schema::Field else ancestor = ancestors[1..-1].find { |a| a.respond_to?(:field_class) && a.field_class } ancestor ? ancestor.field_class : GraphQL::Schema::Field end end def global_id_field(field_name, **kwargs) id_resolver = GraphQL::Relay::GlobalIdResolve.new(type: self) field field_name, "ID", **kwargs, null: false define_method(field_name) do id_resolver.call(object, {}, context) end end # @return [Array] Fields defined on this class _specifically_, not parent classes def own_fields @own_fields ||= {} end private # @param [GraphQL::Schema::Field] # @return [String] A warning to give when this field definition might conflict with a built-in method def conflict_field_name_warning(field_defn) "#{self.graphql_name}'s `field :#{field_defn.original_name}` conflicts with a built-in method, use `resolver_method:` to pick a different resolver method for this field (for example, `resolver_method: :resolve_#{field_defn.resolver_method}` and `def resolve_#{field_defn.resolver_method}`). Or use `method_conflict_warning: false` to suppress this warning." end end end end end graphql-ruby-1.11.10/lib/graphql/schema/member/has_path.rb000066400000000000000000000011551414121453000233140ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module HasPath # @return [String] A description of this member's place in the GraphQL schema def path path_str = if self.respond_to?(:graphql_name) self.graphql_name elsif self.class.respond_to?(:graphql_name) # Instances of resolvers self.class.graphql_name end if self.respond_to?(:owner) && owner.respond_to?(:path) path_str = "#{owner.path}.#{path_str}" end path_str end end end end end graphql-ruby-1.11.10/lib/graphql/schema/member/has_unresolved_type_error.rb000066400000000000000000000006241414121453000270200ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member # Set up a type-specific error to make debugging & bug tracker integration better module HasUnresolvedTypeError private def add_unresolved_type_error(child_class) child_class.const_set(:UnresolvedTypeError, Class.new(GraphQL::UnresolvedTypeError)) end end end end end graphql-ruby-1.11.10/lib/graphql/schema/member/instrumentation.rb000066400000000000000000000112571414121453000247740ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../object.rb module GraphQL class Schema class Member module Instrumentation module_function def instrument(type, field) return_type = field.type.unwrap if (return_type.is_a?(GraphQL::ObjectType) && return_type.metadata[:type_class]) || return_type.is_a?(GraphQL::InterfaceType) || (return_type.is_a?(GraphQL::UnionType) && return_type.possible_types.any? { |t| t.metadata[:type_class] }) field = apply_proxy(field) end field end def before_query(query) # Get the root type for this query root_node = query.irep_selection if root_node.nil? # It's an invalid query, nothing to do here else root_type = query.irep_selection.return_type # If it has a wrapper, apply it wrapper_class = root_type.metadata[:type_class] if wrapper_class new_root_value = wrapper_class.authorized_new(query.root_value, query.context) new_root_value = query.schema.sync_lazy(new_root_value) if new_root_value.nil? # This is definitely a hack, # but we need some way to tell execute.rb not to run. query.context[:__root_unauthorized] = true end query.root_value = new_root_value end end end def after_query(_query) end private module_function def apply_proxy(field) resolve_proc = field.resolve_proc lazy_resolve_proc = field.lazy_resolve_proc inner_return_type = field.type.unwrap depth = list_depth(field.type) field.redefine( resolve: ProxiedResolve.new(inner_resolve: resolve_proc, list_depth: depth, inner_return_type: inner_return_type), lazy_resolve: ProxiedResolve.new(inner_resolve: lazy_resolve_proc, list_depth: depth, inner_return_type: inner_return_type), ) end def list_depth(type, starting_at = 0) case type when GraphQL::ListType list_depth(type.of_type, starting_at + 1) when GraphQL::NonNullType list_depth(type.of_type, starting_at) else starting_at end end class ProxiedResolve def initialize(inner_resolve:, list_depth:, inner_return_type:) @inner_resolve = inner_resolve @inner_return_type = inner_return_type @list_depth = list_depth end def call(obj, args, ctx) result = @inner_resolve.call(obj, args, ctx) if ctx.skip == result || ctx.schema.lazy?(result) || result.nil? || execution_errors?(result) || ctx.wrapped_object result else ctx.wrapped_object = true proxy_to_depth(result, @list_depth, ctx) end end private def execution_errors?(result) result.is_a?(GraphQL::ExecutionError) || (result.is_a?(Array) && result.any? && result.all? { |v| v.is_a?(GraphQL::ExecutionError) }) end def proxy_to_depth(inner_obj, depth, ctx) if depth > 0 inner_obj.map { |i| proxy_to_depth(i, depth - 1, ctx) } else ctx.schema.after_lazy(inner_obj) do |inner_obj| if inner_obj.nil? # For lists with nil, we need another nil check here nil else concrete_type_or_lazy = case @inner_return_type when GraphQL::UnionType, GraphQL::InterfaceType ctx.query.resolve_type(@inner_return_type, inner_obj) when GraphQL::ObjectType @inner_return_type else raise "unexpected proxying type #{@inner_return_type} for #{inner_obj} at #{ctx.owner_type}.#{ctx.field.name}" end # .resolve_type may have returned a lazy ctx.schema.after_lazy(concrete_type_or_lazy) do |concrete_type| if concrete_type && (object_class = concrete_type.metadata[:type_class]) # use the query-level context here, since it won't be field-specific anyways query_ctx = ctx.query.context object_class.authorized_new(inner_obj, query_ctx) else inner_obj end end end end end end end end end end end graphql-ruby-1.11.10/lib/graphql/schema/member/relay_shortcuts.rb000066400000000000000000000025351414121453000247620ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module RelayShortcuts def edge_type_class(new_edge_type_class = nil) if new_edge_type_class @edge_type_class = new_edge_type_class else @edge_type_class || find_inherited_value(:edge_type_class, Types::Relay::BaseEdge) end end def connection_type_class(new_connection_type_class = nil) if new_connection_type_class @connection_type_class = new_connection_type_class else @connection_type_class || find_inherited_value(:connection_type_class, Types::Relay::BaseConnection) end end def edge_type @edge_type ||= begin edge_name = self.graphql_name + "Edge" node_type_class = self Class.new(edge_type_class) do graphql_name(edge_name) node_type(node_type_class) end end end def connection_type @connection_type ||= begin conn_name = self.graphql_name + "Connection" edge_type_class = self.edge_type Class.new(connection_type_class) do graphql_name(conn_name) edge_type(edge_type_class) end end end end end end end graphql-ruby-1.11.10/lib/graphql/schema/member/scoped.rb000066400000000000000000000011621414121453000230000ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module Scoped # This is called when a field has `scope: true`. # The field's return type class receives this call. # # By default, it's a no-op. Override it to scope your objects. # # @param items [Object] Some list-like object (eg, Array, ActiveRecord::Relation) # @param context [GraphQL::Query::Context] # @return [Object] Another list-like object, scoped to the current context def scope_items(items, context) items end end end end end graphql-ruby-1.11.10/lib/graphql/schema/member/type_system_helpers.rb000066400000000000000000000020061414121453000256300ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module TypeSystemHelpers # @return [Schema::NonNull] Make a non-null-type representation of this type def to_non_null_type @to_non_null_type ||= GraphQL::Schema::NonNull.new(self) end # @return [Schema::List] Make a list-type representation of this type def to_list_type @to_list_type ||= GraphQL::Schema::List.new(self) end # @return [Boolean] true if this is a non-nullable type. A nullable list of non-nullables is considered nullable. def non_null? false end # @return [Boolean] true if this is a list type. A non-nullable list is considered a list. def list? false end def to_type_signature graphql_name end # @return [GraphQL::TypeKinds::TypeKind] def kind raise GraphQL::RequiredImplementationMissingError end end end end end graphql-ruby-1.11.10/lib/graphql/schema/member/validates_input.rb000066400000000000000000000013451414121453000247210ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Member module ValidatesInput def valid_input?(val, ctx) validate_input(val, ctx).valid? end def validate_input(val, ctx) if val.nil? GraphQL::Query::InputValidationResult.new else validate_non_null_input(val, ctx) end end def valid_isolated_input?(v) valid_input?(v, GraphQL::Query::NullContext) end def coerce_isolated_input(v) coerce_input(v, GraphQL::Query::NullContext) end def coerce_isolated_result(v) coerce_result(v, GraphQL::Query::NullContext) end end end end end graphql-ruby-1.11.10/lib/graphql/schema/middleware_chain.rb000066400000000000000000000042531414121453000235370ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Given {steps} and {arguments}, call steps in order, passing `(*arguments, next_step)`. # # Steps should call `next_step.call` to continue the chain, or _not_ call it to stop the chain. class MiddlewareChain extend Forwardable # @return [Array<#call(*args)>] Steps in this chain, will be called with arguments and `next_middleware` attr_reader :steps, :final_step def initialize(steps: [], final_step: nil) @steps = steps @final_step = final_step end def initialize_copy(other) super @steps = other.steps.dup end def_delegators :@steps, :[], :first, :insert, :delete def <<(callable) add_middleware(callable) end def push(callable) add_middleware(callable) end def ==(other) steps == other.steps && final_step == other.final_step end def invoke(arguments) invoke_core(0, arguments) end def concat(callables) callables.each { |c| add_middleware(c) } end private def invoke_core(index, arguments) if index >= steps.length final_step.call(*arguments) else steps[index].call(*arguments) { |next_args = arguments| invoke_core(index + 1, next_args) } end end def add_middleware(callable) # TODO: Stop wrapping callables once deprecated middleware becomes unsupported steps << wrap(callable) end # TODO: Remove this code once deprecated middleware becomes unsupported class MiddlewareWrapper attr_reader :callable def initialize(callable) @callable = callable end def call(*args, &next_middleware) callable.call(*args, next_middleware) end end def wrap(callable) if BackwardsCompatibility.get_arity(callable) == 6 warn("Middleware that takes a next_middleware parameter is deprecated (#{callable.inspect}); instead, accept a block and use yield.") MiddlewareWrapper.new(callable) else callable end end end end end graphql-ruby-1.11.10/lib/graphql/schema/mutation.rb000066400000000000000000000060471414121453000221230ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # This base class accepts configuration for a mutation root field, # then it can be hooked up to your mutation root object type. # # If you want to customize how this class generates types, in your base class, # override the various `generate_*` methods. # # @see {GraphQL::Schema::RelayClassicMutation} for an extension of this class with some conventions built-in. # # @example Creating a comment # # Define the mutation: # class Mutations::CreateComment < GraphQL::Schema::Mutation # argument :body, String, required: true # argument :post_id, ID, required: true # # field :comment, Types::Comment, null: true # field :errors, [String], null: false # # def resolve(body:, post_id:) # post = Post.find(post_id) # comment = post.comments.build(body: body, author: context[:current_user]) # if comment.save # # Successful creation, return the created object with no errors # { # comment: comment, # errors: [], # } # else # # Failed save, return the errors to the client # { # comment: nil, # errors: comment.errors.full_messages # } # end # end # end # # # Hook it up to your mutation: # class Types::Mutation < GraphQL::Schema::Object # field :create_comment, mutation: Mutations::CreateComment # end # # # Call it from GraphQL: # result = MySchema.execute <<-GRAPHQL # mutation { # createComment(postId: "1", body: "Nice Post!") { # errors # comment { # body # author { # login # } # } # } # } # GRAPHQL # class Mutation < GraphQL::Schema::Resolver extend GraphQL::Schema::Member::HasFields extend GraphQL::Schema::Resolver::HasPayloadType class << self # Override this method to handle legacy-style usages of `MyMutation.field` def field(*args, **kwargs, &block) if args.empty? raise ArgumentError, "#{name}.field is used for adding fields to this mutation. Use `mutation: #{name}` to attach this mutation instead." else super end end def visible?(context) true end private def conflict_field_name_warning(field_defn) "#{self.graphql_name}'s `field :#{field_defn.name}` conflicts with a built-in method, use `hash_key:` or `method:` to pick a different resolve behavior for this field (for example, `hash_key: :#{field_defn.resolver_method}_value`, and modify the return hash). Or use `method_conflict_warning: false` to suppress this warning." end # Override this to attach self as `mutation` def generate_payload_type payload_class = super payload_class.mutation(self) payload_class end end end end end graphql-ruby-1.11.10/lib/graphql/schema/non_null.rb000066400000000000000000000030171414121453000221010ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Represents a non null type in the schema. # Wraps a {Schema::Member} when it is required. # @see {Schema::Member::TypeSystemHelpers#to_non_null_type} class NonNull < GraphQL::Schema::Wrapper include Schema::Member::ValidatesInput def to_graphql @of_type.graphql_definition.to_non_null_type end # @return [GraphQL::TypeKinds::NON_NULL] def kind GraphQL::TypeKinds::NON_NULL end # @return [true] def non_null? true end # @return [Boolean] True if this type wraps a list type def list? @of_type.list? end def to_type_signature "#{@of_type.to_type_signature}!" end def inspect "#<#{self.class.name} @of_type=#{@of_type.inspect}>" end def validate_input(value, ctx) if value.nil? result = GraphQL::Query::InputValidationResult.new result.add_problem("Expected value to not be null") result else of_type.validate_input(value, ctx) end end # This is for introspection, where it's expected the name will be `null` def graphql_name nil end def coerce_input(value, ctx) of_type.coerce_input(value, ctx) end def coerce_result(value, ctx) of_type.coerce_result(value, ctx) end # This is for implementing introspection def description nil end end end end graphql-ruby-1.11.10/lib/graphql/schema/null_mask.rb000066400000000000000000000002561414121453000222440ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # @api private module NullMask def self.call(member, ctx) false end end end end graphql-ruby-1.11.10/lib/graphql/schema/object.rb000066400000000000000000000172371414121453000215340ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/query/null_context" module GraphQL class Schema class Object < GraphQL::Schema::Member extend GraphQL::Schema::Member::AcceptsDefinition extend GraphQL::Schema::Member::HasFields # @return [Object] the application object this type is wrapping attr_reader :object # @return [GraphQL::Query::Context] the context instance for this query attr_reader :context class << self # This is protected so that we can be sure callers use the public method, {.authorized_new} # @see authorized_new to make instances protected :new # Make a new instance of this type _if_ the auth check passes, # otherwise, raise an error. # # Probably only the framework should call this method. # # This might return a {GraphQL::Execution::Lazy} if the user-provided `.authorized?` # hook returns some lazy value (like a Promise). # # The reason that the auth check is in this wrapper method instead of {.new} is because # of how it might return a Promise. It would be weird if `.new` returned a promise; # It would be a headache to try to maintain Promise-y state inside a {Schema::Object} # instance. So, hopefully this wrapper method will do the job. # # @param object [Object] The thing wrapped by this object # @param context [GraphQL::Query::Context] # @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy] # @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false` def authorized_new(object, context) auth_val = context.query.with_error_handling do begin authorized?(object, context) rescue GraphQL::UnauthorizedError => err context.schema.unauthorized_object(err) end end context.schema.after_lazy(auth_val) do |is_authorized| if is_authorized self.new(object, context) else # It failed the authorization check, so go to the schema's authorized object hook err = GraphQL::UnauthorizedError.new(object: object, type: self, context: context) # If a new value was returned, wrap that instead of the original value begin new_obj = context.schema.unauthorized_object(err) if new_obj self.new(new_obj, context) else nil end end end end end end def initialize(object, context) @object = object @context = context end class << self # Set up a type-specific invalid null error to use when this object's non-null fields wrongly return `nil`. # It should help with debugging and bug tracker integrations. def inherited(child_class) child_class.const_set(:InvalidNullError, GraphQL::InvalidNullError.subclass_for(child_class)) super end def implements(*new_interfaces, **options) new_memberships = [] new_interfaces.each do |int| if int.is_a?(Module) unless int.include?(GraphQL::Schema::Interface) raise "#{int} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules." end new_memberships << int.type_membership_class.new(int, self, **options) # Include the methods here, # `.fields` will use the inheritance chain # to find inherited fields include(int) elsif int.is_a?(GraphQL::InterfaceType) new_memberships << int.type_membership_class.new(int, self, **options) elsif int.is_a?(String) || int.is_a?(GraphQL::Schema::LateBoundType) if options.any? raise ArgumentError, "`implements(...)` doesn't support options with late-loaded types yet. Remove #{options} and open an issue to request this feature." end new_memberships << int else raise ArgumentError, "Unexpected interface definition (expected module): #{int} (#{int.class})" end end # Remove any interfaces which are being replaced (late-bound types are updated in place this way) own_interface_type_memberships.reject! { |old_i_m| old_int_type = old_i_m.respond_to?(:abstract_type) ? old_i_m.abstract_type : old_i_m old_name = Schema::Member::BuildType.to_type_name(old_int_type) new_memberships.any? { |new_i_m| new_int_type = new_i_m.respond_to?(:abstract_type) ? new_i_m.abstract_type : new_i_m new_name = Schema::Member::BuildType.to_type_name(new_int_type) new_name == old_name } } own_interface_type_memberships.concat(new_memberships) end def own_interface_type_memberships @own_interface_type_memberships ||= [] end def interface_type_memberships own_interface_type_memberships + (superclass.respond_to?(:interface_type_memberships) ? superclass.interface_type_memberships : []) end # param context [Query::Context] If omitted, skip filtering. def interfaces(context = GraphQL::Query::NullContext) visible_interfaces = [] unfiltered = context == GraphQL::Query::NullContext own_interface_type_memberships.each do |type_membership| # During initialization, `type_memberships` can hold late-bound types case type_membership when String, Schema::LateBoundType visible_interfaces << type_membership when Schema::TypeMembership if unfiltered || type_membership.visible?(context) visible_interfaces << type_membership.abstract_type end else raise "Invariant: Unexpected type_membership #{type_membership.class}: #{type_membership.inspect}" end end visible_interfaces + (superclass <= GraphQL::Schema::Object ? superclass.interfaces(context) : []) end # @return [Hash GraphQL::Schema::Field>] All of this object's fields, indexed by name # @see get_field A faster way to find one field by name ({#fields} merges hashes of inherited fields; {#get_field} just looks up one field.) def fields all_fields = super interfaces.each do |int| # Include legacy-style interfaces, too if int.is_a?(GraphQL::InterfaceType) int_f = {} int.fields.each do |name, legacy_field| int_f[name] = field_class.from_options(name, field: legacy_field) end all_fields = int_f.merge(all_fields) end end all_fields end # @return [GraphQL::ObjectType] def to_graphql obj_type = GraphQL::ObjectType.new obj_type.name = graphql_name obj_type.description = description obj_type.structural_interface_type_memberships = interface_type_memberships obj_type.introspection = introspection obj_type.mutation = mutation obj_type.ast_node = ast_node fields.each do |field_name, field_inst| field_defn = field_inst.to_graphql obj_type.fields[field_defn.name] = field_defn end obj_type.metadata[:type_class] = self obj_type end def kind GraphQL::TypeKinds::OBJECT end end end end end graphql-ruby-1.11.10/lib/graphql/schema/possible_types.rb000066400000000000000000000030241414121453000233170ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Find the members of a union or interface within a given schema. # # (Although its members never change, unions are handled this way to simplify execution code.) # # Internally, the calculation is cached. It's assumed that schema members _don't_ change after creating the schema! # # @example Get an interface's possible types # possible_types = GraphQL::Schema::PossibleTypes(MySchema) # possible_types.possible_types(MyInterface) # # => [MyObjectType, MyOtherObjectType] class PossibleTypes def initialize(schema) @object_types = schema.types.values.select { |type| type.kind.object? } @interface_implementers = Hash.new do |h1, ctx| h1[ctx] = Hash.new do |h2, int_type| h2[int_type] = @object_types.select { |type| type.interfaces(ctx).include?(int_type) }.sort_by(&:name) end end end def possible_types(type_defn, ctx) case type_defn when Module possible_types(type_defn.graphql_definition, ctx) when GraphQL::UnionType type_defn.possible_types(ctx) when GraphQL::InterfaceType interface_implementers(ctx, type_defn) when GraphQL::BaseType [type_defn] else raise "Unexpected possible_types object: #{type_defn.inspect}" end end def interface_implementers(ctx, type_defn) @interface_implementers[ctx][type_defn] end end end end graphql-ruby-1.11.10/lib/graphql/schema/printer.rb000066400000000000000000000066271414121453000217520ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Used to convert your {GraphQL::Schema} to a GraphQL schema string # # @example print your schema to standard output (via helper) # MySchema = GraphQL::Schema.define(query: QueryType) # puts GraphQL::Schema::Printer.print_schema(MySchema) # # @example print your schema to standard output # MySchema = GraphQL::Schema.define(query: QueryType) # puts GraphQL::Schema::Printer.new(MySchema).print_schema # # @example print a single type to standard output # query_root = GraphQL::ObjectType.define do # name "Query" # description "The query root of this schema" # # field :post do # type post_type # resolve ->(obj, args, ctx) { Post.find(args["id"]) } # end # end # # post_type = GraphQL::ObjectType.define do # name "Post" # description "A blog post" # # field :id, !types.ID # field :title, !types.String # field :body, !types.String # end # # MySchema = GraphQL::Schema.define(query: query_root) # # printer = GraphQL::Schema::Printer.new(MySchema) # puts printer.print_type(post_type) # class Printer < GraphQL::Language::Printer attr_reader :schema, :warden # @param schema [GraphQL::Schema] # @param context [Hash] # @param only [<#call(member, ctx)>] # @param except [<#call(member, ctx)>] # @param introspection [Boolean] Should include the introspection types in the string? def initialize(schema, context: nil, only: nil, except: nil, introspection: false) @document_from_schema = GraphQL::Language::DocumentFromSchemaDefinition.new( schema, context: context, only: only, except: except, include_introspection_types: introspection, ) @document = @document_from_schema.document @schema = schema end # Return the GraphQL schema string for the introspection type system def self.print_introspection_schema query_root = ObjectType.define(name: "Root") do field :throwaway_field, types.String end schema = GraphQL::Schema.define(query: query_root) introspection_schema_ast = GraphQL::Language::DocumentFromSchemaDefinition.new( schema, except: ->(member, _) { member.name == "Root" }, include_introspection_types: true, include_built_in_directives: true, ).document introspection_schema_ast.to_query_string(printer: IntrospectionPrinter.new) end # Return a GraphQL schema string for the defined types in the schema # @param schema [GraphQL::Schema] # @param context [Hash] # @param only [<#call(member, ctx)>] # @param except [<#call(member, ctx)>] def self.print_schema(schema, **args) printer = new(schema, **args) printer.print_schema end # Return a GraphQL schema string for the defined types in the schema def print_schema print(@document) end def print_type(type) node = @document_from_schema.build_type_definition_node(type) print(node) end class IntrospectionPrinter < GraphQL::Language::Printer def print_schema_definition(schema) "schema {\n query: Root\n}" end end end end end graphql-ruby-1.11.10/lib/graphql/schema/relay_classic_mutation.rb000066400000000000000000000125711414121453000250170ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/types/string" module GraphQL class Schema # Mutations that extend this base class get some conventions added for free: # # - An argument called `clientMutationId` is _always_ added, but it's not passed # to the resolve method. The value is re-inserted to the response. (It's for # client libraries to manage optimistic updates.) # - The returned object type always has a field called `clientMutationId` to support that. # - The mutation accepts one argument called `input`, `argument`s defined in the mutation # class are added to that input object, which is generated by the mutation. # # These conventions were first specified by Relay Classic, but they come in handy: # # - `clientMutationId` supports optimistic updates and cache rollbacks on the client # - using a single `input:` argument makes it easy to post whole JSON objects to the mutation # using one GraphQL variable (`$input`) instead of making a separate variable for each argument. # # @see {GraphQL::Schema::Mutation} for an example, it's basically the same. # class RelayClassicMutation < GraphQL::Schema::Mutation # The payload should always include this field field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.", null: true) # Relay classic default: null(true) # Override {GraphQL::Schema::Resolver#resolve_with_support} to # delete `client_mutation_id` from the kwargs. def resolve_with_support(**inputs) # Without the interpreter, the inputs are unwrapped by an instrumenter. # But when using the interpreter, no instrumenters are applied. if context.interpreter? input = inputs[:input].to_kwargs new_extras = field ? field.extras : [] all_extras = self.class.extras + new_extras # Transfer these from the top-level hash to the # shortcutted `input:` object all_extras.each do |ext| # It's possible that the `extra` was not passed along by this point, # don't re-add it if it wasn't given here. if inputs.key?(ext) input[ext] = inputs[ext] end end else input = inputs end if input # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are. input_kwargs = input.to_h client_mutation_id = input_kwargs.delete(:client_mutation_id) else # Relay Classic Mutations with no `argument`s # don't require `input:` input_kwargs = {} end return_value = if input_kwargs.any? super(**input_kwargs) else super() end # Again, this is done by an instrumenter when using non-interpreter execution. if context.interpreter? context.schema.after_lazy(return_value) do |return_hash| # It might be an error if return_hash.is_a?(Hash) return_hash[:client_mutation_id] = client_mutation_id end return_hash end else return_value end end class << self # The base class for generated input object types # @param new_class [Class] The base class to use for generating input object definitions # @return [Class] The base class for this mutation's generated input object (default is {GraphQL::Schema::InputObject}) def input_object_class(new_class = nil) if new_class @input_object_class = new_class end @input_object_class || (superclass.respond_to?(:input_object_class) ? superclass.input_object_class : GraphQL::Schema::InputObject) end # @param new_input_type [Class, nil] If provided, it configures this mutation to accept `new_input_type` instead of generating an input type # @return [Class] The generated {Schema::InputObject} class for this mutation's `input` def input_type(new_input_type = nil) if new_input_type @input_type = new_input_type end @input_type ||= generate_input_type end # Extend {Schema::Mutation.field_options} to add the `input` argument def field_options sig = super # Arguments were added at the root, but they should be nested sig[:arguments].clear sig[:arguments][:input] = { type: input_type, required: true, description: "Parameters for #{graphql_name}" } sig end private # Generate the input type for the `input:` argument # To customize how input objects are generated, override this method # @return [Class] a subclass of {.input_object_class} def generate_input_type mutation_args = arguments mutation_name = graphql_name mutation_class = self Class.new(input_object_class) do graphql_name("#{mutation_name}Input") description("Autogenerated input type of #{mutation_name}") mutation(mutation_class) mutation_args.each do |_name, arg| add_argument(arg) end argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false end end end end end end graphql-ruby-1.11.10/lib/graphql/schema/rescue_middleware.rb000066400000000000000000000037021414121453000237410ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # - Store a table of errors & handlers # - Rescue errors in a middleware chain, then check for a handler # - If a handler is found, use it & return a {GraphQL::ExecutionError} # - If no handler is found, re-raise the error class RescueMiddleware # @return [Hash] `{class => proc}` pairs for handling errors attr_reader :rescue_table def initialize @rescue_table = {} end # @example Rescue from not-found by telling the user # MySchema.rescue_from(ActiveRecord::RecordNotFound) { "An item could not be found" } # # @param error_classes [Class] one or more classes of errors to rescue from # @yield [err] A handler to return a message for these error instances # @yieldparam [Exception] an error that was rescued # @yieldreturn [String] message to put in GraphQL response def rescue_from(*error_classes, &block) error_classes.map{ |error_class| rescue_table[error_class] = block } end # Remove the handler for `error_classs` # @param error_class [Class] the error class whose handler should be removed def remove_handler(*error_classes) error_classes.map{ |error_class| rescue_table.delete(error_class) } end # Implement the requirement for {GraphQL::Schema::MiddlewareChain} def call(*args) begin yield rescue StandardError => err attempt_rescue(err) end end private def attempt_rescue(err) rescue_table.each { |klass, handler| if klass.is_a?(Class) && err.is_a?(klass) && handler result = handler.call(err) case result when String return GraphQL::ExecutionError.new(result) when GraphQL::ExecutionError return result end end } raise(err) end end end end graphql-ruby-1.11.10/lib/graphql/schema/resolver.rb000066400000000000000000000327061414121453000221250ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/schema/resolver/has_payload_type" module GraphQL class Schema # A class-based container for field configuration and resolution logic. It supports: # # - Arguments, via `.argument(...)` helper, which will be applied to the field. # - Return type, via `.type(..., null: ...)`, which will be applied to the field. # - Description, via `.description(...)`, which will be applied to the field # - Resolution, via `#resolve(**args)` method, which will be called to resolve the field. # - `#object` and `#context` accessors for use during `#resolve`. # # Resolvers can be attached with the `resolver:` option in a `field(...)` call. # # A resolver's configuration may be overridden with other keywords in the `field(...)` call. # # See the {.field_options} to see how a Resolver becomes a set of field configuration options. # # @see {GraphQL::Schema::Mutation} for a concrete subclass of `Resolver`. # @see {GraphQL::Function} `Resolver` is a replacement for `GraphQL::Function` class Resolver include Schema::Member::GraphQLTypeNames # Really we only need description from here, but: extend Schema::Member::BaseDSLMethods extend GraphQL::Schema::Member::HasArguments include Schema::Member::HasPath extend Schema::Member::HasPath # @param object [Object] the initialize object, pass to {Query.initialize} as `root_value` # @param context [GraphQL::Query::Context] # @param field [GraphQL::Schema::Field] def initialize(object:, context:, field:) @object = object @context = context @field = field # Since this hash is constantly rebuilt, cache it for this call @arguments_by_keyword = {} self.class.arguments.each do |name, arg| @arguments_by_keyword[arg.keyword] = arg end @arguments_loads_as_type = self.class.arguments_loads_as_type @prepared_arguments = nil end # @return [Object] The application object this field is being resolved on attr_reader :object # @return [GraphQL::Query::Context] attr_reader :context # @return [GraphQL::Schema::Field] attr_reader :field def arguments @prepared_arguments || raise("Arguments have not been prepared yet, still waiting for #load_arguments to resolve. (Call `.arguments` later in the code.)") end # This method is _actually_ called by the runtime, # it does some preparation and then eventually calls # the user-defined `#resolve` method. # @api private def resolve_with_support(**args) # First call the ready? hook which may raise ready_val = if args.any? ready?(**args) else ready? end context.schema.after_lazy(ready_val) do |is_ready, ready_early_return| if ready_early_return if is_ready != false raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{authorized_result.inspect}, #{ready_early_return.inspect}]" else ready_early_return end elsif is_ready # Then call each prepare hook, which may return a different value # for that argument, or may return a lazy object load_arguments_val = load_arguments(args) context.schema.after_lazy(load_arguments_val) do |loaded_args| @prepared_arguments = loaded_args # Then call `authorized?`, which may raise or may return a lazy object authorized_val = if loaded_args.any? authorized?(**loaded_args) else authorized? end context.schema.after_lazy(authorized_val) do |(authorized_result, early_return)| # If the `authorized?` returned two values, `false, early_return`, # then use the early return value instead of continuing if early_return if authorized_result == false early_return else raise "Unexpected result from #authorized? (expected `true`, `false` or `[false, {...}]`): [#{authorized_result.inspect}, #{early_return.inspect}]" end elsif authorized_result # Finally, all the hooks have passed, so resolve it if loaded_args.any? public_send(self.class.resolve_method, **loaded_args) else public_send(self.class.resolve_method) end else nil end end end end end end # Do the work. Everything happens here. # @return [Object] An object corresponding to the return type def resolve(**args) raise GraphQL::RequiredImplementationMissingError, "#{self.class.name}#resolve should execute the field's logic" end # Called before arguments are prepared. # Implement this hook to make checks before doing any work. # # If it returns a lazy object (like a promise), it will be synced by GraphQL # (but the resulting value won't be used). # # @param args [Hash] The input arguments, if there are any # @raise [GraphQL::ExecutionError] To add an error to the response # @raise [GraphQL::UnauthorizedError] To signal an authorization failure # @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.) def ready?(**args) true end # Called after arguments are loaded, but before resolving. # # Override it to check everything before calling the mutation. # @param inputs [Hash] The input arguments # @raise [GraphQL::ExecutionError] To add an error to the response # @raise [GraphQL::UnauthorizedError] To signal an authorization failure # @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.) def authorized?(**inputs) self.class.arguments.each_value do |argument| arg_keyword = argument.keyword if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value) arg_auth, err = argument.authorized?(self, arg_value, context) if !arg_auth return arg_auth, err else true end else true end end end private def load_arguments(args) prepared_args = {} prepare_lazies = [] args.each do |key, value| arg_defn = @arguments_by_keyword[key] if arg_defn if value.nil? prepared_args[key] = value else prepped_value = prepared_args[key] = load_argument(key, value) if context.schema.lazy?(prepped_value) prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value| prepared_args[key] = finished_prepped_value end end end else # These are `extras: [...]` prepared_args[key] = value end end # Avoid returning a lazy if none are needed if prepare_lazies.any? GraphQL::Execution::Lazy.all(prepare_lazies).then { prepared_args } else prepared_args end end def load_argument(name, value) public_send("load_#{name}", value) end class << self # Default `:resolve` set below. # @return [Symbol] The method to call on instances of this object to resolve the field def resolve_method(new_method = nil) if new_method @resolve_method = new_method end @resolve_method || (superclass.respond_to?(:resolve_method) ? superclass.resolve_method : :resolve) end # Additional info injected into {#resolve} # @see {GraphQL::Schema::Field#extras} def extras(new_extras = nil) if new_extras @own_extras = new_extras end own_extras = @own_extras || [] own_extras + (superclass.respond_to?(:extras) ? superclass.extras : []) end # Specifies whether or not the field is nullable. Defaults to `true` # TODO unify with {#type} # @param allow_null [Boolean] Whether or not the response can be null def null(allow_null = nil) if !allow_null.nil? @null = allow_null end @null.nil? ? (superclass.respond_to?(:null) ? superclass.null : true) : @null end # Call this method to get the return type of the field, # or use it as a configuration method to assign a return type # instead of generating one. # TODO unify with {#null} # @param new_type [Class, Array, nil] If a type definition class is provided, it will be used as the return type of the field # @param null [true, false] Whether or not the field may return `nil` # @return [Class] The type which this field returns. def type(new_type = nil, null: nil) if new_type if null.nil? raise ArgumentError, "required argument `null:` is missing" end @type_expr = new_type @null = null else if @type_expr GraphQL::Schema::Member::BuildType.parse_type(@type_expr, null: @null) elsif superclass.respond_to?(:type) superclass.type else nil end end end # Specifies the complexity of the field. Defaults to `1` # @return [Integer, Proc] def complexity(new_complexity = nil) if new_complexity @complexity = new_complexity end @complexity || (superclass.respond_to?(:complexity) ? superclass.complexity : 1) end def broadcastable(new_broadcastable) @broadcastable = new_broadcastable end # @return [Boolean, nil] def broadcastable? if defined?(@broadcastable) @broadcastable else (superclass.respond_to?(:broadcastable?) ? superclass.broadcastable? : nil) end end def field_options { type: type_expr, description: description, extras: extras, resolver_method: :resolve_with_support, resolver_class: self, arguments: arguments, null: null, complexity: complexity, extensions: extensions, broadcastable: broadcastable?, } end # A non-normalized type configuration, without `null` applied def type_expr @type_expr || (superclass.respond_to?(:type_expr) ? superclass.type_expr : nil) end # Add an argument to this field's signature, but # also add some preparation hook methods which will be used for this argument # @see {GraphQL::Schema::Argument#initialize} for the signature def argument(*args, **kwargs, &block) loads = kwargs[:loads] # Use `from_resolver: true` to short-circuit the InputObject's own `loads:` implementation # so that we can support `#load_{x}` methods below. arg_defn = super(*args, from_resolver: true, **kwargs) own_arguments_loads_as_type[arg_defn.keyword] = loads if loads if loads && arg_defn.type.list? class_eval <<-RUBY, __FILE__, __LINE__ + 1 def load_#{arg_defn.keyword}(values) argument = @arguments_by_keyword[:#{arg_defn.keyword}] lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}] context.schema.after_lazy(values) do |values2| GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, lookup_as_type, value, context) }) end end RUBY elsif loads class_eval <<-RUBY, __FILE__, __LINE__ + 1 def load_#{arg_defn.keyword}(value) argument = @arguments_by_keyword[:#{arg_defn.keyword}] lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}] load_application_object(argument, lookup_as_type, value, context) end RUBY else class_eval <<-RUBY, __FILE__, __LINE__ + 1 def load_#{arg_defn.keyword}(value) value end RUBY end arg_defn end # @api private def arguments_loads_as_type inherited_lookups = superclass.respond_to?(:arguments_loads_as_type) ? superclass.arguments_loads_as_type : {} inherited_lookups.merge(own_arguments_loads_as_type) end # Registers new extension # @param extension [Class] Extension class # @param options [Hash] Optional extension options def extension(extension, **options) extensions << {extension => options} end # @api private def extensions @extensions ||= [] end private def own_arguments_loads_as_type @own_arguments_loads_as_type ||= {} end end end end end graphql-ruby-1.11.10/lib/graphql/schema/resolver/000077500000000000000000000000001414121453000215705ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/schema/resolver/has_payload_type.rb000066400000000000000000000050341414121453000254440ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Resolver # Adds `field(...)` helper to resolvers so that they can # generate payload types. # # Or, an already-defined one can be attached with `payload_type(...)`. module HasPayloadType # Call this method to get the derived return type of the mutation, # or use it as a configuration method to assign a return type # instead of generating one. # @param new_payload_type [Class, nil] If a type definition class is provided, it will be used as the return type of the mutation field # @return [Class] The object type which this mutation returns. def payload_type(new_payload_type = nil) if new_payload_type @payload_type = new_payload_type end @payload_type ||= generate_payload_type end alias :type :payload_type alias :type_expr :payload_type def field_class(new_class = nil) if new_class @field_class = new_class elsif defined?(@field_class) && @field_class @field_class else find_inherited_value(:field_class, GraphQL::Schema::Field) end end # An object class to use for deriving return types # @param new_class [Class, nil] Defaults to {GraphQL::Schema::Object} # @return [Class] def object_class(new_class = nil) if new_class @object_class = new_class else @object_class || find_inherited_value(:object_class, GraphQL::Schema::Object) end end private # Build a subclass of {.object_class} based on `self`. # This value will be cached as `{.payload_type}`. # Override this hook to customize return type generation. def generate_payload_type resolver_name = graphql_name resolver_fields = fields Class.new(object_class) do graphql_name("#{resolver_name}Payload") description("Autogenerated return type of #{resolver_name}") resolver_fields.each do |name, f| # Reattach the already-defined field here # (The field's `.owner` will still point to the mutation, not the object type, I think) # Don't re-warn about a method conflict. Since this type is generated, it should be fixed in the resolver instead. add_field(f, method_conflict_warning: false) end end end end end end end graphql-ruby-1.11.10/lib/graphql/schema/scalar.rb000066400000000000000000000035551414121453000215310ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Scalar < GraphQL::Schema::Member extend GraphQL::Schema::Member::AcceptsDefinition extend GraphQL::Schema::Member::ValidatesInput class << self def coerce_input(val, ctx) val end def coerce_result(val, ctx) val end def to_graphql type_defn = GraphQL::ScalarType.new type_defn.name = graphql_name type_defn.description = description type_defn.coerce_result = method(:coerce_result) type_defn.coerce_input = method(:coerce_input) type_defn.metadata[:type_class] = self type_defn.default_scalar = default_scalar type_defn.ast_node = ast_node type_defn end def kind GraphQL::TypeKinds::SCALAR end def default_scalar(is_default = nil) if !is_default.nil? @default_scalar = is_default end @default_scalar end def default_scalar? @default_scalar ||= false end def validate_non_null_input(value, ctx) result = Query::InputValidationResult.new coerced_result = begin coerce_input(value, ctx) rescue GraphQL::CoercionError => err err end if coerced_result.nil? str_value = if value == Float::INFINITY "" else " #{GraphQL::Language.serialize(value)}" end result.add_problem("Could not coerce value#{str_value} to #{graphql_name}") elsif coerced_result.is_a?(GraphQL::CoercionError) result.add_problem(coerced_result.message, message: coerced_result.message, extensions: coerced_result.extensions) end result end end end end end graphql-ruby-1.11.10/lib/graphql/schema/subscription.rb000066400000000000000000000066221414121453000230060ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # This class can be extended to create fields on your subscription root. # # It provides hooks for the different parts of the subscription lifecycle: # # - `#authorized?`: called before initial subscription and subsequent updates # - `#subscribe`: called for the initial subscription # - `#update`: called for subsequent update # # Also, `#unsubscribe` terminates the subscription. class Subscription < GraphQL::Schema::Resolver extend GraphQL::Schema::Resolver::HasPayloadType extend GraphQL::Schema::Member::HasFields # The generated payload type is required; If there's no payload, # propagate null. null false def initialize(object:, context:, field:) super # Figure out whether this is an update or an initial subscription @mode = context.query.subscription_update? ? :update : :subscribe end # Implement the {Resolve} API def resolve(**args) # Dispatch based on `@mode`, which will raise a `NoMethodError` if we ever # have an unexpected `@mode` public_send("resolve_#{@mode}", **args) end # Wrap the user-defined `#subscribe` hook def resolve_subscribe(**args) ret_val = args.any? ? subscribe(**args) : subscribe if ret_val == :no_response context.skip else ret_val end end # Default implementation returns the root object. # Override it to return an object or # `:no_response` to return nothing. # # The default is `:no_response`. def subscribe(args = {}) :no_response end # Wrap the user-provided `#update` hook def resolve_update(**args) ret_val = args.any? ? update(**args) : update if ret_val == :no_update throw :graphql_no_subscription_update else ret_val end end # The default implementation returns the root object. # Override it to return `:no_update` if you want to # skip updates sometimes. Or override it to return a different object. def update(args = {}) object end # If an argument is flagged with `loads:` and no object is found for it, # remove this subscription (assuming that the object was deleted in the meantime, # or that it became inaccessible). def load_application_object_failed(err) if @mode == :update unsubscribe end super end # Call this to halt execution and remove this subscription from the system def unsubscribe throw :graphql_subscription_unsubscribed end READING_SCOPE = ::Object.new # Call this method to provide a new subscription_scope; OR # call it without an argument to get the subscription_scope # @param new_scope [Symbol] # @return [Symbol] def self.subscription_scope(new_scope = READING_SCOPE) if new_scope != READING_SCOPE @subscription_scope = new_scope elsif defined?(@subscription_scope) @subscription_scope else find_inherited_value(:subscription_scope) end end # Overriding Resolver#field_options to include subscription_scope def self.field_options super.merge( subscription_scope: subscription_scope ) end end end end graphql-ruby-1.11.10/lib/graphql/schema/timeout.rb000066400000000000000000000113341414121453000217440ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # This plugin will stop resolving new fields after `max_seconds` have elapsed. # After the time has passed, any remaining fields will be `nil`, with errors added # to the `errors` key. Any already-resolved fields will be in the `data` key, so # you'll get a partial response. # # You can subclass `GraphQL::Schema::Timeout` and override `max_seconds` and/or `handle_timeout` # to provide custom logic when a timeout error occurs. # # Note that this will stop a query _in between_ field resolutions, but # it doesn't interrupt long-running `resolve` functions. Be sure to use # timeout options for external connections. For more info, see # www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/ # # @example Stop resolving fields after 2 seconds # class MySchema < GraphQL::Schema # use GraphQL::Schema::Timeout, max_seconds: 2 # end # # @example Notifying Bugsnag and logging a timeout # class MyTimeout < GraphQL::Schema::Timeout # def handle_timeout(error, query) # Rails.logger.warn("GraphQL Timeout: #{error.message}: #{query.query_string}") # Bugsnag.notify(error, {query_string: query.query_string}) # end # end # # class MySchema < GraphQL::Schema # use MyTimeout, max_seconds: 2 # end # class Timeout def self.use(schema, **options) tracer = new(**options) schema.tracer(tracer) end # @param max_seconds [Numeric] how many seconds the query should be allowed to resolve new fields def initialize(max_seconds:) @max_seconds = max_seconds end def trace(key, data) case key when 'execute_multiplex' data.fetch(:multiplex).queries.each do |query| timeout_duration_s = max_seconds(query) timeout_state = if timeout_duration_s == false # if the method returns `false`, don't apply a timeout false else now = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) timeout_at = now + (max_seconds(query) * 1000) { timeout_at: timeout_at, timed_out: false } end query.context.namespace(self.class)[:state] = timeout_state end yield when 'execute_field', 'execute_field_lazy' query_context = data[:context] || data[:query].context timeout_state = query_context.namespace(self.class).fetch(:state) # If the `:state` is `false`, then `max_seconds(query)` opted out of timeout for this query. if timeout_state != false && Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at) error = if data[:context] GraphQL::Schema::Timeout::TimeoutError.new(query_context.parent_type, query_context.field) else field = data.fetch(:field) GraphQL::Schema::Timeout::TimeoutError.new(field.owner, field) end # Only invoke the timeout callback for the first timeout if !timeout_state[:timed_out] timeout_state[:timed_out] = true handle_timeout(error, query_context.query) end error else yield end else yield end end # Called at the start of each query. # The default implementation returns the `max_seconds:` value from installing this plugin. # # @param query [GraphQL::Query] The query that's about to run # @return [Integer, false] The number of seconds after which to interrupt query execution and call {#handle_error}, or `false` to bypass the timeout. def max_seconds(query) @max_seconds end # Invoked when a query times out. # @param error [GraphQL::Schema::Timeout::TimeoutError] # @param query [GraphQL::Error] def handle_timeout(error, query) # override to do something interesting end # This error is raised when a query exceeds `max_seconds`. # Since it's a child of {GraphQL::ExecutionError}, # its message will be added to the response's `errors` key. # # To raise an error that will stop query resolution, use a custom block # to take this error and raise a new one which _doesn't_ descend from {GraphQL::ExecutionError}, # such as `RuntimeError`. class TimeoutError < GraphQL::ExecutionError def initialize(parent_type, field) super("Timeout on #{parent_type.graphql_name}.#{field.graphql_name}") end end end end end graphql-ruby-1.11.10/lib/graphql/schema/timeout_middleware.rb000066400000000000000000000071101414121453000241360ustar00rootroot00000000000000# frozen_string_literal: true require "delegate" module GraphQL class Schema # This middleware will stop resolving new fields after `max_seconds` have elapsed. # After the time has passed, any remaining fields will be `nil`, with errors added # to the `errors` key. Any already-resolved fields will be in the `data` key, so # you'll get a partial response. # # You can provide a block which will be called with any timeout errors that occur. # # Note that this will stop a query _in between_ field resolutions, but # it doesn't interrupt long-running `resolve` functions. Be sure to use # timeout options for external connections. For more info, see # www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/ # # @example Stop resolving fields after 2 seconds # MySchema.middleware << GraphQL::Schema::TimeoutMiddleware.new(max_seconds: 2) # # @example Notifying Bugsnag on a timeout # MySchema.middleware << GraphQL::Schema::TimeoutMiddleware(max_seconds: 1.5) do |timeout_error, query| # Bugsnag.notify(timeout_error, {query_string: query_ctx.query.query_string}) # end # class TimeoutMiddleware # @param max_seconds [Numeric] how many seconds the query should be allowed to resolve new fields def initialize(max_seconds:, context_key: nil, &block) @max_seconds = max_seconds if context_key warn("TimeoutMiddleware's `context_key` is ignored, timeout data is now stored in isolated storage") end @error_handler = block end def call(parent_type, parent_object, field_definition, field_args, query_context) ns = query_context.namespace(self.class) now = Process.clock_gettime(Process::CLOCK_MONOTONIC) timeout_at = ns[:timeout_at] ||= now + @max_seconds if timeout_at < now on_timeout(parent_type, parent_object, field_definition, field_args, query_context) else yield end end # This is called when a field _would_ be resolved, except that we're over the time limit. # @return [GraphQL::Schema::TimeoutMiddleware::TimeoutError] An error whose message will be added to the `errors` key def on_timeout(parent_type, parent_object, field_definition, field_args, field_context) err = GraphQL::Schema::TimeoutMiddleware::TimeoutError.new(parent_type, field_definition) if @error_handler query_proxy = TimeoutQueryProxy.new(field_context.query, field_context) @error_handler.call(err, query_proxy) end err end # This behaves like {GraphQL::Query} but {#context} returns # the _field-level_ context, not the query-level context. # This means you can reliably get the `irep_node` and `path` # from it after the fact. class TimeoutQueryProxy < SimpleDelegator def initialize(query, ctx) @context = ctx super(query) end attr_reader :context end # This error is raised when a query exceeds `max_seconds`. # Since it's a child of {GraphQL::ExecutionError}, # its message will be added to the response's `errors` key. # # To raise an error that will stop query resolution, use a custom block # to take this error and raise a new one which _doesn't_ descend from {GraphQL::ExecutionError}, # such as `RuntimeError`. class TimeoutError < GraphQL::ExecutionError def initialize(parent_type, field_defn) super("Timeout on #{parent_type.name}.#{field_defn.name}") end end end end end graphql-ruby-1.11.10/lib/graphql/schema/traversal.rb000066400000000000000000000236161414121453000222670ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # Visit the members of this schema and build up artifacts for runtime. # @api private class Traversal # @return [Hash GraphQL::BaseType] attr_reader :type_map # @return [Hash Hash GraphQL::Field>>] attr_reader :instrumented_field_map # @return [Hash Array] attr_reader :type_reference_map # @return [Hash Array] attr_reader :union_memberships # @param schema [GraphQL::Schema] def initialize(schema, introspection: true) @schema = schema @introspection = introspection built_in_insts = [ GraphQL::Relay::ConnectionInstrumentation, GraphQL::Relay::EdgesInstrumentation, GraphQL::Relay::Mutation::Instrumentation, ] if schema.query_execution_strategy != GraphQL::Execution::Interpreter built_in_insts << GraphQL::Schema::Member::Instrumentation end @field_instrumenters = schema.instrumenters[:field] + built_in_insts + schema.instrumenters[:field_after_built_ins] # These fields have types specified by _name_, # So we need to inspect the schema and find those types, # then update their references. @late_bound_fields = [] @type_map = {} @instrumented_field_map = Hash.new { |h, k| h[k] = {} } @type_reference_map = Hash.new { |h, k| h[k] = [] } @union_memberships = Hash.new { |h, k| h[k] = [] } visit(schema, schema, nil) resolve_late_bound_fields end private # A brute-force appraoch to late binding. # Just keep trying the whole list, hoping that they # eventually all resolve. # This could be replaced with proper dependency tracking. def resolve_late_bound_fields # This is a bit tricky, with the writes going to internal state. prev_late_bound_fields = @late_bound_fields # Things might get added here during `visit...` # or they might be added manually if we can't find them by hand @late_bound_fields = [] prev_late_bound_fields.each do |(owner_type, field_defn, dynamic_field)| if @type_map.key?(field_defn.type.unwrap.name) late_bound_return_type = field_defn.type resolved_type = @type_map.fetch(late_bound_return_type.unwrap.name) wrapped_resolved_type = rewrap_resolved_type(late_bound_return_type, resolved_type) # Update the field definition in place? :thinking_face: field_defn.type = wrapped_resolved_type visit_field_on_type(@schema, owner_type, field_defn, dynamic_field: dynamic_field) else @late_bound_fields << [owner_type, field_defn, dynamic_field] end end if @late_bound_fields.any? # If we visited each field and failed to resolve _any_, # then we're stuck. if @late_bound_fields == prev_late_bound_fields type_names = prev_late_bound_fields.map { |f| f[1] }.map(&:type).map(&:unwrap).map(&:name).uniq raise <<-ERR Some late-bound types couldn't be resolved: - #{type_names} - Found __* types: #{@type_map.keys.select { |k| k.start_with?("__") }} ERR else resolve_late_bound_fields end end end # The late-bound type may be wrapped with list or non-null types. # Apply the same wrapping to the resolve type and # return the maybe-wrapped type def rewrap_resolved_type(late_bound_type, resolved_inner_type) case late_bound_type when GraphQL::NonNullType rewrap_resolved_type(late_bound_type.of_type, resolved_inner_type).to_non_null_type when GraphQL::ListType rewrap_resolved_type(late_bound_type.of_type, resolved_inner_type).to_list_type when GraphQL::Schema::LateBoundType resolved_inner_type else raise "Unexpected late_bound_type: #{late_bound_type.inspect} (#{late_bound_type.class})" end end def visit(schema, member, context_description) case member when GraphQL::Schema member.directives.each { |name, directive| visit(schema, directive, "Directive #{name}") } # Find the starting points, then visit them visit_roots = [member.query, member.mutation, member.subscription] if @introspection introspection_types = schema.introspection_system.types.values visit_roots.concat(introspection_types) if member.query member.introspection_system.entry_points.each do |introspection_field| # Visit this so that arguments class is preconstructed # Skip validation since it begins with "__" visit_field_on_type(schema, member.query, introspection_field, dynamic_field: true) end end end visit_roots.concat(member.orphan_types) visit_roots.compact! visit_roots.each { |t| visit(schema, t, t.name) } when GraphQL::Directive member.arguments.each do |name, argument| @type_reference_map[argument.type.unwrap.to_s] << argument visit(schema, argument.type, "Directive argument #{member.name}.#{name}") end # Construct arguments class here, which is later used to generate GraphQL::Query::Arguments # to be passed to a resolver proc GraphQL::Query::Arguments.construct_arguments_class(member) when GraphQL::BaseType type_defn = member.unwrap prev_type = @type_map[type_defn.name] # Continue to visit this type if it's the first time we've seen it: if prev_type.nil? validate_type(type_defn, context_description) @type_map[type_defn.name] = type_defn case type_defn when GraphQL::ObjectType type_defn.interfaces.each { |i| visit(schema, i, "Interface on #{type_defn.name}") } visit_fields(schema, type_defn) when GraphQL::InterfaceType visit_fields(schema, type_defn) type_defn.orphan_types.each do |t| visit(schema, t, "Orphan type for #{type_defn.name}") end when GraphQL::UnionType type_defn.possible_types.each do |t| @union_memberships[t.name] << type_defn visit(schema, t, "Possible type for #{type_defn.name}") end when GraphQL::InputObjectType type_defn.arguments.each do |name, arg| @type_reference_map[arg.type.unwrap.to_s] << arg visit(schema, arg.type, "Input field #{type_defn.name}.#{name}") end # Construct arguments class here, which is later used to generate GraphQL::Query::Arguments # to be passed to a resolver proc if type_defn.arguments_class.nil? GraphQL::Query::Arguments.construct_arguments_class(type_defn) end end elsif !prev_type.equal?(type_defn) # If the previous entry in the map isn't the same object we just found, raise. raise("Duplicate type definition found for name '#{type_defn.name}' at '#{context_description}' (#{prev_type.metadata[:type_class] || prev_type}, #{type_defn.metadata[:type_class] || type_defn})") end when Class if member.respond_to?(:graphql_definition) graphql_member = member.graphql_definition visit(schema, graphql_member, context_description) else raise GraphQL::Schema::InvalidTypeError.new("Unexpected traversal member: #{member} (#{member.class.name})") end else message = "Unexpected schema traversal member: #{member} (#{member.class.name})" raise GraphQL::Schema::InvalidTypeError.new(message) end end def visit_fields(schema, type_defn) type_defn.all_fields.each do |field_defn| visit_field_on_type(schema, type_defn, field_defn) end end def visit_field_on_type(schema, type_defn, field_defn, dynamic_field: false) base_return_type = field_defn.type.unwrap if base_return_type.is_a?(GraphQL::Schema::LateBoundType) @late_bound_fields << [type_defn, field_defn, dynamic_field] return end if dynamic_field # Don't apply instrumentation to dynamic fields since they're shared constants instrumented_field_defn = field_defn else instrumented_field_defn = @field_instrumenters.reduce(field_defn) do |defn, inst| inst.instrument(type_defn, defn) end @instrumented_field_map[type_defn.name][instrumented_field_defn.name] = instrumented_field_defn end @type_reference_map[instrumented_field_defn.type.unwrap.name] << instrumented_field_defn visit(schema, instrumented_field_defn.type, "Field #{type_defn.name}.#{instrumented_field_defn.name}'s return type") instrumented_field_defn.arguments.each do |name, arg| @type_reference_map[arg.type.unwrap.to_s] << arg visit(schema, arg.type, "Argument #{name} on #{type_defn.name}.#{instrumented_field_defn.name}") end # Construct arguments class here, which is later used to generate GraphQL::Query::Arguments # to be passed to a resolver proc GraphQL::Query::Arguments.construct_arguments_class(instrumented_field_defn) end def validate_type(member, context_description) error_message = GraphQL::Schema::Validation.validate(member) if error_message raise GraphQL::Schema::InvalidTypeError.new("#{context_description} is invalid: #{error_message}") end end end end end graphql-ruby-1.11.10/lib/graphql/schema/type_expression.rb000066400000000000000000000027431414121453000235220ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # @api private module TypeExpression # Fetch a type from a type map by its AST specification. # Return `nil` if not found. # @param type_owner [#get_type] A thing for looking up types by name # @param ast_node [GraphQL::Language::Nodes::AbstractNode] # @return [Class, GraphQL::Schema::NonNull, GraphQL::Schema:List] def self.build_type(type_owner, ast_node) case ast_node when GraphQL::Language::Nodes::TypeName type_owner.get_type(ast_node.name) when GraphQL::Language::Nodes::NonNullType ast_inner_type = ast_node.of_type inner_type = build_type(type_owner, ast_inner_type) wrap_type(inner_type, :to_non_null_type) when GraphQL::Language::Nodes::ListType ast_inner_type = ast_node.of_type inner_type = build_type(type_owner, ast_inner_type) wrap_type(inner_type, :to_list_type) else raise "Invariant: unexpected type from ast: #{ast_node.inspect}" end end class << self private def wrap_type(type, wrapper_method) if type.nil? nil elsif wrapper_method == :to_list_type || wrapper_method == :to_non_null_type type.public_send(wrapper_method) else raise ArgumentError, "Unexpected wrapper method: #{wrapper_method.inspect}" end end end end end end graphql-ruby-1.11.10/lib/graphql/schema/type_membership.rb000066400000000000000000000022661414121453000234560ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # This class joins an object type to an abstract type (interface or union) of which # it is a member. # # TODO: Not yet implemented for interfaces. class TypeMembership # @return [Class] attr_accessor :object_type # @return [Class, Module] attr_reader :abstract_type # Called when an object is hooked up to an abstract type, such as {Schema::Union.possible_types} # or {Schema::Object.implements} (for interfaces). # # @param abstract_type [Class, Module] # @param object_type [Class] # @param options [Hash] Any options passed to `.possible_types` or `.implements` def initialize(abstract_type, object_type, **options) @abstract_type = abstract_type @object_type = object_type @options = options end # @return [Boolean] if false, {#object_type} will be treated as _not_ a member of {#abstract_type} def visible?(_ctx) true end end end end graphql-ruby-1.11.10/lib/graphql/schema/union.rb000066400000000000000000000061441414121453000214110ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Union < GraphQL::Schema::Member extend GraphQL::Schema::Member::AcceptsDefinition extend GraphQL::Schema::Member::HasUnresolvedTypeError class << self def inherited(child_class) add_unresolved_type_error(child_class) super end def possible_types(*types, context: GraphQL::Query::NullContext, **options) if types.any? types.each do |t| assert_valid_union_member(t) type_memberships << type_membership_class.new(self, t, **options) end else visible_types = [] type_memberships.each do |type_membership| if type_membership.visible?(context) visible_types << type_membership.object_type end end visible_types end end def to_graphql type_defn = GraphQL::UnionType.new type_defn.name = graphql_name type_defn.description = description type_defn.ast_node = ast_node type_defn.type_memberships = type_memberships if respond_to?(:resolve_type) type_defn.resolve_type = method(:resolve_type) end type_defn.metadata[:type_class] = self type_defn end def type_membership_class(membership_class = nil) if membership_class @type_membership_class = membership_class else @type_membership_class || find_inherited_value(:type_membership_class, GraphQL::Schema::TypeMembership) end end def kind GraphQL::TypeKinds::UNION end def type_memberships @type_memberships ||= [] end # Update a type membership whose `.object_type` is a string or late-bound type # so that the type membership's `.object_type` is the given `object_type`. # (This is used for updating the union after the schema as lazily loaded the union member.) # @api private def assign_type_membership_object_type(object_type) assert_valid_union_member(object_type) type_memberships.each { |tm| possible_type = tm.object_type if possible_type.is_a?(String) && (possible_type == object_type.name) # This is a match of Ruby class names, not graphql names, # since strings are used to refer to constants. tm.object_type = object_type elsif possible_type.is_a?(LateBoundType) && possible_type.graphql_name == object_type.graphql_name tm.object_type = object_type end } nil end private def assert_valid_union_member(type_defn) if type_defn.is_a?(Module) && !type_defn.is_a?(Class) # it's an interface type, defined as a module raise ArgumentError, "Union possible_types can only be object types (not interface types), remove #{type_defn.graphql_name} (#{type_defn.inspect})" end end end end end end graphql-ruby-1.11.10/lib/graphql/schema/unique_within_type.rb000066400000000000000000000021441414121453000242060ustar00rootroot00000000000000# frozen_string_literal: true require 'graphql/schema/base_64_bp' module GraphQL class Schema module UniqueWithinType class << self attr_accessor :default_id_separator end self.default_id_separator = "-" module_function # @param type_name [String] # @param object_value [Any] # @return [String] a unique, opaque ID generated as a function of the two inputs def encode(type_name, object_value, separator: self.default_id_separator) object_value_str = object_value.to_s if type_name.include?(separator) raise "encode(#{type_name}, #{object_value_str}) contains reserved characters `#{separator}` in the type name" end Base64.strict_encode64([type_name, object_value_str].join(separator)) end # @param node_id [String] A unique ID generated by {.encode} # @return [Array<(String, String)>] The type name & value passed to {.encode} def decode(node_id, separator: self.default_id_separator) GraphQL::Schema::Base64Encoder.decode(node_id).split(separator, 2) end end end end graphql-ruby-1.11.10/lib/graphql/schema/validation.rb000066400000000000000000000327721414121453000224210ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema # This module provides a function for validating GraphQL types. # # Its {RULES} contain objects that respond to `#call(type)`. Rules are # looked up for given types (by class ancestry), then applied to # the object until an error is returned. class Validation # Lookup the rules for `object` based on its class, # Then returns an error message or `nil` # @param object [Object] something to be validated # @return [String, Nil] error message, if there was one def self.validate(object) RULES.each do |parent_class, validations| if object.is_a?(parent_class) validations.each do |rule| if error = rule.call(object) return error end end end end nil end module Rules # @param property_name [Symbol] The method to validate # @param allowed_classes [Class] Classes which the return value may be an instance of # @return [Proc] A proc which will validate the input by calling `property_name` and asserting it is an instance of one of `allowed_classes` def self.assert_property(property_name, *allowed_classes) # Hide LateBoundType from user-facing errors allowed_classes_message = allowed_classes.map(&:name).reject {|n| n.include?("LateBoundType") }.join(" or ") ->(obj) { property_value = obj.public_send(property_name) is_valid_value = allowed_classes.any? { |allowed_class| property_value.is_a?(allowed_class) } is_valid_value ? nil : "#{property_name} must return #{allowed_classes_message}, not #{property_value.class.name} (#{property_value.inspect})" } end # @param property_name [Symbol] The method whose return value will be validated # @param from_class [Class] The class for keys in the return value # @param to_class [Class] The class for values in the return value # @return [Proc] A proc to validate that validates the input by calling `property_name` and asserting that the return value is a Hash of `{from_class => to_class}` pairs def self.assert_property_mapping(property_name, from_class, to_class) ->(obj) { property_value = obj.public_send(property_name) if !property_value.is_a?(Hash) "#{property_name} must be a hash of {#{from_class.name} => #{to_class.name}}, not a #{property_value.class.name} (#{property_value.inspect})" else invalid_key, invalid_value = property_value.find { |key, value| !key.is_a?(from_class) || !value.is_a?(to_class) } if invalid_key "#{property_name} must map #{from_class} => #{to_class}, not #{invalid_key.class.name} => #{invalid_value.class.name} (#{invalid_key.inspect} => #{invalid_value.inspect})" else nil # OK end end } end # @param property_name [Symbol] The method whose return value will be validated # @param list_member_class [Class] The class which each member of the returned array should be an instance of # @return [Proc] A proc to validate the input by calling `property_name` and asserting that the return is an Array of `list_member_class` instances def self.assert_property_list_of(property_name, list_member_class) ->(obj) { property_value = obj.public_send(property_name) if !property_value.is_a?(Array) "#{property_name} must be an Array of #{list_member_class.name}, not a #{property_value.class.name} (#{property_value.inspect})" else invalid_member = property_value.find { |value| !value.is_a?(list_member_class) } if invalid_member "#{property_name} must contain #{list_member_class.name}, not #{invalid_member.class.name} (#{invalid_member.inspect})" else nil # OK end end } end def self.count_at_least(item_name, minimum_count, get_items_proc) ->(type) { items = get_items_proc.call(type) if items.size < minimum_count "#{type.name} must define at least #{minimum_count} #{item_name}. #{items.size} defined." else nil end } end def self.assert_named_items_are_valid(item_name, get_items_proc) ->(type) { items = get_items_proc.call(type) error_message = nil items.each do |item| item_message = GraphQL::Schema::Validation.validate(item) if item_message error_message = "#{item_name} #{item.name.inspect} #{item_message}" break end end error_message } end HAS_AT_LEAST_ONE_FIELD = Rules.count_at_least("field", 1, ->(type) { type.all_fields }) FIELDS_ARE_VALID = Rules.assert_named_items_are_valid("field", ->(type) { type.all_fields }) HAS_AT_LEAST_ONE_ARGUMENT = Rules.count_at_least("argument", 1, ->(type) { type.arguments }) HAS_ONE_OR_MORE_POSSIBLE_TYPES = ->(type) { type.possible_types.length >= 1 ? nil : "must have at least one possible type" } NAME_IS_STRING = Rules.assert_property(:name, String) DESCRIPTION_IS_STRING_OR_NIL = Rules.assert_property(:description, String, NilClass) ARGUMENTS_ARE_STRING_TO_ARGUMENT = Rules.assert_property_mapping(:arguments, String, GraphQL::Argument) ARGUMENTS_ARE_VALID = Rules.assert_named_items_are_valid("argument", ->(type) { type.arguments.values }) DEFAULT_VALUE_IS_VALID_FOR_TYPE = ->(type) { if !type.default_value.nil? coerced_value = begin type.type.coerce_isolated_result(type.default_value) rescue => ex ex end if coerced_value.nil? || coerced_value.is_a?(StandardError) msg = "default value #{type.default_value.inspect} is not valid for type #{type.type}" msg += " (#{coerced_value})" if coerced_value.is_a?(StandardError) msg end end } DEPRECATED_ARGUMENTS_ARE_OPTIONAL = ->(argument) { if argument.deprecation_reason && argument.type.non_null? "must be optional because it's deprecated" end } TYPE_IS_VALID_INPUT_TYPE = ->(type) { outer_type = type.type inner_type = outer_type.respond_to?(:unwrap) ? outer_type.unwrap : nil case inner_type when GraphQL::ScalarType, GraphQL::InputObjectType, GraphQL::EnumType # OK else "type must be a valid input type (Scalar or InputObject), not #{outer_type.class} (#{outer_type})" end } SCHEMA_CAN_RESOLVE_TYPES = ->(schema) { if schema.types.values.any? { |type| type.kind.abstract? } && schema.resolve_type_proc.nil? "schema contains Interfaces or Unions, so you must define a `resolve_type -> (obj, ctx) { ... }` function" else # :+1: end } SCHEMA_CAN_FETCH_IDS = ->(schema) { has_node_field = schema.query && schema.query.fields.each_value.any?(&:relay_node_field) if has_node_field && schema.object_from_id_proc.nil? "schema contains `node(id:...)` field, so you must define a `object_from_id -> (id, ctx) { ... }` function" else # :rocket: end } SCHEMA_CAN_GENERATE_IDS = ->(schema) { has_id_field = schema.types.values.any? { |t| t.kind.fields? && t.all_fields.any? { |f| f.resolve_proc.is_a?(GraphQL::Relay::GlobalIdResolve) } } if has_id_field && schema.id_from_object_proc.nil? "schema contains `global_id_field`, so you must define a `id_from_object -> (obj, type, ctx) { ... }` function" else # :ok_hand: end } SCHEMA_INSTRUMENTERS_ARE_VALID = ->(schema) { errs = [] schema.instrumenters[:query].each do |inst| if !inst.respond_to?(:before_query) || !inst.respond_to?(:after_query) errs << "`instrument(:query, #{inst})` is invalid: must respond to `before_query(query)` and `after_query(query)` " end end schema.instrumenters[:field].each do |inst| if !inst.respond_to?(:instrument) errs << "`instrument(:field, #{inst})` is invalid: must respond to `instrument(type, field)`" end end if errs.any? errs.join("Invalid instrumenters:\n" + errs.join("\n")) else nil end } RESERVED_TYPE_NAME = ->(type) { if type.name.start_with?('__') && !type.introspection? # TODO: make this a hard failure in a later version warn("Name #{type.name.inspect} must not begin with \"__\", which is reserved by GraphQL introspection.") nil else # ok name end } RESERVED_NAME = ->(named_thing) { if named_thing.name.start_with?('__') # TODO: make this a hard failure in a later version warn("Name #{named_thing.name.inspect} must not begin with \"__\", which is reserved by GraphQL introspection.") nil else # no worries end } INTERFACES_ARE_IMPLEMENTED = ->(obj_type) { field_errors = [] obj_type.interfaces.each do |interface_type| interface_type.fields.each do |field_name, field_defn| object_field = obj_type.get_field(field_name) if object_field.nil? field_errors << %|"#{field_name}" is required by #{interface_type.name} but not implemented by #{obj_type.name}| elsif !GraphQL::Execution::Typecast.subtype?(field_defn.type, object_field.type) field_errors << %|"#{field_name}" is required by #{interface_type.name} to return #{field_defn.type} but #{obj_type.name}.#{field_name} returns #{object_field.type}| else field_defn.arguments.each do |arg_name, arg_defn| object_field_arg = object_field.arguments[arg_name] if object_field_arg.nil? field_errors << %|"#{arg_name}" argument is required by #{interface_type.name}.#{field_name} but not accepted by #{obj_type.name}.#{field_name}| elsif arg_defn.type != object_field_arg.type field_errors << %|"#{arg_name}" is required by #{interface_type.name}.#{field_defn.name} to accept #{arg_defn.type} but #{obj_type.name}.#{field_name} accepts #{object_field_arg.type} for "#{arg_name}"| end end object_field.arguments.each do |arg_name, arg_defn| if field_defn.arguments[arg_name].nil? && arg_defn.type.is_a?(GraphQL::NonNullType) field_errors << %|"#{arg_name}" is not accepted by #{interface_type.name}.#{field_name} but required by #{obj_type.name}.#{field_name}| end end end end end if field_errors.any? "#{obj_type.name} failed to implement some interfaces: #{field_errors.join(", ")}" else nil end } end # A mapping of `{Class => [Proc, Proc...]}` pairs. # To validate an instance, find entries where `object.is_a?(key)` is true. # Then apply each rule from the matching values. RULES = { GraphQL::Field => [ Rules::NAME_IS_STRING, Rules::RESERVED_NAME, Rules::DESCRIPTION_IS_STRING_OR_NIL, Rules.assert_property(:deprecation_reason, String, NilClass), Rules.assert_property(:type, GraphQL::BaseType, GraphQL::Schema::LateBoundType), Rules.assert_property(:property, Symbol, NilClass), Rules::ARGUMENTS_ARE_STRING_TO_ARGUMENT, Rules::ARGUMENTS_ARE_VALID, ], GraphQL::Argument => [ Rules::NAME_IS_STRING, Rules::RESERVED_NAME, Rules::DESCRIPTION_IS_STRING_OR_NIL, Rules.assert_property(:deprecation_reason, String, NilClass), Rules::TYPE_IS_VALID_INPUT_TYPE, Rules::DEFAULT_VALUE_IS_VALID_FOR_TYPE, Rules::DEPRECATED_ARGUMENTS_ARE_OPTIONAL, ], GraphQL::BaseType => [ Rules::NAME_IS_STRING, Rules::RESERVED_TYPE_NAME, Rules::DESCRIPTION_IS_STRING_OR_NIL, ], GraphQL::ObjectType => [ Rules::HAS_AT_LEAST_ONE_FIELD, Rules.assert_property_list_of(:interfaces, GraphQL::InterfaceType), Rules::FIELDS_ARE_VALID, Rules::INTERFACES_ARE_IMPLEMENTED, ], GraphQL::InputObjectType => [ Rules::HAS_AT_LEAST_ONE_ARGUMENT, Rules::ARGUMENTS_ARE_STRING_TO_ARGUMENT, Rules::ARGUMENTS_ARE_VALID, ], GraphQL::UnionType => [ Rules.assert_property_list_of(:possible_types, GraphQL::ObjectType), Rules::HAS_ONE_OR_MORE_POSSIBLE_TYPES, ], GraphQL::InterfaceType => [ Rules::FIELDS_ARE_VALID, ], GraphQL::Schema => [ Rules::SCHEMA_INSTRUMENTERS_ARE_VALID, Rules::SCHEMA_CAN_RESOLVE_TYPES, Rules::SCHEMA_CAN_FETCH_IDS, Rules::SCHEMA_CAN_GENERATE_IDS, ], } end end end graphql-ruby-1.11.10/lib/graphql/schema/warden.rb000066400000000000000000000274221414121453000215430ustar00rootroot00000000000000# frozen_string_literal: true require 'set' module GraphQL class Schema # Restrict access to a {GraphQL::Schema} with a user-defined filter. # # When validating and executing a query, all access to schema members # should go through a warden. If you access the schema directly, # you may show a client something that it shouldn't be allowed to see. # # @example Hidding private fields # private_members = -> (member, ctx) { member.metadata[:private] } # result = Schema.execute(query_string, except: private_members) # # @example Custom filter implementation # # It must respond to `#call(member)`. # class MissingRequiredFlags # def initialize(user) # @user = user # end # # # Return `false` if any required flags are missing # def call(member, ctx) # member.metadata[:required_flags].any? do |flag| # !@user.has_flag?(flag) # end # end # end # # # Then, use the custom filter in query: # missing_required_flags = MissingRequiredFlags.new(current_user) # # # This query can only access members which match the user's flags # result = Schema.execute(query_string, except: missing_required_flags) # # @api private class Warden # @param filter [<#call(member)>] Objects are hidden when `.call(member, ctx)` returns true # @param context [GraphQL::Query::Context] # @param schema [GraphQL::Schema] def initialize(filter, context:, schema:) @schema = schema.interpreter? ? schema : schema.graphql_definition # Cache these to avoid repeated hits to the inheritance chain when one isn't present @query = @schema.query @mutation = @schema.mutation @subscription = @schema.subscription @context = context @visibility_cache = read_through { |m| filter.call(m, context) } end # @return [Hash] Visible types in the schema def types @types ||= begin vis_types = {} @schema.types.each do |n, t| if visible_type?(t) vis_types[n] = t end end vis_types end end # @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`) def get_type(type_name) @visible_types ||= read_through do |name| type_defn = @schema.get_type(name) if type_defn && visible_type?(type_defn) type_defn else nil end end @visible_types[type_name] end # @return [Array] Visible and reachable types in the schema def reachable_types @reachable_types ||= reachable_type_set.to_a end # @return Boolean True if the type is visible and reachable in the schema def reachable_type?(type_name) type = get_type(type_name) type && reachable_type_set.include?(type) end # @return [GraphQL::Field, nil] The field named `field_name` on `parent_type`, if it exists def get_field(parent_type, field_name) @visible_parent_fields ||= read_through do |type| read_through do |f_name| field_defn = @schema.get_field(type, f_name) if field_defn && visible_field?(type, field_defn) field_defn else nil end end end @visible_parent_fields[parent_type][field_name] end # @return [GraphQL::Argument, nil] The argument named `argument_name` on `parent_type`, if it exists and is visible def get_argument(parent_type, argument_name) argument = parent_type.get_argument(argument_name) return argument if argument && visible_argument?(argument) end # @return [Array] The types which may be member of `type_defn` def possible_types(type_defn) @visible_possible_types ||= read_through { |type_defn| pt = @schema.possible_types(type_defn, @context) pt.select { |t| visible_type?(t) } } @visible_possible_types[type_defn] end # @param type_defn [GraphQL::ObjectType, GraphQL::InterfaceType] # @return [Array] Fields on `type_defn` def fields(type_defn) @visible_fields ||= read_through { |t| @schema.get_fields(t).each_value.select { |f| visible_field?(t, f) } } @visible_fields[type_defn] end # @param argument_owner [GraphQL::Field, GraphQL::InputObjectType] # @return [Array] Visible arguments on `argument_owner` def arguments(argument_owner) @visible_arguments ||= read_through { |o| o.arguments.each_value.select { |a| visible_argument?(a) } } @visible_arguments[argument_owner] end # @return [Array] Visible members of `enum_defn` def enum_values(enum_defn) @visible_enum_values ||= read_through { |e| e.values.each_value.select { |enum_value_defn| visible?(enum_value_defn) } } @visible_enum_values[enum_defn] end # @return [Array] Visible interfaces implemented by `obj_type` def interfaces(obj_type) @visible_interfaces ||= read_through { |t| t.interfaces(@context).select { |i| visible?(i) } } @visible_interfaces[obj_type] end def directives @schema.directives.each_value.select { |d| visible?(d) } end def root_type_for_operation(op_name) root_type = @schema.root_type_for_operation(op_name) if root_type && visible?(root_type) root_type else nil end end private def union_memberships(obj_type) @unions ||= read_through { |obj_type| @schema.union_memberships(obj_type).select { |u| visible?(u) } } @unions[obj_type] end def visible_argument?(arg_defn) visible?(arg_defn) && visible_type?(arg_defn.type.unwrap) end def visible_field?(owner_type, field_defn) # This field is visible in its own right visible?(field_defn) && # This field's return type is visible visible_type?(field_defn.type.unwrap) && # This field is either defined on this object type, # or the interface it's inherited from is also visible ((field_defn.respond_to?(:owner) && field_defn.owner == owner_type) || field_on_visible_interface?(field_defn, owner_type)) end # We need this to tell whether a field was inherited by an interface # even when that interface is hidden from `#interfaces` def unfiltered_interfaces(type_defn) @unfiltered_interfaces ||= read_through(&:interfaces) @unfiltered_interfaces[type_defn] end # If this field was inherited from an interface, and the field on that interface is _hidden_, # then treat this inherited field as hidden. # (If it _wasn't_ inherited, then don't hide it for this reason.) def field_on_visible_interface?(field_defn, type_defn) if type_defn.kind.object? any_interface_has_field = false any_interface_has_visible_field = false ints = unfiltered_interfaces(type_defn) ints.each do |interface_type| if (iface_field_defn = interface_type.get_field(field_defn.graphql_name)) any_interface_has_field = true if interfaces(type_defn).include?(interface_type) && visible_field?(interface_type, iface_field_defn) any_interface_has_visible_field = true end end end if any_interface_has_field any_interface_has_visible_field else # it's the object's own field true end else true end end def visible_type?(type_defn) @type_visibility ||= read_through do |type_defn| next false unless visible?(type_defn) next true if root_type?(type_defn) || type_defn.introspection? if type_defn.kind.union? visible_possible_types?(type_defn) && (referenced?(type_defn) || orphan_type?(type_defn)) elsif type_defn.kind.interface? visible_possible_types?(type_defn) else referenced?(type_defn) || visible_abstract_type?(type_defn) end end @type_visibility[type_defn] end def root_type?(type_defn) @query == type_defn || @mutation == type_defn || @subscription == type_defn end def referenced?(type_defn) @references_to ||= @schema.references_to graphql_name = type_defn.unwrap.graphql_name members = @references_to[graphql_name] || NO_REFERENCES members.any? { |m| visible?(m) } end NO_REFERENCES = [].freeze def orphan_type?(type_defn) @schema.orphan_types.include?(type_defn) end def visible_abstract_type?(type_defn) type_defn.kind.object? && ( interfaces(type_defn).any? || union_memberships(type_defn).any? ) end def visible_possible_types?(type_defn) possible_types(type_defn).any? { |t| visible_type?(t) } end def visible?(member) @visibility_cache[member] end def read_through Hash.new { |h, k| h[k] = yield(k) } end def reachable_type_set return @reachable_type_set if defined?(@reachable_type_set) @reachable_type_set = Set.new unvisited_types = [] ['query', 'mutation', 'subscription'].each do |op_name| root_type = root_type_for_operation(op_name) unvisited_types << root_type if root_type end unvisited_types.concat(@schema.introspection_system.types.values) directives.each do |dir_class| dir_class.arguments.values.each do |arg_defn| arg_t = arg_defn.type.unwrap if get_type(arg_t.graphql_name) unvisited_types << arg_t end end end @schema.orphan_types.each do |orphan_type| if get_type(orphan_type.graphql_name) unvisited_types << orphan_type end end until unvisited_types.empty? type = unvisited_types.pop if @reachable_type_set.add?(type) if type.kind.input_object? # recurse into visible arguments arguments(type).each do |argument| argument_type = argument.type.unwrap unvisited_types << argument_type end elsif type.kind.union? # recurse into visible possible types possible_types(type).each do |possible_type| unvisited_types << possible_type end elsif type.kind.fields? if type.kind.interface? # recurse into visible possible types possible_types(type).each do |possible_type| unvisited_types << possible_type end elsif type.kind.object? # recurse into visible implemented interfaces interfaces(type).each do |interface| unvisited_types << interface end end # recurse into visible fields fields(type).each do |field| field_type = field.type.unwrap unvisited_types << field_type # recurse into visible arguments arguments(field).each do |argument| argument_type = argument.type.unwrap unvisited_types << argument_type end end end end end @reachable_type_set end end end end graphql-ruby-1.11.10/lib/graphql/schema/wrapper.rb000066400000000000000000000012401414121453000217310ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Schema class Wrapper include GraphQL::Schema::Member::CachedGraphQLDefinition include GraphQL::Schema::Member::TypeSystemHelpers # @return [Class, Module] The inner type of this wrapping type, the type of which one or more objects may be present. attr_reader :of_type def initialize(of_type) @of_type = of_type end def to_graphql raise GraphQL::RequiredImplementationMissingError end def unwrap @of_type.unwrap end def ==(other) self.class == other.class && of_type == other.of_type end end end end graphql-ruby-1.11.10/lib/graphql/static_validation.rb000066400000000000000000000014231414121453000225150ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/static_validation/error" require "graphql/static_validation/definition_dependencies" require "graphql/static_validation/type_stack" require "graphql/static_validation/validator" require "graphql/static_validation/validation_context" require "graphql/static_validation/validation_timeout_error" require "graphql/static_validation/literal_validator" require "graphql/static_validation/base_visitor" require "graphql/static_validation/no_validate_visitor" rules_glob = File.expand_path("../static_validation/rules/*.rb", __FILE__) Dir.glob(rules_glob).each do |file| require(file) end require "graphql/static_validation/all_rules" require "graphql/static_validation/default_visitor" require "graphql/static_validation/interpreter_visitor" graphql-ruby-1.11.10/lib/graphql/static_validation/000077500000000000000000000000001414121453000221705ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/static_validation/all_rules.rb000066400000000000000000000037551414121453000245110ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # Default rules for {GraphQL::StaticValidation::Validator} # # Order is important here. Some validators return {GraphQL::Language::Visitor::SKIP} # which stops the visit on that node. That way it doesn't try to find fields on types that # don't exist, etc. ALL_RULES = [ GraphQL::StaticValidation::NoDefinitionsArePresent, GraphQL::StaticValidation::DirectivesAreDefined, GraphQL::StaticValidation::DirectivesAreInValidLocations, GraphQL::StaticValidation::UniqueDirectivesPerLocation, GraphQL::StaticValidation::OperationNamesAreValid, GraphQL::StaticValidation::FragmentNamesAreUnique, GraphQL::StaticValidation::FragmentsAreFinite, GraphQL::StaticValidation::FragmentsAreNamed, GraphQL::StaticValidation::FragmentsAreUsed, GraphQL::StaticValidation::FragmentTypesExist, GraphQL::StaticValidation::FragmentsAreOnCompositeTypes, GraphQL::StaticValidation::FragmentSpreadsArePossible, GraphQL::StaticValidation::FieldsAreDefinedOnType, GraphQL::StaticValidation::FieldsWillMerge, GraphQL::StaticValidation::FieldsHaveAppropriateSelections, GraphQL::StaticValidation::ArgumentsAreDefined, GraphQL::StaticValidation::ArgumentLiteralsAreCompatible, GraphQL::StaticValidation::RequiredArgumentsArePresent, GraphQL::StaticValidation::RequiredInputObjectAttributesArePresent, GraphQL::StaticValidation::ArgumentNamesAreUnique, GraphQL::StaticValidation::VariableNamesAreUnique, GraphQL::StaticValidation::VariablesAreInputTypes, GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTyped, GraphQL::StaticValidation::VariablesAreUsedAndDefined, GraphQL::StaticValidation::VariableUsagesAreAllowed, GraphQL::StaticValidation::MutationRootExists, GraphQL::StaticValidation::SubscriptionRootExists, GraphQL::StaticValidation::InputObjectNamesAreUnique, ] end end graphql-ruby-1.11.10/lib/graphql/static_validation/base_visitor.rb000066400000000000000000000143051414121453000252110ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class BaseVisitor < GraphQL::Language::Visitor def initialize(document, context) @path = [] @object_types = [] @directives = [] @field_definitions = [] @argument_definitions = [] @directive_definitions = [] @context = context @schema = context.schema super(document) end # This will be overwritten by {InternalRepresentation::Rewrite} if it's included def rewrite_document nil end attr_reader :context # @return [Array] Types whose scope we've entered attr_reader :object_types # @return [Array] The nesting of the current position in the AST def path @path.dup end # Build a class to visit the AST and perform validation, # or use a pre-built class if rules is `ALL_RULES` or empty. # @param rules [Array] # @param rewrite [Boolean] if `false`, don't include rewrite # @return [Class] A class for validating `rules` during visitation def self.including_rules(rules, rewrite: true) if rules.empty? if rewrite NoValidateVisitor else # It's not doing _anything?!?_ BaseVisitor end elsif rules == ALL_RULES if rewrite DefaultVisitor else InterpreterVisitor end else visitor_class = Class.new(self) do include(GraphQL::StaticValidation::DefinitionDependencies) end rules.reverse_each do |r| # If it's a class, it gets attached later. if !r.is_a?(Class) visitor_class.include(r) end end if rewrite visitor_class.include(GraphQL::InternalRepresentation::Rewrite) end visitor_class.include(ContextMethods) visitor_class end end module ContextMethods def on_operation_definition(node, parent) object_type = @schema.root_type_for_operation(node.operation_type) push_type(object_type) @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}") super @object_types.pop @path.pop end def on_fragment_definition(node, parent) on_fragment_with_type(node) do @path.push("fragment #{node.name}") super end end def on_inline_fragment(node, parent) on_fragment_with_type(node) do @path.push("...#{node.type ? " on #{node.type.to_query_string}" : ""}") super end end def on_field(node, parent) parent_type = @object_types.last field_definition = @schema.get_field(parent_type, node.name) @field_definitions.push(field_definition) if !field_definition.nil? next_object_type = field_definition.type.unwrap push_type(next_object_type) else push_type(nil) end @path.push(node.alias || node.name) super @field_definitions.pop @object_types.pop @path.pop end def on_directive(node, parent) directive_defn = @schema.directives[node.name] @directive_definitions.push(directive_defn) super @directive_definitions.pop end def on_argument(node, parent) argument_defn = if (arg = @argument_definitions.last) arg_type = arg.type.unwrap if arg_type.kind.input_object? arg_type.arguments[node.name] else nil end elsif (directive_defn = @directive_definitions.last) directive_defn.arguments[node.name] elsif (field_defn = @field_definitions.last) field_defn.arguments[node.name] else nil end @argument_definitions.push(argument_defn) @path.push(node.name) super @argument_definitions.pop @path.pop end def on_fragment_spread(node, parent) @path.push("... #{node.name}") super @path.pop end def on_input_object(node, parent) arg_defn = @argument_definitions.last if arg_defn && arg_defn.type.list? @path.push(parent.children.index(node)) super @path.pop else super end end # @return [GraphQL::BaseType] The current object type def type_definition @object_types.last end # @return [GraphQL::BaseType] The type which the current type came from def parent_type_definition @object_types[-2] end # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one def field_definition @field_definitions.last end # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one def directive_definition @directive_definitions.last end # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one def argument_definition # Don't get the _last_ one because that's the current one. # Get the second-to-last one, which is the parent of the current one. @argument_definitions[-2] end private def on_fragment_with_type(node) object_type = if node.type @schema.get_type(node.type.name) else @object_types.last end push_type(object_type) yield(node) @object_types.pop @path.pop end def push_type(t) @object_types.push(t) end end private def add_error(error, path: nil) if @context.too_many_errors? throw :too_many_validation_errors end error.path ||= (path || @path.dup) context.errors << error end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/default_visitor.rb000066400000000000000000000005541414121453000257240ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class DefaultVisitor < BaseVisitor include(GraphQL::StaticValidation::DefinitionDependencies) StaticValidation::ALL_RULES.reverse_each do |r| include(r) end include(GraphQL::InternalRepresentation::Rewrite) include(ContextMethods) end end end graphql-ruby-1.11.10/lib/graphql/static_validation/definition_dependencies.rb000066400000000000000000000173431414121453000273630ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # Track fragment dependencies for operations # and expose the fragment definitions which # are used by a given operation module DefinitionDependencies attr_reader :dependencies def initialize(*) super @defdep_node_paths = {} # { name => [node, ...] } pairs for fragments (although duplicate-named fragments are _invalid_, they are _possible_) @defdep_fragment_definitions = Hash.new{ |h, k| h[k] = [] } # This tracks dependencies from fragment to Node where it was used # { fragment_definition_name => [dependent_node, dependent_node]} @defdep_dependent_definitions = Hash.new { |h, k| h[k] = Set.new } # First-level usages of spreads within definitions # (When a key has an empty list as its value, # we can resolve that key's dependents) # { definition_node => [node, node ...] } @defdep_immediate_dependencies = Hash.new { |h, k| h[k] = Set.new } # When we encounter a spread, # this node is the one who depends on it @defdep_current_parent = nil end def on_document(node, parent) node.definitions.each do |definition| if definition.is_a? GraphQL::Language::Nodes::FragmentDefinition @defdep_fragment_definitions[definition.name] << definition end end super @dependencies = dependency_map { |defn, spreads, frag| context.on_dependency_resolve_handlers.each { |h| h.call(defn, spreads, frag) } } end def on_operation_definition(node, prev_node) @defdep_node_paths[node.name] = NodeWithPath.new(node, context.path) @defdep_current_parent = node super @defdep_current_parent = nil end def on_fragment_definition(node, parent) @defdep_node_paths[node] = NodeWithPath.new(node, context.path) @defdep_current_parent = node super @defdep_current_parent = nil end def on_fragment_spread(node, parent) @defdep_node_paths[node] = NodeWithPath.new(node, context.path) # Track both sides of the dependency @defdep_dependent_definitions[node.name] << @defdep_current_parent @defdep_immediate_dependencies[@defdep_current_parent] << node super end # A map of operation definitions to an array of that operation's dependencies # @return [DependencyMap] def dependency_map(&block) @dependency_map ||= resolve_dependencies(&block) end # Map definition AST nodes to the definition AST nodes they depend on. # Expose circular dependencies. class DependencyMap # @return [Array] attr_reader :cyclical_definitions # @return [Hash>] attr_reader :unmet_dependencies # @return [Array] attr_reader :unused_dependencies def initialize @dependencies = Hash.new { |h, k| h[k] = [] } @cyclical_definitions = [] @unmet_dependencies = Hash.new { |h, k| h[k] = [] } @unused_dependencies = [] end # @return [Array] dependencies for `definition_node` def [](definition_node) @dependencies[definition_node] end end class NodeWithPath extend Forwardable attr_reader :node, :path def initialize(node, path) @node = node @path = path end def_delegators :@node, :name, :eql?, :hash end private # Return a hash of { node => [node, node ... ]} pairs # Keys are top-level definitions # Values are arrays of flattened dependencies def resolve_dependencies dependency_map = DependencyMap.new # Don't allow the loop to run more times # than the number of fragments in the document max_loops = 0 @defdep_fragment_definitions.each_value do |v| max_loops += v.size end loops = 0 # Instead of tracking independent fragments _as you visit_, # determine them at the end. This way, we can treat fragments with the # same name as if they were the same name. If _any_ of the fragments # with that name has a dependency, we record it. independent_fragment_nodes = @defdep_fragment_definitions.values.flatten - @defdep_immediate_dependencies.keys while fragment_node = independent_fragment_nodes.pop loops += 1 if loops > max_loops raise("Resolution loops exceeded the number of definitions; infinite loop detected. (Max: #{max_loops}, Current: #{loops})") end # Since it's independent, let's remove it from here. # That way, we can use the remainder to identify cycles @defdep_immediate_dependencies.delete(fragment_node) fragment_usages = @defdep_dependent_definitions[fragment_node.name] if fragment_usages.empty? # If we didn't record any usages during the visit, # then this fragment is unused. dependency_map.unused_dependencies << @defdep_node_paths[fragment_node] else fragment_usages.each do |definition_node| # Register the dependency AND second-order dependencies dependency_map[definition_node] << fragment_node dependency_map[definition_node].concat(dependency_map[fragment_node]) # Since we've registered it, remove it from our to-do list deps = @defdep_immediate_dependencies[definition_node] # Can't find a way to _just_ delete from `deps` and return the deleted entries removed, remaining = deps.partition { |spread| spread.name == fragment_node.name } @defdep_immediate_dependencies[definition_node] = remaining if block_given? yield(definition_node, removed, fragment_node) end if remaining.empty? && definition_node.is_a?(GraphQL::Language::Nodes::FragmentDefinition) && definition_node.name != fragment_node.name # If all of this definition's dependencies have # been resolved, we can now resolve its # own dependents. # # But, it's possible to have a duplicate-named fragment here. # Skip it in that case independent_fragment_nodes << definition_node end end end end # If any dependencies were _unmet_ # (eg, spreads with no corresponding definition) # then they're still in there @defdep_immediate_dependencies.each do |defn_node, deps| deps.each do |spread| if !@defdep_fragment_definitions.key?(spread.name) dependency_map.unmet_dependencies[@defdep_node_paths[defn_node]] << @defdep_node_paths[spread] deps.delete(spread) end end if deps.empty? @defdep_immediate_dependencies.delete(defn_node) end end # Anything left in @immediate_dependencies is cyclical cyclical_nodes = @defdep_immediate_dependencies.keys.map { |n| @defdep_node_paths[n] } # @immediate_dependencies also includes operation names, but we don't care about # those. They became nil when we looked them up on `@fragment_definitions`, so remove them. cyclical_nodes.compact! dependency_map.cyclical_definitions.concat(cyclical_nodes) dependency_map end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/error.rb000066400000000000000000000021661414121453000236530ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # Generates GraphQL-compliant validation message. class Error # Convenience for validators module ErrorHelper # Error `error_message` is located at `node` def error(error_message, nodes, context: nil, path: nil, extensions: {}) path ||= context.path nodes = Array(nodes) GraphQL::StaticValidation::Error.new(error_message, nodes: nodes, path: path) end end attr_reader :message attr_accessor :path def initialize(message, path: nil, nodes: []) @message = message @nodes = Array(nodes) @path = path end # A hash representation of this Message def to_h { "message" => message, "locations" => locations }.tap { |h| h["path"] = path unless path.nil? } end private def locations @nodes.map do |node| h = {"line" => node.line, "column" => node.col} h["filename"] = node.filename if node.filename h end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/interpreter_visitor.rb000066400000000000000000000004701414121453000266400ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class InterpreterVisitor < BaseVisitor include(GraphQL::StaticValidation::DefinitionDependencies) StaticValidation::ALL_RULES.reverse_each do |r| include(r) end include(ContextMethods) end end end graphql-ruby-1.11.10/lib/graphql/static_validation/literal_validator.rb000066400000000000000000000122711414121453000262210ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # Test whether `ast_value` is a valid input for `type` class LiteralValidator def initialize(context:) @context = context @warden = context.warden @invalid_response = GraphQL::Query::InputValidationResult.new(valid: false, problems: []) @valid_response = GraphQL::Query::InputValidationResult.new(valid: true, problems: []) end def validate(ast_value, type) catch(:invalid) do recursively_validate(ast_value, type) end end private def recursively_validate(ast_value, type) if type.nil? # this means we're an undefined argument, see #present_input_field_values_are_valid maybe_raise_if_invalid(ast_value) do @invalid_response end elsif ast_value.is_a?(GraphQL::Language::Nodes::NullValue) maybe_raise_if_invalid(ast_value) do type.kind.non_null? ? @invalid_response : @valid_response end elsif type.kind.non_null? maybe_raise_if_invalid(ast_value) do ast_value.nil? ? @invalid_response : recursively_validate(ast_value, type.of_type) end elsif type.kind.list? item_type = type.of_type results = ensure_array(ast_value).map { |val| recursively_validate(val, item_type) } merge_results(results) elsif ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) @valid_response elsif type.kind.scalar? && constant_scalar?(ast_value) maybe_raise_if_invalid(ast_value) do type.validate_input(ast_value, @context) end elsif type.kind.enum? maybe_raise_if_invalid(ast_value) do if ast_value.is_a?(GraphQL::Language::Nodes::Enum) type.validate_input(ast_value.name, @context) else # if our ast_value isn't an Enum it's going to be invalid so return false @invalid_response end end elsif type.kind.input_object? && ast_value.is_a?(GraphQL::Language::Nodes::InputObject) maybe_raise_if_invalid(ast_value) do merge_results([ required_input_fields_are_present(type, ast_value), present_input_field_values_are_valid(type, ast_value) ]) end else maybe_raise_if_invalid(ast_value) do @invalid_response end end end # When `error_bubbling` is false, we want to bail on the first failure that we find. # Use `throw` to escape the current call stack, returning the invalid response. def maybe_raise_if_invalid(ast_value) ret = yield if !@context.schema.error_bubbling && !ret.valid? throw(:invalid, ret) else ret end end # The GraphQL grammar supports variables embedded within scalars but graphql.js # doesn't support it so we won't either for simplicity def constant_scalar?(ast_value) if ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) false elsif ast_value.is_a?(Array) ast_value.all? { |element| constant_scalar?(element) } elsif ast_value.is_a?(GraphQL::Language::Nodes::InputObject) ast_value.arguments.all? { |arg| constant_scalar?(arg.value) } else true end end def required_input_fields_are_present(type, ast_node) # TODO - would be nice to use these to create an error message so the caller knows # that required fields are missing required_field_names = type.arguments.each_value .select { |argument| argument.type.kind.non_null? && @warden.get_argument(type, argument.name) } .map(&:name) present_field_names = ast_node.arguments.map(&:name) missing_required_field_names = required_field_names - present_field_names if @context.schema.error_bubbling missing_required_field_names.empty? ? @valid_response : @invalid_response else results = missing_required_field_names.map do |name| arg_type = @warden.get_argument(type, name).type recursively_validate(GraphQL::Language::Nodes::NullValue.new(name: name), arg_type) end merge_results(results) end end def present_input_field_values_are_valid(type, ast_node) results = ast_node.arguments.map do |value| field = @warden.get_argument(type, value.name) # we want to call validate on an argument even if it's an invalid one # so that our raise exception is on it instead of the entire InputObject field_type = field && field.type recursively_validate(value.value, field_type) end merge_results(results) end def ensure_array(value) value.is_a?(Array) ? value : [value] end def merge_results(results_list) merged_result = Query::InputValidationResult.new results_list.each do |inner_result| merged_result.merge_result!([], inner_result) end merged_result end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/no_validate_visitor.rb000066400000000000000000000004541414121453000265640ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class NoValidateVisitor < StaticValidation::BaseVisitor include(GraphQL::InternalRepresentation::Rewrite) include(GraphQL::StaticValidation::DefinitionDependencies) include(ContextMethods) end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/000077500000000000000000000000001414121453000233225ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb000066400000000000000000000043771414121453000324310ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module ArgumentLiteralsAreCompatible def on_argument(node, parent) # Check the child arguments first; # don't add a new error if one of them reports an error super # Don't validate variables here if node.value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) return end if @context.schema.error_bubbling || context.errors.none? { |err| err.path.take(@path.size) == @path } parent_defn = parent_definition(parent) if parent_defn && (arg_defn = parent_defn.arguments[node.name]) validation_result = context.validate_literal(node.value, arg_defn.type) if !validation_result.valid? kind_of_node = node_type(parent) error_arg_name = parent_name(parent, parent_defn) string_value = if node.value == Float::INFINITY "" else " (#{GraphQL::Language::Printer.new.print(node.value)})" end problems = validation_result.problems first_problem = problems && problems.first if first_problem message = first_problem["message"] # This is some legacy stuff from when `CoercionError` was raised thru the stack if message coerce_extensions = first_problem["extensions"] || { "code" => "argumentLiteralsIncompatible" } end end error_options = { nodes: parent, type: kind_of_node, argument: node.name } if coerce_extensions error_options[:coerce_extensions] = coerce_extensions end message ||= "Argument '#{node.name}' on #{kind_of_node} '#{error_arg_name}' has an invalid value#{string_value}. Expected type '#{arg_defn.type.to_type_signature}'." error = GraphQL::StaticValidation::ArgumentLiteralsAreCompatibleError.new( message, **error_options ) add_error(error) end end end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb000066400000000000000000000023531414121453000336320ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class ArgumentLiteralsAreCompatibleError < StaticValidation::Error attr_reader :type_name attr_reader :argument_name def initialize(message, path: nil, nodes: [], type:, argument: nil, extensions: nil, coerce_extensions: nil) super(message, path: path, nodes: nodes) @type_name = type @argument_name = argument @extensions = extensions @coerce_extensions = coerce_extensions end # A hash representation of this Message def to_h if @coerce_extensions extensions = @coerce_extensions # This is for legacy compat -- but this key is supposed to be a GraphQL type name :confounded: extensions["typeName"] = "CoercionError" else extensions = { "code" => code, "typeName" => type_name } if argument_name extensions["argumentName"] = argument_name end end extensions.merge!(@extensions) unless @extensions.nil? super.merge({ "extensions" => extensions }) end def code "argumentLiteralsIncompatible" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/argument_names_are_unique.rb000066400000000000000000000015641414121453000310770ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module ArgumentNamesAreUnique include GraphQL::StaticValidation::Error::ErrorHelper def on_field(node, parent) validate_arguments(node) super end def on_directive(node, parent) validate_arguments(node) super end def validate_arguments(node) argument_defns = node.arguments if argument_defns.any? args_by_name = Hash.new { |h, k| h[k] = [] } argument_defns.each { |a| args_by_name[a.name] << a } args_by_name.each do |name, defns| if defns.size > 1 add_error(GraphQL::StaticValidation::ArgumentNamesAreUniqueError.new("There can be only one argument named \"#{name}\"", nodes: defns, name: name)) end end end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/argument_names_are_unique_error.rb000066400000000000000000000011271414121453000323030ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class ArgumentNamesAreUniqueError < StaticValidation::Error attr_reader :name def initialize(message, path: nil, nodes: [], name:) super(message, path: path, nodes: nodes) @name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "name" => name } super.merge({ "extensions" => extensions }) end def code "argumentNotUnique" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/arguments_are_defined.rb000066400000000000000000000041531414121453000301640ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module ArgumentsAreDefined def on_argument(node, parent) parent_defn = parent_definition(parent) if parent_defn && context.warden.get_argument(parent_defn, node.name) super elsif parent_defn kind_of_node = node_type(parent) error_arg_name = parent_name(parent, parent_defn) add_error(GraphQL::StaticValidation::ArgumentsAreDefinedError.new( "#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'", nodes: node, name: error_arg_name, type: kind_of_node, argument: node.name )) else # Some other weird error super end end private # TODO smell: these methods are added to all visitors, since they're included in a module. def parent_name(parent, type_defn) case parent when GraphQL::Language::Nodes::Field parent.alias || parent.name when GraphQL::Language::Nodes::InputObject type_defn.graphql_name when GraphQL::Language::Nodes::Argument, GraphQL::Language::Nodes::Directive parent.name else raise "Invariant: Unexpected parent #{parent.inspect} (#{parent.class})" end end def node_type(parent) parent.class.name.split("::").last end def parent_definition(parent) case parent when GraphQL::Language::Nodes::InputObject arg_defn = context.argument_definition if arg_defn.nil? nil else arg_ret_type = arg_defn.type.unwrap if arg_ret_type.kind.input_object? arg_ret_type else nil end end when GraphQL::Language::Nodes::Directive context.schema.directives[parent.name] when GraphQL::Language::Nodes::Field context.field_definition else raise "Unexpected argument parent: #{parent.class} (##{parent})" end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/arguments_are_defined_error.rb000066400000000000000000000014571414121453000314010ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class ArgumentsAreDefinedError < StaticValidation::Error attr_reader :name attr_reader :type_name attr_reader :argument_name def initialize(message, path: nil, nodes: [], name:, type:, argument:) super(message, path: path, nodes: nodes) @name = name @type_name = type @argument_name = argument end # A hash representation of this Message def to_h extensions = { "code" => code, "name" => name, "typeName" => type_name, "argumentName" => argument_name } super.merge({ "extensions" => extensions }) end def code "argumentNotAccepted" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/directives_are_defined.rb000066400000000000000000000010701414121453000303130ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module DirectivesAreDefined def initialize(*) super @directive_names = context.schema.directives.keys end def on_directive(node, parent) if !@directive_names.include?(node.name) add_error(GraphQL::StaticValidation::DirectivesAreDefinedError.new( "Directive @#{node.name} is not defined", nodes: node, directive: node.name )) else super end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/directives_are_defined_error.rb000066400000000000000000000012061414121453000315250ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class DirectivesAreDefinedError < StaticValidation::Error attr_reader :directive_name def initialize(message, path: nil, nodes: [], directive:) super(message, path: path, nodes: nodes) @directive_name = directive end # A hash representation of this Message def to_h extensions = { "code" => code, "directiveName" => directive_name } super.merge({ "extensions" => extensions }) end def code "undefinedDirective" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb000066400000000000000000000052521414121453000325630ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module DirectivesAreInValidLocations include GraphQL::Language def on_directive(node, parent) validate_location(node, parent, context.schema.directives) super end private LOCATION_MESSAGE_NAMES = { GraphQL::Directive::QUERY => "queries", GraphQL::Directive::MUTATION => "mutations", GraphQL::Directive::SUBSCRIPTION => "subscriptions", GraphQL::Directive::FIELD => "fields", GraphQL::Directive::FRAGMENT_DEFINITION => "fragment definitions", GraphQL::Directive::FRAGMENT_SPREAD => "fragment spreads", GraphQL::Directive::INLINE_FRAGMENT => "inline fragments", } SIMPLE_LOCATIONS = { Nodes::Field => GraphQL::Directive::FIELD, Nodes::InlineFragment => GraphQL::Directive::INLINE_FRAGMENT, Nodes::FragmentSpread => GraphQL::Directive::FRAGMENT_SPREAD, Nodes::FragmentDefinition => GraphQL::Directive::FRAGMENT_DEFINITION, } SIMPLE_LOCATION_NODES = SIMPLE_LOCATIONS.keys def validate_location(ast_directive, ast_parent, directives) directive_defn = directives[ast_directive.name] case ast_parent when Nodes::OperationDefinition required_location = GraphQL::Directive.const_get(ast_parent.operation_type.upcase) assert_includes_location(directive_defn, ast_directive, required_location) when *SIMPLE_LOCATION_NODES required_location = SIMPLE_LOCATIONS[ast_parent.class] assert_includes_location(directive_defn, ast_directive, required_location) else add_error(GraphQL::StaticValidation::DirectivesAreInValidLocationsError.new( "Directives can't be applied to #{ast_parent.class.name}s", nodes: ast_directive, target: ast_parent.class.name )) end end def assert_includes_location(directive_defn, directive_ast, required_location) if !directive_defn.locations.include?(required_location) location_name = LOCATION_MESSAGE_NAMES[required_location] allowed_location_names = directive_defn.locations.map { |loc| LOCATION_MESSAGE_NAMES[loc] } add_error(GraphQL::StaticValidation::DirectivesAreInValidLocationsError.new( "'@#{directive_defn.graphql_name}' can't be applied to #{location_name} (allowed: #{allowed_location_names.join(", ")})", nodes: directive_ast, target: location_name, name: directive_defn.graphql_name )) end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/directives_are_in_valid_locations_error.rb000066400000000000000000000013511414121453000337700ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class DirectivesAreInValidLocationsError < StaticValidation::Error attr_reader :target_name attr_reader :name def initialize(message, path: nil, nodes: [], target:, name: nil) super(message, path: path, nodes: nodes) @target_name = target @name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "targetName" => target_name }.tap { |h| h["name"] = name unless name.nil? } super.merge({ "extensions" => extensions }) end def code "directiveCannotBeApplied" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb000066400000000000000000000017321414121453000311620ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FieldsAreDefinedOnType def on_field(node, parent) parent_type = @object_types[-2] field = context.warden.get_field(parent_type, node.name) if field.nil? if parent_type.kind.union? add_error(GraphQL::StaticValidation::FieldsHaveAppropriateSelectionsError.new( "Selections can't be made directly on unions (see selections on #{parent_type.graphql_name})", nodes: parent, node_name: parent_type.graphql_name )) else add_error(GraphQL::StaticValidation::FieldsAreDefinedOnTypeError.new( "Field '#{node.name}' doesn't exist on type '#{parent_type.graphql_name}'", nodes: node, field: node.name, type: parent_type.graphql_name )) end else super end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fields_are_defined_on_type_error.rb000066400000000000000000000013151414121453000323700ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FieldsAreDefinedOnTypeError < StaticValidation::Error attr_reader :type_name attr_reader :field_name def initialize(message, path: nil, nodes: [], type:, field:) super(message, path: path, nodes: nodes) @type_name = type @field_name = field end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name, "fieldName" => field_name } super.merge({ "extensions" => extensions }) end def code "undefinedField" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb000066400000000000000000000050241414121453000327570ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # Scalars _can't_ have selections # Objects _must_ have selections module FieldsHaveAppropriateSelections include GraphQL::StaticValidation::Error::ErrorHelper def on_field(node, parent) field_defn = field_definition if validate_field_selections(node, field_defn.type.unwrap) super end end def on_operation_definition(node, _parent) if validate_field_selections(node, type_definition) super end end private def validate_field_selections(ast_node, resolved_type) msg = if resolved_type.nil? nil elsif resolved_type.kind.scalar? && ast_node.selections.any? if ast_node.selections.first.is_a?(GraphQL::Language::Nodes::InlineFragment) "Selections can't be made on scalars (%{node_name} returns #{resolved_type.graphql_name} but has inline fragments [#{ast_node.selections.map(&:type).map(&:name).join(", ")}])" else "Selections can't be made on scalars (%{node_name} returns #{resolved_type.graphql_name} but has selections [#{ast_node.selections.map(&:name).join(", ")}])" end elsif resolved_type.kind.fields? && ast_node.selections.empty? "Field must have selections (%{node_name} returns #{resolved_type.graphql_name} but has no selections. Did you mean '#{ast_node.name} { ... }'?)" else nil end if msg node_name = case ast_node when GraphQL::Language::Nodes::Field "field '#{ast_node.name}'" when GraphQL::Language::Nodes::OperationDefinition if ast_node.name.nil? "anonymous query" else "#{ast_node.operation_type} '#{ast_node.name}'" end else raise("Unexpected node #{ast_node}") end extensions = { "rule": "StaticValidation::FieldsHaveAppropriateSelections", "name": node_name.to_s } unless resolved_type.nil? extensions["type"] = resolved_type.to_type_signature end add_error(GraphQL::StaticValidation::FieldsHaveAppropriateSelectionsError.new( msg % { node_name: node_name }, nodes: ast_node, node_name: node_name.to_s, type: resolved_type.nil? ? nil : resolved_type.graphql_name, )) false else true end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fields_have_appropriate_selections_error.rb000066400000000000000000000013721414121453000341720ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FieldsHaveAppropriateSelectionsError < StaticValidation::Error attr_reader :type_name attr_reader :node_name def initialize(message, path: nil, nodes: [], node_name:, type: nil) super(message, path: path, nodes: nodes) @node_name = node_name @type_name = type end # A hash representation of this Message def to_h extensions = { "code" => code, "nodeName" => node_name }.tap { |h| h["typeName"] = type_name unless type_name.nil? } super.merge({ "extensions" => extensions }) end def code "selectionMismatch" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fields_will_merge.rb000066400000000000000000000335161414121453000273330ustar00rootroot00000000000000 # frozen_string_literal: true # frozen_string_literal: true module GraphQL module StaticValidation module FieldsWillMerge # Validates that a selection set is valid if all fields (including spreading any # fragments) either correspond to distinct response names or can be merged # without ambiguity. # # Original Algorithm: https://github.com/graphql/graphql-js/blob/master/src/validation/rules/OverlappingFieldsCanBeMerged.js NO_ARGS = {}.freeze Field = Struct.new(:node, :definition, :owner_type, :parents) FragmentSpread = Struct.new(:name, :parents) def initialize(*) super @visited_fragments = {} @compared_fragments = {} end def on_operation_definition(node, _parent) conflicts_within_selection_set(node, type_definition) super end def on_field(node, _parent) conflicts_within_selection_set(node, type_definition) super end private def conflicts_within_selection_set(node, parent_type) return if parent_type.nil? fields, fragment_spreads = fields_and_fragments_from_selection(node, owner_type: parent_type, parents: []) # (A) Find find all conflicts "within" the fields of this selection set. find_conflicts_within(fields) fragment_spreads.each_with_index do |fragment_spread, i| are_mutually_exclusive = mutually_exclusive?( fragment_spread.parents, [parent_type] ) # (B) Then find conflicts between these fields and those represented by # each spread fragment name found. find_conflicts_between_fields_and_fragment( fragment_spread, fields, mutually_exclusive: are_mutually_exclusive, ) # (C) Then compare this fragment with all other fragments found in this # selection set to collect conflicts between fragments spread together. # This compares each item in the list of fragment names to every other # item in that same list (except for itself). fragment_spreads[i + 1..-1].each do |fragment_spread2| are_mutually_exclusive = mutually_exclusive?( fragment_spread.parents, fragment_spread2.parents ) find_conflicts_between_fragments( fragment_spread, fragment_spread2, mutually_exclusive: are_mutually_exclusive, ) end end end def find_conflicts_between_fragments(fragment_spread1, fragment_spread2, mutually_exclusive:) fragment_name1 = fragment_spread1.name fragment_name2 = fragment_spread2.name return if fragment_name1 == fragment_name2 cache_key = compared_fragments_key( fragment_name1, fragment_name2, mutually_exclusive, ) if @compared_fragments.key?(cache_key) return else @compared_fragments[cache_key] = true end fragment1 = context.fragments[fragment_name1] fragment2 = context.fragments[fragment_name2] return if fragment1.nil? || fragment2.nil? fragment_type1 = context.warden.get_type(fragment1.type.name) fragment_type2 = context.warden.get_type(fragment2.type.name) return if fragment_type1.nil? || fragment_type2.nil? fragment_fields1, fragment_spreads1 = fields_and_fragments_from_selection( fragment1, owner_type: fragment_type1, parents: [*fragment_spread1.parents, fragment_type1] ) fragment_fields2, fragment_spreads2 = fields_and_fragments_from_selection( fragment2, owner_type: fragment_type1, parents: [*fragment_spread2.parents, fragment_type2] ) # (F) First, find all conflicts between these two collections of fields # (not including any nested fragments). find_conflicts_between( fragment_fields1, fragment_fields2, mutually_exclusive: mutually_exclusive, ) # (G) Then collect conflicts between the first fragment and any nested # fragments spread in the second fragment. fragment_spreads2.each do |fragment_spread| find_conflicts_between_fragments( fragment_spread1, fragment_spread, mutually_exclusive: mutually_exclusive, ) end # (G) Then collect conflicts between the first fragment and any nested # fragments spread in the second fragment. fragment_spreads1.each do |fragment_spread| find_conflicts_between_fragments( fragment_spread2, fragment_spread, mutually_exclusive: mutually_exclusive, ) end end def find_conflicts_between_fields_and_fragment(fragment_spread, fields, mutually_exclusive:) fragment_name = fragment_spread.name return if @visited_fragments.key?(fragment_name) @visited_fragments[fragment_name] = true fragment = context.fragments[fragment_name] return if fragment.nil? fragment_type = context.warden.get_type(fragment.type.name) return if fragment_type.nil? fragment_fields, fragment_spreads = fields_and_fragments_from_selection(fragment, owner_type: fragment_type, parents: [*fragment_spread.parents, fragment_type]) # (D) First find any conflicts between the provided collection of fields # and the collection of fields represented by the given fragment. find_conflicts_between( fields, fragment_fields, mutually_exclusive: mutually_exclusive, ) # (E) Then collect any conflicts between the provided collection of fields # and any fragment names found in the given fragment. fragment_spreads.each do |fragment_spread| find_conflicts_between_fields_and_fragment( fragment_spread, fields, mutually_exclusive: mutually_exclusive, ) end end def find_conflicts_within(response_keys) response_keys.each do |key, fields| next if fields.size < 2 # find conflicts within nodes for i in 0..fields.size - 1 for j in i + 1..fields.size - 1 find_conflict(key, fields[i], fields[j]) end end end end def find_conflict(response_key, field1, field2, mutually_exclusive: false) node1 = field1.node node2 = field2.node are_mutually_exclusive = mutually_exclusive || mutually_exclusive?(field1.parents, field2.parents) if !are_mutually_exclusive if node1.name != node2.name errored_nodes = [node1.name, node2.name].sort.join(" or ") msg = "Field '#{response_key}' has a field conflict: #{errored_nodes}?" add_error(GraphQL::StaticValidation::FieldsWillMergeError.new( msg, nodes: [node1, node2], path: [], field_name: response_key, conflicts: errored_nodes )) end if !same_arguments?(node1, node2) args = [serialize_field_args(node1), serialize_field_args(node2)] conflicts = args.map { |arg| GraphQL::Language.serialize(arg) }.join(" or ") msg = "Field '#{response_key}' has an argument conflict: #{conflicts}?" add_error(GraphQL::StaticValidation::FieldsWillMergeError.new( msg, nodes: [node1, node2], path: [], field_name: response_key, conflicts: conflicts )) end end find_conflicts_between_sub_selection_sets( field1, field2, mutually_exclusive: are_mutually_exclusive, ) end def find_conflicts_between_sub_selection_sets(field1, field2, mutually_exclusive:) return if field1.definition.nil? || field2.definition.nil? return_type1 = field1.definition.type.unwrap return_type2 = field2.definition.type.unwrap parents1 = [return_type1] parents2 = [return_type2] fields, fragment_spreads = fields_and_fragments_from_selection( field1.node, owner_type: return_type1, parents: parents1 ) fields2, fragment_spreads2 = fields_and_fragments_from_selection( field2.node, owner_type: return_type2, parents: parents2 ) # (H) First, collect all conflicts between these two collections of field. find_conflicts_between(fields, fields2, mutually_exclusive: mutually_exclusive) # (I) Then collect conflicts between the first collection of fields and # those referenced by each fragment name associated with the second. fragment_spreads2.each do |fragment_spread| find_conflicts_between_fields_and_fragment( fragment_spread, fields, mutually_exclusive: mutually_exclusive, ) end # (I) Then collect conflicts between the second collection of fields and # those referenced by each fragment name associated with the first. fragment_spreads.each do |fragment_spread| find_conflicts_between_fields_and_fragment( fragment_spread, fields2, mutually_exclusive: mutually_exclusive, ) end # (J) Also collect conflicts between any fragment names by the first and # fragment names by the second. This compares each item in the first set of # names to each item in the second set of names. fragment_spreads.each do |frag1| fragment_spreads2.each do |frag2| find_conflicts_between_fragments( frag1, frag2, mutually_exclusive: mutually_exclusive ) end end end def find_conflicts_between(response_keys, response_keys2, mutually_exclusive:) response_keys.each do |key, fields| fields2 = response_keys2[key] if fields2 fields.each do |field| fields2.each do |field2| find_conflict( key, field, field2, mutually_exclusive: mutually_exclusive, ) end end end end end NO_SELECTIONS = [{}.freeze, [].freeze].freeze def fields_and_fragments_from_selection(node, owner_type:, parents:) if node.selections.empty? NO_SELECTIONS else fields, fragment_spreads = find_fields_and_fragments(node.selections, owner_type: owner_type, parents: parents, fields: [], fragment_spreads: []) response_keys = fields.group_by { |f| f.node.alias || f.node.name } [response_keys, fragment_spreads] end end def find_fields_and_fragments(selections, owner_type:, parents:, fields:, fragment_spreads:) selections.each do |node| case node when GraphQL::Language::Nodes::Field definition = context.schema.get_field(owner_type, node.name) fields << Field.new(node, definition, owner_type, parents) when GraphQL::Language::Nodes::InlineFragment fragment_type = node.type ? context.warden.get_type(node.type.name) : owner_type find_fields_and_fragments(node.selections, parents: [*parents, fragment_type], owner_type: owner_type, fields: fields, fragment_spreads: fragment_spreads) if fragment_type when GraphQL::Language::Nodes::FragmentSpread fragment_spreads << FragmentSpread.new(node.name, parents) end end [fields, fragment_spreads] end def same_arguments?(field1, field2) # Check for incompatible / non-identical arguments on this node: arguments1 = field1.arguments arguments2 = field2.arguments return false if arguments1.length != arguments2.length arguments1.all? do |argument1| argument2 = arguments2.find { |argument| argument.name == argument1.name } return false if argument2.nil? serialize_arg(argument1.value) == serialize_arg(argument2.value) end end def serialize_arg(arg_value) case arg_value when GraphQL::Language::Nodes::AbstractNode arg_value.to_query_string when Array "[#{arg_value.map { |a| serialize_arg(a) }.join(", ")}]" else GraphQL::Language.serialize(arg_value) end end def serialize_field_args(field) serialized_args = {} field.arguments.each do |argument| serialized_args[argument.name] = serialize_arg(argument.value) end serialized_args end def compared_fragments_key(frag1, frag2, exclusive) # Cache key to not compare two fragments more than once. # The key includes both fragment names sorted (this way we # avoid computing "A vs B" and "B vs A"). It also includes # "exclusive" since the result may change depending on the parent_type "#{[frag1, frag2].sort.join('-')}-#{exclusive}" end # Given two list of parents, find out if they are mutually exclusive # In this context, `parents` represends the "self scope" of the field, # what types may be found at this point in the query. def mutually_exclusive?(parents1, parents2) parents1.each do |type1| parents2.each do |type2| # If the types we're comparing are both different object types, # they have to be mutually exclusive. if type1 != type2 && type1.kind.object? && type2.kind.object? return true end end end false end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fields_will_merge_error.rb000066400000000000000000000013321414121453000305330ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FieldsWillMergeError < StaticValidation::Error attr_reader :field_name attr_reader :conflicts def initialize(message, path: nil, nodes: [], field_name:, conflicts:) super(message, path: path, nodes: nodes) @field_name = field_name @conflicts = conflicts end # A hash representation of this Message def to_h extensions = { "code" => code, "fieldName" => field_name, "conflicts" => conflicts } super.merge({ "extensions" => extensions }) end def code "fieldConflict" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fragment_names_are_unique.rb000066400000000000000000000013301414121453000310470ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentNamesAreUnique def initialize(*) super @fragments_by_name = Hash.new { |h, k| h[k] = [] } end def on_fragment_definition(node, parent) @fragments_by_name[node.name] << node super end def on_document(_n, _p) super @fragments_by_name.each do |name, fragments| if fragments.length > 1 add_error(GraphQL::StaticValidation::FragmentNamesAreUniqueError.new( %|Fragment name "#{name}" must be unique|, nodes: fragments, name: name )) end end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fragment_names_are_unique_error.rb000066400000000000000000000011711414121453000322630ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentNamesAreUniqueError < StaticValidation::Error attr_reader :fragment_name def initialize(message, path: nil, nodes: [], name:) super(message, path: path, nodes: nodes) @fragment_name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "fragmentName" => fragment_name } super.merge({ "extensions" => extensions }) end def code "fragmentNotUnique" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb000066400000000000000000000046771414121453000317400ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentSpreadsArePossible def initialize(*) super @spreads_to_validate = [] end def on_inline_fragment(node, parent) fragment_parent = context.object_types[-2] fragment_child = context.object_types.last if fragment_child validate_fragment_in_scope(fragment_parent, fragment_child, node, context, context.path) end super end def on_fragment_spread(node, parent) fragment_parent = context.object_types.last @spreads_to_validate << FragmentSpread.new(node: node, parent_type: fragment_parent, path: context.path) super end def on_document(node, parent) super @spreads_to_validate.each do |frag_spread| frag_node = context.fragments[frag_spread.node.name] if frag_node fragment_child_name = frag_node.type.name fragment_child = context.warden.get_type(fragment_child_name) # Might be non-existent type name if fragment_child validate_fragment_in_scope(frag_spread.parent_type, fragment_child, frag_spread.node, context, frag_spread.path) end end end end private def validate_fragment_in_scope(parent_type, child_type, node, context, path) if !child_type.kind.fields? # It's not a valid fragment type, this error was handled someplace else return end parent_types = context.warden.possible_types(parent_type.unwrap) child_types = context.warden.possible_types(child_type.unwrap) if child_types.none? { |c| parent_types.include?(c) } name = node.respond_to?(:name) ? " #{node.name}" : "" add_error(GraphQL::StaticValidation::FragmentSpreadsArePossibleError.new( "Fragment#{name} on #{child_type.graphql_name} can't be spread inside #{parent_type.graphql_name}", nodes: node, path: path, fragment_name: name.empty? ? "unknown" : name, type: child_type.graphql_name, parent: parent_type.graphql_name )) end end class FragmentSpread attr_reader :node, :parent_type, :path def initialize(node:, parent_type:, path:) @node = node @parent_type = parent_type @path = path end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fragment_spreads_are_possible_error.rb000066400000000000000000000015401414121453000331330ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentSpreadsArePossibleError < StaticValidation::Error attr_reader :type_name attr_reader :fragment_name attr_reader :parent_name def initialize(message, path: nil, nodes: [], type:, fragment_name:, parent:) super(message, path: path, nodes: nodes) @type_name = type @fragment_name = fragment_name @parent_name = parent end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name, "fragmentName" => fragment_name, "parentName" => parent_name } super.merge({ "extensions" => extensions }) end def code "cannotSpreadFragment" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fragment_types_exist.rb000066400000000000000000000016431414121453000301160ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentTypesExist def on_fragment_definition(node, _parent) if validate_type_exists(node) super end end def on_inline_fragment(node, _parent) if validate_type_exists(node) super end end private def validate_type_exists(fragment_node) if !fragment_node.type true else type_name = fragment_node.type.name type = context.warden.get_type(type_name) if type.nil? add_error(GraphQL::StaticValidation::FragmentTypesExistError.new( "No such type #{type_name}, so it can't be a fragment condition", nodes: fragment_node, type: type_name )) false else true end end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fragment_types_exist_error.rb000066400000000000000000000011411414121453000313200ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentTypesExistError < StaticValidation::Error attr_reader :type_name def initialize(message, path: nil, nodes: [], type:) super(message, path: path, nodes: nodes) @type_name = type end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name } super.merge({ "extensions" => extensions }) end def code "undefinedType" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fragments_are_finite.rb000066400000000000000000000011661414121453000300260ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentsAreFinite def on_document(_n, _p) super dependency_map = context.dependencies dependency_map.cyclical_definitions.each do |defn| if defn.node.is_a?(GraphQL::Language::Nodes::FragmentDefinition) add_error(GraphQL::StaticValidation::FragmentsAreFiniteError.new( "Fragment #{defn.name} contains an infinite loop", nodes: defn.node, path: defn.path, name: defn.name )) end end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fragments_are_finite_error.rb000066400000000000000000000011601414121453000312310ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentsAreFiniteError < StaticValidation::Error attr_reader :fragment_name def initialize(message, path: nil, nodes: [], name:) super(message, path: path, nodes: nodes) @fragment_name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "fragmentName" => fragment_name } super.merge({ "extensions" => extensions }) end def code "infiniteLoop" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fragments_are_named.rb000066400000000000000000000006031414121453000276270ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentsAreNamed def on_fragment_definition(node, _parent) if node.name.nil? add_error(GraphQL::StaticValidation::FragmentsAreNamedError.new( "Fragment definition has no name", nodes: node )) end super end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fragments_are_named_error.rb000066400000000000000000000010041414121453000310340ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentsAreNamedError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code, } super.merge({ "extensions" => extensions }) end def code "anonymousFragment" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb000066400000000000000000000020021414121453000324600ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentsAreOnCompositeTypes def on_fragment_definition(node, parent) validate_type_is_composite(node) && super end def on_inline_fragment(node, parent) validate_type_is_composite(node) && super end private def validate_type_is_composite(node) node_type = node.type if node_type.nil? # Inline fragment on the same type true else type_name = node_type.to_query_string type_def = context.warden.get_type(type_name) if type_def.nil? || !type_def.kind.composite? add_error(GraphQL::StaticValidation::FragmentsAreOnCompositeTypesError.new( "Invalid fragment on type #{type_name} (must be Union, Interface or Object)", nodes: node, type: type_name )) false else true end end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fragments_are_on_composite_types_error.rb000066400000000000000000000012311414121453000336740ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentsAreOnCompositeTypesError < StaticValidation::Error attr_reader :type_name attr_reader :argument_name def initialize(message, path: nil, nodes: [], type:) super(message, path: path, nodes: nodes) @type_name = type end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name } super.merge({ "extensions" => extensions }) end def code "fragmentOnNonCompositeType" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fragments_are_used.rb000066400000000000000000000020461414121453000275060ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module FragmentsAreUsed def on_document(node, parent) super dependency_map = context.dependencies dependency_map.unmet_dependencies.each do |op_defn, spreads| spreads.each do |fragment_spread| add_error(GraphQL::StaticValidation::FragmentsAreUsedError.new( "Fragment #{fragment_spread.name} was used, but not defined", nodes: fragment_spread.node, path: fragment_spread.path, fragment: fragment_spread.name )) end end dependency_map.unused_dependencies.each do |fragment| if fragment && !fragment.name.nil? add_error(GraphQL::StaticValidation::FragmentsAreUsedError.new( "Fragment #{fragment.name} was defined, but not used", nodes: fragment.node, path: fragment.path, fragment: fragment.name )) end end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/fragments_are_used_error.rb000066400000000000000000000011761414121453000307220ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class FragmentsAreUsedError < StaticValidation::Error attr_reader :fragment_name def initialize(message, path: nil, nodes: [], fragment:) super(message, path: path, nodes: nodes) @fragment_name = fragment end # A hash representation of this Message def to_h extensions = { "code" => code, "fragmentName" => fragment_name } super.merge({ "extensions" => extensions }) end def code "useAndDefineFragment" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/input_object_names_are_unique.rb000066400000000000000000000014651414121453000317420ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module InputObjectNamesAreUnique def on_input_object(node, parent) validate_input_fields(node) super end private def validate_input_fields(node) input_field_defns = node.arguments input_fields_by_name = Hash.new { |h, k| h[k] = [] } input_field_defns.each { |a| input_fields_by_name[a.name] << a } input_fields_by_name.each do |name, defns| if defns.size > 1 error = GraphQL::StaticValidation::InputObjectNamesAreUniqueError.new( "There can be only one input field named \"#{name}\"", nodes: defns, name: name ) add_error(error) end end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb000066400000000000000000000011341414121453000331440ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class InputObjectNamesAreUniqueError < StaticValidation::Error attr_reader :name def initialize(message, path: nil, nodes: [], name:) super(message, path: path, nodes: nodes) @name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "name" => name } super.merge({ "extensions" => extensions }) end def code "inputFieldNotUnique" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/mutation_root_exists.rb000066400000000000000000000007521414121453000301550ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module MutationRootExists def on_operation_definition(node, _parent) if node.operation_type == 'mutation' && context.warden.root_type_for_operation("mutation").nil? add_error(GraphQL::StaticValidation::MutationRootExistsError.new( 'Schema is not configured for mutations', nodes: node )) else super end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/mutation_root_exists_error.rb000066400000000000000000000010201414121453000313530ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class MutationRootExistsError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code, } super.merge({ "extensions" => extensions }) end def code "missingMutationConfiguration" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/no_definitions_are_present.rb000066400000000000000000000027111414121453000312460ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module NoDefinitionsArePresent include GraphQL::StaticValidation::Error::ErrorHelper def initialize(*) super @schema_definition_nodes = [] end def on_invalid_node(node, parent) @schema_definition_nodes << node nil end alias :on_directive_definition :on_invalid_node alias :on_schema_definition :on_invalid_node alias :on_scalar_type_definition :on_invalid_node alias :on_object_type_definition :on_invalid_node alias :on_input_object_type_definition :on_invalid_node alias :on_interface_type_definition :on_invalid_node alias :on_union_type_definition :on_invalid_node alias :on_enum_type_definition :on_invalid_node alias :on_schema_extension :on_invalid_node alias :on_scalar_type_extension :on_invalid_node alias :on_object_type_extension :on_invalid_node alias :on_input_object_type_extension :on_invalid_node alias :on_interface_type_extension :on_invalid_node alias :on_union_type_extension :on_invalid_node alias :on_enum_type_extension :on_invalid_node def on_document(node, parent) super if @schema_definition_nodes.any? add_error(GraphQL::StaticValidation::NoDefinitionsArePresentError.new(%|Query cannot contain schema definitions|, nodes: @schema_definition_nodes)) end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/no_definitions_are_present_error.rb000066400000000000000000000010261414121453000324550ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class NoDefinitionsArePresentError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code, } super.merge({ "extensions" => extensions }) end def code "queryContainsSchemaDefinitions" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/operation_names_are_valid.rb000066400000000000000000000020211414121453000310330ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module OperationNamesAreValid def initialize(*) super @operation_names = Hash.new { |h, k| h[k] = [] } end def on_operation_definition(node, parent) @operation_names[node.name] << node super end def on_document(node, parent) super op_count = @operation_names.values.inject(0) { |m, v| m + v.size } @operation_names.each do |name, nodes| if name.nil? && op_count > 1 add_error(GraphQL::StaticValidation::OperationNamesAreValidError.new( %|Operation name is required when multiple operations are present|, nodes: nodes )) elsif nodes.length > 1 add_error(GraphQL::StaticValidation::OperationNamesAreValidError.new( %|Operation name "#{name}" must be unique|, nodes: nodes, name: name )) end end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/operation_names_are_valid_error.rb000066400000000000000000000012451414121453000322530ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class OperationNamesAreValidError < StaticValidation::Error attr_reader :operation_name def initialize(message, path: nil, nodes: [], name: nil) super(message, path: path, nodes: nodes) @operation_name = name end # A hash representation of this Message def to_h extensions = { "code" => code }.tap { |h| h["operationName"] = operation_name unless operation_name.nil? } super.merge({ "extensions" => extensions }) end def code "uniquelyNamedOperations" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/required_arguments_are_present.rb000066400000000000000000000023711414121453000321460ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module RequiredArgumentsArePresent def on_field(node, _parent) assert_required_args(node, field_definition) super end def on_directive(node, _parent) directive_defn = context.schema.directives[node.name] assert_required_args(node, directive_defn) super end private def assert_required_args(ast_node, defn) present_argument_names = ast_node.arguments.map(&:name) required_argument_names = defn.arguments.each_value .select { |a| a.type.kind.non_null? && !a.default_value? && context.warden.get_argument(defn, a.name) } .map(&:name) missing_names = required_argument_names - present_argument_names if missing_names.any? add_error(GraphQL::StaticValidation::RequiredArgumentsArePresentError.new( "#{ast_node.class.name.split("::").last} '#{ast_node.name}' is missing required arguments: #{missing_names.join(", ")}", nodes: ast_node, class_name: ast_node.class.name.split("::").last, name: ast_node.name, arguments: "#{missing_names.join(", ")}" )) end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/required_arguments_are_present_error.rb000066400000000000000000000014771414121453000333650ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class RequiredArgumentsArePresentError < StaticValidation::Error attr_reader :class_name attr_reader :name attr_reader :arguments def initialize(message, path: nil, nodes: [], class_name:, name:, arguments:) super(message, path: path, nodes: nodes) @class_name = class_name @name = name @arguments = arguments end # A hash representation of this Message def to_h extensions = { "code" => code, "className" => class_name, "name" => name, "arguments" => arguments } super.merge({ "extensions" => extensions }) end def code "missingRequiredArguments" end end end end required_input_object_attributes_are_present.rb000066400000000000000000000041311414121453000350110ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/static_validation/rules# frozen_string_literal: true module GraphQL module StaticValidation module RequiredInputObjectAttributesArePresent def on_input_object(node, parent) if parent.is_a? GraphQL::Language::Nodes::Argument validate_input_object(node, context, parent) end super end private def get_parent_type(context, parent) # If argument_definition is defined we're at nested object # and need to refer to the containing input object type rather # than the field_definition. # h/t @rmosolgo arg_defn = context.argument_definition # Double checking that arg_defn is an input object as nested # scalars, namely JSON, can make it to this branch defn = if arg_defn && arg_defn.type.unwrap.kind.input_object? arg_defn.type.unwrap else context.field_definition end parent_type = context.warden.get_argument(defn, parent_name(parent, defn)) parent_type ? parent_type.type.unwrap : nil end def validate_input_object(ast_node, context, parent) parent_type = get_parent_type(context, parent) return unless parent_type && parent_type.kind.input_object? required_fields = parent_type.arguments .select{|k,v| v.type.kind.non_null?} .keys present_fields = ast_node.arguments.map(&:name) missing_fields = required_fields - present_fields missing_fields.each do |missing_field| path = [*context.path, missing_field] missing_field_type = parent_type.arguments[missing_field].type add_error(RequiredInputObjectAttributesArePresentError.new( "Argument '#{missing_field}' on InputObject '#{parent_type.to_type_signature}' is required. Expected type #{missing_field_type.to_type_signature}", argument_name: missing_field, argument_type: missing_field_type.to_type_signature, input_object_type: parent_type.to_type_signature, path: path, nodes: ast_node, )) end end end end end required_input_object_attributes_are_present_error.rb000066400000000000000000000017051414121453000362260ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/static_validation/rules# frozen_string_literal: true module GraphQL module StaticValidation class RequiredInputObjectAttributesArePresentError < StaticValidation::Error attr_reader :argument_type attr_reader :argument_name attr_reader :input_object_type def initialize(message, path:, nodes:, argument_type:, argument_name:, input_object_type:) super(message, path: path, nodes: nodes) @argument_type = argument_type @argument_name = argument_name @input_object_type = input_object_type end # A hash representation of this Message def to_h extensions = { "code" => code, "argumentName" => argument_name, "argumentType" => argument_type, "inputObjectType" => input_object_type, } super.merge({ "extensions" => extensions }) end def code "missingRequiredInputObjectAttribute" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/subscription_root_exists.rb000066400000000000000000000007761414121453000310470ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module SubscriptionRootExists def on_operation_definition(node, _parent) if node.operation_type == "subscription" && context.warden.root_type_for_operation("subscription").nil? add_error(GraphQL::StaticValidation::SubscriptionRootExistsError.new( 'Schema is not configured for subscriptions', nodes: node )) else super end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/subscription_root_exists_error.rb000066400000000000000000000010301414121453000322400ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class SubscriptionRootExistsError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code, } super.merge({ "extensions" => extensions }) end def code "missingSubscriptionConfiguration" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/unique_directives_per_location.rb000066400000000000000000000027711414121453000321430ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module UniqueDirectivesPerLocation DIRECTIVE_NODE_HOOKS = [ :on_fragment_definition, :on_fragment_spread, :on_inline_fragment, :on_operation_definition, :on_scalar_type_definition, :on_object_type_definition, :on_input_value_definition, :on_field_definition, :on_interface_type_definition, :on_union_type_definition, :on_enum_type_definition, :on_enum_value_definition, :on_input_object_type_definition, :on_field, ] DIRECTIVE_NODE_HOOKS.each do |method_name| define_method(method_name) do |node, parent| if node.directives.any? validate_directive_location(node) end super(node, parent) end end private def validate_directive_location(node) used_directives = {} node.directives.each do |ast_directive| directive_name = ast_directive.name if used_directives[directive_name] add_error(GraphQL::StaticValidation::UniqueDirectivesPerLocationError.new( "The directive \"#{directive_name}\" can only be used once at this location.", nodes: [used_directives[directive_name], ast_directive], directive: directive_name, )) else used_directives[directive_name] = ast_directive end end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/unique_directives_per_location_error.rb000066400000000000000000000012301414121453000333410ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class UniqueDirectivesPerLocationError < StaticValidation::Error attr_reader :directive_name def initialize(message, path: nil, nodes: [], directive:) super(message, path: path, nodes: nodes) @directive_name = directive end # A hash representation of this Message def to_h extensions = { "code" => code, "directiveName" => directive_name } super.merge({ "extensions" => extensions }) end def code "directiveNotUniqueForLocation" end end end end variable_default_values_are_correctly_typed.rb000066400000000000000000000033571414121453000345720ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/static_validation/rules# frozen_string_literal: true module GraphQL module StaticValidation module VariableDefaultValuesAreCorrectlyTyped def on_variable_definition(node, parent) if !node.default_value.nil? value = node.default_value if node.type.is_a?(GraphQL::Language::Nodes::NonNullType) add_error(GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTypedError.new( "Non-null variable $#{node.name} can't have a default value", nodes: node, name: node.name, error_type: VariableDefaultValuesAreCorrectlyTypedError::VIOLATIONS[:INVALID_ON_NON_NULL] )) else type = context.schema.type_from_ast(node.type, context: context) if type.nil? # This is handled by another validator else validation_result = context.validate_literal(value, type) if !validation_result.valid? problems = validation_result.problems first_problem = problems && problems.first if first_problem error_message = first_problem["message"] end error_message ||= "Default value for $#{node.name} doesn't match type #{type.to_type_signature}" add_error(GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTypedError.new( error_message, nodes: node, name: node.name, type: type.to_type_signature, error_type: VariableDefaultValuesAreCorrectlyTypedError::VIOLATIONS[:INVALID_TYPE], )) end end end end super end end end end variable_default_values_are_correctly_typed_error.rb000066400000000000000000000021141414121453000357710ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/static_validation/rules# frozen_string_literal: true module GraphQL module StaticValidation class VariableDefaultValuesAreCorrectlyTypedError < StaticValidation::Error attr_reader :variable_name attr_reader :type_name attr_reader :violation VIOLATIONS = { :INVALID_TYPE => "defaultValueInvalidType", :INVALID_ON_NON_NULL => "defaultValueInvalidOnNonNullVariable", } def initialize(message, path: nil, nodes: [], name:, type: nil, error_type:) super(message, path: path, nodes: nodes) @variable_name = name @type_name = type raise("Unexpected error type: #{error_type}") if !VIOLATIONS.values.include?(error_type) @violation = error_type end # A hash representation of this Message def to_h extensions = { "code" => code, "variableName" => variable_name }.tap { |h| h["typeName"] = type_name unless type_name.nil? } super.merge({ "extensions" => extensions }) end def code @violation end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/variable_names_are_unique.rb000066400000000000000000000013111414121453000310300ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module VariableNamesAreUnique def on_operation_definition(node, parent) var_defns = node.variables if var_defns.any? vars_by_name = Hash.new { |h, k| h[k] = [] } var_defns.each { |v| vars_by_name[v.name] << v } vars_by_name.each do |name, defns| if defns.size > 1 add_error(GraphQL::StaticValidation::VariableNamesAreUniqueError.new( "There can only be one variable named \"#{name}\"", nodes: defns, name: name )) end end end super end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/variable_names_are_unique_error.rb000066400000000000000000000011711414121453000322450ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class VariableNamesAreUniqueError < StaticValidation::Error attr_reader :variable_name def initialize(message, path: nil, nodes: [], name:) super(message, path: path, nodes: nodes) @variable_name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "variableName" => variable_name } super.merge({ "extensions" => extensions }) end def code "variableNotUnique" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb000066400000000000000000000122631414121453000313450ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module VariableUsagesAreAllowed def initialize(*) super # holds { name => ast_node } pairs @declared_variables = {} end def on_operation_definition(node, parent) @declared_variables = node.variables.each_with_object({}) { |var, memo| memo[var.name] = var } super end def on_argument(node, parent) node_values = if node.value.is_a?(Array) node.value else [node.value] end node_values = node_values.select { |value| value.is_a? GraphQL::Language::Nodes::VariableIdentifier } if node_values.any? arguments = case parent when GraphQL::Language::Nodes::Field context.field_definition.arguments when GraphQL::Language::Nodes::Directive context.directive_definition.arguments when GraphQL::Language::Nodes::InputObject arg_type = context.argument_definition.type.unwrap if arg_type.is_a?(GraphQL::InputObjectType) arguments = arg_type.input_fields else # This is some kind of error nil end else raise("Unexpected argument parent: #{parent}") end node_values.each do |node_value| var_defn_ast = @declared_variables[node_value.name] # Might be undefined :( # VariablesAreUsedAndDefined can't finalize its search until the end of the document. var_defn_ast && arguments && validate_usage(arguments, node, var_defn_ast) end end super end private def validate_usage(arguments, arg_node, ast_var) var_type = context.schema.type_from_ast(ast_var.type, context: context) if var_type.nil? return end if !ast_var.default_value.nil? unless var_type.kind.non_null? # If the value is required, but the argument is not, # and yet there's a non-nil default, then we impliclty # make the argument also a required type. var_type = var_type.to_non_null_type end end arg_defn = arguments[arg_node.name] arg_defn_type = arg_defn.type var_inner_type = var_type.unwrap arg_inner_type = arg_defn_type.unwrap var_type = wrap_var_type_with_depth_of_arg(var_type, arg_node) if var_inner_type != arg_inner_type create_error("Type mismatch", var_type, ast_var, arg_defn, arg_node) elsif list_dimension(var_type) != list_dimension(arg_defn_type) create_error("List dimension mismatch", var_type, ast_var, arg_defn, arg_node) elsif !non_null_levels_match(arg_defn_type, var_type) create_error("Nullability mismatch", var_type, ast_var, arg_defn, arg_node) end end def create_error(error_message, var_type, ast_var, arg_defn, arg_node) add_error(GraphQL::StaticValidation::VariableUsagesAreAllowedError.new( "#{error_message} on variable $#{ast_var.name} and argument #{arg_node.name} (#{var_type.to_type_signature} / #{arg_defn.type.to_type_signature})", nodes: arg_node, name: ast_var.name, type: var_type.to_type_signature, argument: arg_node.name, error: error_message )) end def wrap_var_type_with_depth_of_arg(var_type, arg_node) arg_node_value = arg_node.value return var_type unless arg_node_value.is_a?(Array) new_var_type = var_type depth_of_array(arg_node_value).times do # Since the array _is_ present, treat it like a non-null type # (It satisfies a non-null requirement AND a nullable requirement) new_var_type = new_var_type.to_list_type.to_non_null_type end new_var_type end # @return [Integer] Returns the max depth of `array`, or `0` if it isn't an array at all def depth_of_array(array) case array when Array max_child_depth = 0 array.each do |item| item_depth = depth_of_array(item) if item_depth > max_child_depth max_child_depth = item_depth end end 1 + max_child_depth else 0 end end def list_dimension(type) if type.kind.list? 1 + list_dimension(type.of_type) elsif type.kind.non_null? list_dimension(type.of_type) else 0 end end def non_null_levels_match(arg_type, var_type) if arg_type.kind.non_null? && !var_type.kind.non_null? false elsif arg_type.kind.wraps? && var_type.kind.wraps? # If var_type is a non-null wrapper for a type, and arg_type is nullable, peel off the wrapper # That way, a var_type of `[DairyAnimal]!` works with an arg_type of `[DairyAnimal]` if var_type.kind.non_null? && !arg_type.kind.non_null? var_type = var_type.of_type end non_null_levels_match(arg_type.of_type, var_type.of_type) else true end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/variable_usages_are_allowed_error.rb000066400000000000000000000017071414121453000325570ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class VariableUsagesAreAllowedError < StaticValidation::Error attr_reader :type_name attr_reader :variable_name attr_reader :argument_name attr_reader :error_message def initialize(message, path: nil, nodes: [], type:, name:, argument:, error:) super(message, path: path, nodes: nodes) @type_name = type @variable_name = name @argument_name = argument @error_message = error end # A hash representation of this Message def to_h extensions = { "code" => code, "variableName" => variable_name, "typeName" => type_name, "argumentName" => argument_name, "errorMessage" => error_message } super.merge({ "extensions" => extensions }) end def code "variableMismatch" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/variables_are_input_types.rb000066400000000000000000000020351414121453000311110ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation module VariablesAreInputTypes def on_variable_definition(node, parent) type_name = get_type_name(node.type) type = context.warden.get_type(type_name) if type.nil? add_error(GraphQL::StaticValidation::VariablesAreInputTypesError.new( "#{type_name} isn't a defined input type (on $#{node.name})", nodes: node, name: node.name, type: type_name )) elsif !type.kind.input? add_error(GraphQL::StaticValidation::VariablesAreInputTypesError.new( "#{type.graphql_name} isn't a valid input type (on $#{node.name})", nodes: node, name: node.name, type: type_name )) end super end private def get_type_name(ast_type) if ast_type.respond_to?(:of_type) get_type_name(ast_type.of_type) else ast_type.name end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/variables_are_input_types_error.rb000066400000000000000000000013421414121453000323220ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class VariablesAreInputTypesError < StaticValidation::Error attr_reader :type_name attr_reader :variable_name def initialize(message, path: nil, nodes: [], type:, name:) super(message, path: path, nodes: nodes) @type_name = type @variable_name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name, "variableName" => variable_name } super.merge({ "extensions" => extensions }) end def code "variableRequiresValidType" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb000066400000000000000000000137031414121453000317720ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # The problem is # - Variable $usage must be determined at the OperationDefinition level # - You can't tell how fragments use variables until you visit FragmentDefinitions (which may be at the end of the document) # # So, this validator includes some crazy logic to follow fragment spreads recursively, while avoiding infinite loops. # # `graphql-js` solves this problem by: # - re-visiting the AST for each validator # - allowing validators to say `followSpreads: true` # module VariablesAreUsedAndDefined class VariableUsage attr_accessor :ast_node, :used_by, :declared_by, :path def used? !!@used_by end def declared? !!@declared_by end end def initialize(*) super @variable_usages_for_context = Hash.new {|hash, key| hash[key] = Hash.new {|h, k| h[k] = VariableUsage.new } } @spreads_for_context = Hash.new {|hash, key| hash[key] = [] } @variable_context_stack = [] end def on_operation_definition(node, parent) # initialize the hash of vars for this context: @variable_usages_for_context[node] @variable_context_stack.push(node) # mark variables as defined: var_hash = @variable_usages_for_context[node] node.variables.each { |var| var_usage = var_hash[var.name] var_usage.declared_by = node var_usage.path = context.path } super @variable_context_stack.pop end def on_fragment_definition(node, parent) # initialize the hash of vars for this context: @variable_usages_for_context[node] @variable_context_stack.push(node) super @variable_context_stack.pop end # For FragmentSpreads: # - find the context on the stack # - mark the context as containing this spread def on_fragment_spread(node, parent) variable_context = @variable_context_stack.last @spreads_for_context[variable_context] << node.name super end # For VariableIdentifiers: # - mark the variable as used # - assign its AST node def on_variable_identifier(node, parent) usage_context = @variable_context_stack.last declared_variables = @variable_usages_for_context[usage_context] usage = declared_variables[node.name] usage.used_by = usage_context usage.ast_node = node usage.path = context.path super end def on_document(node, parent) super fragment_definitions = @variable_usages_for_context.select { |key, value| key.is_a?(GraphQL::Language::Nodes::FragmentDefinition) } operation_definitions = @variable_usages_for_context.select { |key, value| key.is_a?(GraphQL::Language::Nodes::OperationDefinition) } operation_definitions.each do |node, node_variables| follow_spreads(node, node_variables, @spreads_for_context, fragment_definitions, []) create_errors(node_variables) end end private # Follow spreads in `node`, looking them up from `spreads_for_context` and finding their match in `fragment_definitions`. # Use those fragments to update {VariableUsage}s in `parent_variables`. # Avoid infinite loops by skipping anything in `visited_fragments`. def follow_spreads(node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments) spreads = spreads_for_context[node] - visited_fragments spreads.each do |spread_name| def_node = nil variables = nil # Implement `.find` by hand to avoid Ruby's internal allocations fragment_definitions.each do |frag_def_node, vars| if frag_def_node.name == spread_name def_node = frag_def_node variables = vars break end end next if !def_node visited_fragments << spread_name variables.each do |name, child_usage| parent_usage = parent_variables[name] if child_usage.used? parent_usage.ast_node = child_usage.ast_node parent_usage.used_by = child_usage.used_by parent_usage.path = child_usage.path end end follow_spreads(def_node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments) end end # Determine all the error messages, # Then push messages into the validation context def create_errors(node_variables) # Declared but not used: node_variables .select { |name, usage| usage.declared? && !usage.used? } .each { |var_name, usage| declared_by_error_name = usage.declared_by.name || "anonymous #{usage.declared_by.operation_type}" add_error(GraphQL::StaticValidation::VariablesAreUsedAndDefinedError.new( "Variable $#{var_name} is declared by #{declared_by_error_name} but not used", nodes: usage.declared_by, path: usage.path, name: var_name, error_type: VariablesAreUsedAndDefinedError::VIOLATIONS[:VARIABLE_NOT_USED] )) } # Used but not declared: node_variables .select { |name, usage| usage.used? && !usage.declared? } .each { |var_name, usage| used_by_error_name = usage.used_by.name || "anonymous #{usage.used_by.operation_type}" add_error(GraphQL::StaticValidation::VariablesAreUsedAndDefinedError.new( "Variable $#{var_name} is used by #{used_by_error_name} but not declared", nodes: usage.ast_node, path: usage.path, name: var_name, error_type: VariablesAreUsedAndDefinedError::VIOLATIONS[:VARIABLE_NOT_DEFINED] )) } end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/rules/variables_are_used_and_defined_error.rb000066400000000000000000000016521414121453000332030ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class VariablesAreUsedAndDefinedError < StaticValidation::Error attr_reader :variable_name attr_reader :violation VIOLATIONS = { :VARIABLE_NOT_USED => "variableNotUsed", :VARIABLE_NOT_DEFINED => "variableNotDefined", } def initialize(message, path: nil, nodes: [], name:, error_type:) super(message, path: path, nodes: nodes) @variable_name = name raise("Unexpected error type: #{error_type}") if !VIOLATIONS.values.include?(error_type) @violation = error_type end # A hash representation of this Message def to_h extensions = { "code" => code, "variableName" => variable_name } super.merge({ "extensions" => extensions }) end def code @violation end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/type_stack.rb000066400000000000000000000154261414121453000246730ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # - Ride along with `GraphQL::Language::Visitor` # - Track type info, expose it to validators class TypeStack # These are jumping-off points for infering types down the tree TYPE_INFERRENCE_ROOTS = [ GraphQL::Language::Nodes::OperationDefinition, GraphQL::Language::Nodes::FragmentDefinition, ] # @return [GraphQL::Schema] the schema whose types are present in this document attr_reader :schema # When it enters an object (starting with query or mutation root), it's pushed on this stack. # When it exits, it's popped off. # @return [Array] attr_reader :object_types # When it enters a field, it's pushed on this stack (useful for nested fields, args). # When it exits, it's popped off. # @return [Array] fields which have been entered attr_reader :field_definitions # Directives are pushed on, then popped off while traversing the tree # @return [Array] directives which have been entered attr_reader :directive_definitions # @return [Array] arguments which have been entered attr_reader :argument_definitions # @return [Array] fields which have been entered (by their AST name) attr_reader :path # @param schema [GraphQL::Schema] the schema whose types to use when climbing this document # @param visitor [GraphQL::Language::Visitor] a visitor to follow & watch the types def initialize(schema, visitor) @schema = schema @object_types = [] @field_definitions = [] @directive_definitions = [] @argument_definitions = [] @path = [] PUSH_STRATEGIES.each do |node_class, strategy| visitor[node_class].enter << EnterWithStrategy.new(self, strategy) visitor[node_class].leave << LeaveWithStrategy.new(self, strategy) end end private module FragmentWithTypeStrategy def push(stack, node) object_type = if node.type stack.schema.get_type(node.type.name) else stack.object_types.last end if !object_type.nil? object_type = object_type.unwrap end stack.object_types.push(object_type) push_path_member(stack, node) end def pop(stack, node) stack.object_types.pop stack.path.pop end end module FragmentDefinitionStrategy extend FragmentWithTypeStrategy module_function def push_path_member(stack, node) stack.path.push("fragment #{node.name}") end end module InlineFragmentStrategy extend FragmentWithTypeStrategy module_function def push_path_member(stack, node) stack.path.push("...#{node.type ? " on #{node.type.to_query_string}" : ""}") end end module OperationDefinitionStrategy module_function def push(stack, node) # eg, QueryType, MutationType object_type = stack.schema.root_type_for_operation(node.operation_type) stack.object_types.push(object_type) stack.path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}") end def pop(stack, node) stack.object_types.pop stack.path.pop end end module FieldStrategy module_function def push(stack, node) parent_type = stack.object_types.last parent_type = parent_type.unwrap field_definition = stack.schema.get_field(parent_type, node.name) stack.field_definitions.push(field_definition) if !field_definition.nil? next_object_type = field_definition.type stack.object_types.push(next_object_type) else stack.object_types.push(nil) end stack.path.push(node.alias || node.name) end def pop(stack, node) stack.field_definitions.pop stack.object_types.pop stack.path.pop end end module DirectiveStrategy module_function def push(stack, node) directive_defn = stack.schema.directives[node.name] stack.directive_definitions.push(directive_defn) end def pop(stack, node) stack.directive_definitions.pop end end module ArgumentStrategy module_function # Push `argument_defn` onto the stack. # It's possible that `argument_defn` will be nil. # Push it anyways so `pop` has something to pop. def push(stack, node) if stack.argument_definitions.last arg_type = stack.argument_definitions.last.type.unwrap if arg_type.kind.input_object? argument_defn = arg_type.arguments[node.name] else argument_defn = nil end elsif stack.directive_definitions.last argument_defn = stack.directive_definitions.last.arguments[node.name] elsif stack.field_definitions.last argument_defn = stack.field_definitions.last.arguments[node.name] else argument_defn = nil end stack.argument_definitions.push(argument_defn) stack.path.push(node.name) end def pop(stack, node) stack.argument_definitions.pop stack.path.pop end end module FragmentSpreadStrategy module_function def push(stack, node) stack.path.push("... #{node.name}") end def pop(stack, node) stack.path.pop end end PUSH_STRATEGIES = { GraphQL::Language::Nodes::FragmentDefinition => FragmentDefinitionStrategy, GraphQL::Language::Nodes::InlineFragment => InlineFragmentStrategy, GraphQL::Language::Nodes::FragmentSpread => FragmentSpreadStrategy, GraphQL::Language::Nodes::Argument => ArgumentStrategy, GraphQL::Language::Nodes::Field => FieldStrategy, GraphQL::Language::Nodes::Directive => DirectiveStrategy, GraphQL::Language::Nodes::OperationDefinition => OperationDefinitionStrategy, } class EnterWithStrategy def initialize(stack, strategy) @stack = stack @strategy = strategy end def call(node, parent) @strategy.push(@stack, node) end end class LeaveWithStrategy def initialize(stack, strategy) @stack = stack @strategy = strategy end def call(node, parent) @strategy.pop(@stack, node) end end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/validation_context.rb000066400000000000000000000031451414121453000264160ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # The validation context gets passed to each validator. # # It exposes a {GraphQL::Language::Visitor} where validators may add hooks. ({Language::Visitor#visit} is called in {Validator#validate}) # # It provides access to the schema & fragments which validators may read from. # # It holds a list of errors which each validator may add to. # # It also provides limited access to the {TypeStack} instance, # which tracks state as you climb in and out of different fields. class ValidationContext extend Forwardable attr_reader :query, :errors, :visitor, :on_dependency_resolve_handlers def_delegators :@query, :schema, :document, :fragments, :operations, :warden def initialize(query, visitor_class, max_errors) @query = query @literal_validator = LiteralValidator.new(context: query.context) @errors = [] @max_errors = max_errors || Float::INFINITY @on_dependency_resolve_handlers = [] @visitor = visitor_class.new(document, self) end def_delegators :@visitor, :path, :type_definition, :field_definition, :argument_definition, :parent_type_definition, :directive_definition, :object_types, :dependencies def on_dependency_resolve(&handler) @on_dependency_resolve_handlers << handler end def validate_literal(ast_value, type) @literal_validator.validate(ast_value, type) end def too_many_errors? @errors.length >= @max_errors end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/validation_timeout_error.rb000066400000000000000000000010021414121453000276170ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation class ValidationTimeoutError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code } super.merge({ "extensions" => extensions }) end def code "validationTimeout" end end end end graphql-ruby-1.11.10/lib/graphql/static_validation/validator.rb000066400000000000000000000072701414121453000245100ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module StaticValidation # Initialized with a {GraphQL::Schema}, then it can validate {GraphQL::Language::Nodes::Documents}s based on that schema. # # By default, it's used by {GraphQL::Query} # # @example Validate a query # validator = GraphQL::StaticValidation::Validator.new(schema: MySchema) # query = GraphQL::Query.new(MySchema, query_string) # errors = validator.validate(query)[:errors] # class Validator # @param schema [GraphQL::Schema] # @param rules [Array<#validate(context)>] a list of rules to use when validating def initialize(schema:, rules: GraphQL::StaticValidation::ALL_RULES) @schema = schema @rules = rules end # Validate `query` against the schema. Returns an array of message hashes. # @param query [GraphQL::Query] # @param validate [Boolean] # @param timeout [Float] Number of seconds to wait before aborting validation. Any positive number may be used, including Floats to specify fractional seconds. # @param max_errors [Integer] Maximum number of errors before aborting validation. Any positive number will limit the number of errors. Defaults to nil for no limit. # @return [Array] def validate(query, validate: true, timeout: nil, max_errors: nil) query.trace("validate", { validate: validate, query: query }) do can_skip_rewrite = query.context.interpreter? && query.schema.using_ast_analysis? && query.schema.is_a?(Class) errors = if validate == false && can_skip_rewrite [] else rules_to_use = validate ? @rules : [] visitor_class = BaseVisitor.including_rules(rules_to_use, rewrite: !can_skip_rewrite) context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class, max_errors) begin # CAUTION: Usage of the timeout module makes the assumption that validation rules are stateless Ruby code that requires no cleanup if process was interrupted. This means no blocking IO calls, native gems, locks, or `rescue` clauses that must be reached. # A timeout value of 0 or nil will execute the block without any timeout. Timeout::timeout(timeout) do catch(:too_many_validation_errors) do # Attach legacy-style rules. # Only loop through rules if it has legacy-style rules unless (legacy_rules = rules_to_use - GraphQL::StaticValidation::ALL_RULES).empty? legacy_rules.each do |rule_class_or_module| if rule_class_or_module.method_defined?(:validate) rule_class_or_module.new.validate(context) end end end context.visitor.visit end end rescue Timeout::Error handle_timeout(query, context) end context.errors end irep = if errors.empty? && context # Only return this if there are no errors and validation was actually run context.visitor.rewrite_document else nil end { errors: errors, irep: irep, } end end # Invoked when static validation times out. # @param query [GraphQL::Query] # @param context [GraphQL::StaticValidation::ValidationContext] def handle_timeout(query, context) context.errors << GraphQL::StaticValidation::ValidationTimeoutError.new( "Timeout on validation of query" ) end end end end graphql-ruby-1.11.10/lib/graphql/string_encoding_error.rb000066400000000000000000000004601414121453000234010ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class StringEncodingError < GraphQL::RuntimeTypeError attr_reader :string def initialize(str) @string = str super("String \"#{str}\" was encoded as #{str.encoding}! GraphQL requires an encoding compatible with UTF-8.") end end end graphql-ruby-1.11.10/lib/graphql/string_type.rb000066400000000000000000000001371414121453000213640ustar00rootroot00000000000000# frozen_string_literal: true GraphQL::STRING_TYPE = GraphQL::Types::String.graphql_definition graphql-ruby-1.11.10/lib/graphql/subscriptions.rb000066400000000000000000000260441414121453000217310ustar00rootroot00000000000000# frozen_string_literal: true require "securerandom" require "graphql/subscriptions/broadcast_analyzer" require "graphql/subscriptions/event" require "graphql/subscriptions/instrumentation" require "graphql/subscriptions/serialize" require "graphql/subscriptions/action_cable_subscriptions" require "graphql/subscriptions/subscription_root" require "graphql/subscriptions/default_subscription_resolve_extension" module GraphQL class Subscriptions # Raised when either: # - the triggered `event_name` doesn't match a field in the schema; or # - one or more arguments don't match the field arguments class InvalidTriggerError < GraphQL::Error end # @see {Subscriptions#initialize} for options, concrete implementations may add options. def self.use(defn, options = {}) schema = defn.is_a?(Class) ? defn : defn.target if schema.subscriptions raise ArgumentError, "Can't reinstall subscriptions. #{schema} is using #{schema.subscriptions}, can't also add #{self}" end instrumentation = Subscriptions::Instrumentation.new(schema: schema) defn.instrument(:query, instrumentation) defn.instrument(:field, instrumentation) options[:schema] = schema schema.subscriptions = self.new(**options) schema.add_subscription_extension_if_necessary nil end # @param schema [Class] the GraphQL schema this manager belongs to def initialize(schema:, broadcast: false, default_broadcastable: false, **rest) if broadcast if !schema.using_ast_analysis? raise ArgumentError, "`broadcast: true` requires AST analysis, add `using GraphQL::Analysis::AST` to your schema or see https://graphql-ruby.org/queries/ast_analysis.html." end schema.query_analyzer(Subscriptions::BroadcastAnalyzer) end @default_broadcastable = default_broadcastable @schema = schema end # @return [Boolean] Used when fields don't have `broadcastable:` explicitly set attr_reader :default_broadcastable # Fetch subscriptions matching this field + arguments pair # And pass them off to the queue. # @param event_name [String] # @param args [Hash Object] # @param object [Object] # @param scope [Symbol, String] # @return [void] def trigger(event_name, args, object, scope: nil) event_name = event_name.to_s # Try with the verbatim input first: field = @schema.get_field(@schema.subscription, event_name) if field.nil? # And if it wasn't found, normalize it: normalized_event_name = normalize_name(event_name) field = @schema.get_field(@schema.subscription, normalized_event_name) if field.nil? raise InvalidTriggerError, "No subscription matching trigger: #{event_name} (looked for #{@schema.subscription.graphql_name}.#{normalized_event_name})" end else # Since we found a field, the original input was already normalized normalized_event_name = event_name end # Normalize symbol-keyed args to strings, try camelizing them normalized_args = normalize_arguments(normalized_event_name, field, args) event = Subscriptions::Event.new( name: normalized_event_name, arguments: normalized_args, field: field, scope: scope, ) execute_all(event, object) end # `event` was triggered on `object`, and `subscription_id` was subscribed, # so it should be updated. # # Load `subscription_id`'s GraphQL data, re-evaluate the query and return the result. # # @param subscription_id [String] # @param event [GraphQL::Subscriptions::Event] The event which was triggered # @param object [Object] The value for the subscription field # @return [GraphQL::Query::Result] def execute_update(subscription_id, event, object) # Lookup the saved data for this subscription query_data = read_subscription(subscription_id) if query_data.nil? delete_subscription(subscription_id) return nil end # Fetch the required keys from the saved data query_string = query_data.fetch(:query_string) variables = query_data.fetch(:variables) context = query_data.fetch(:context) operation_name = query_data.fetch(:operation_name) result = nil # this will be set to `false` unless `.execute` is terminated # with a `throw :graphql_subscription_unsubscribed` unsubscribed = true catch(:graphql_subscription_unsubscribed) do catch(:graphql_no_subscription_update) do # Re-evaluate the saved query, # but if it terminates early with a `throw`, # it will stay `nil` result = @schema.execute( query: query_string, context: context, subscription_topic: event.topic, operation_name: operation_name, variables: variables, root_value: object, ) end unsubscribed = false end if unsubscribed # `unsubscribe` was called, clean up on our side # TODO also send `{more: false}` to client? delete_subscription(subscription_id) end result end # Run the update query for this subscription and deliver it # @see {#execute_update} # @see {#deliver} # @return [void] def execute(subscription_id, event, object) res = execute_update(subscription_id, event, object) if !res.nil? deliver(subscription_id, res) end end # Event `event` occurred on `object`, # Update all subscribers. # @param event [Subscriptions::Event] # @param object [Object] # @return [void] def execute_all(event, object) each_subscription_id(event) do |subscription_id| execute(subscription_id, event, object) end end # Get each `subscription_id` subscribed to `event.topic` and yield them # @param event [GraphQL::Subscriptions::Event] # @yieldparam subscription_id [String] # @return [void] def each_subscription_id(event) raise GraphQL::RequiredImplementationMissingError end # The system wants to send an update to this subscription. # Read its data and return it. # @param subscription_id [String] # @return [Hash] Containing required keys def read_subscription(subscription_id) raise GraphQL::RequiredImplementationMissingError end # A subscription query was re-evaluated, returning `result`. # The result should be send to `subscription_id`. # @param subscription_id [String] # @param result [Hash] # @return [void] def deliver(subscription_id, result) raise GraphQL::RequiredImplementationMissingError end # `query` was executed and found subscriptions to `events`. # Update the database to reflect this new state. # @param query [GraphQL::Query] # @param events [Array] # @return [void] def write_subscription(query, events) raise GraphQL::RequiredImplementationMissingError end # A subscription was terminated server-side. # Clean up the database. # @param subscription_id [String] # @return void. def delete_subscription(subscription_id) raise GraphQL::RequiredImplementationMissingError end # @return [String] A new unique identifier for a subscription def build_id SecureRandom.uuid end # Convert a user-provided event name or argument # to the equivalent in GraphQL. # # By default, it converts the identifier to camelcase. # Override this in a subclass to change the transformation. # # @param event_or_arg_name [String, Symbol] # @return [String] def normalize_name(event_or_arg_name) Schema::Member::BuildType.camelize(event_or_arg_name.to_s) end # @return [Boolean] if true, then a query like this one would be broadcasted def broadcastable?(query_str, **query_options) query = GraphQL::Query.new(@schema, query_str, **query_options) if !query.valid? raise "Invalid query: #{query.validation_errors.map(&:to_h).inspect}" end GraphQL::Analysis::AST.analyze_query(query, @schema.query_analyzers) query.context.namespace(:subscriptions)[:subscription_broadcastable] end private # Recursively normalize `args` as belonging to `arg_owner`: # - convert symbols to strings, # - if needed, camelize the string (using {#normalize_name}) # @param arg_owner [GraphQL::Field, GraphQL::BaseType] # @param args [Hash, Array, Any] some GraphQL input value to coerce as `arg_owner` # @return [Any] normalized arguments value def normalize_arguments(event_name, arg_owner, args) case arg_owner when GraphQL::Field, GraphQL::InputObjectType, GraphQL::Schema::Field, Class if arg_owner.is_a?(Class) && !arg_owner.kind.input_object? # it's a type, but not an input object return args end normalized_args = {} missing_arg_names = [] args.each do |k, v| arg_name = k.to_s arg_defn = arg_owner.arguments[arg_name] if arg_defn normalized_arg_name = arg_name else normalized_arg_name = normalize_name(arg_name) arg_defn = arg_owner.arguments[normalized_arg_name] end if arg_defn if arg_defn.loads normalized_arg_name = arg_defn.keyword.to_s end normalized = normalize_arguments(event_name, arg_defn.type, v) normalized_args[normalized_arg_name] = normalized else # Couldn't find a matching argument definition missing_arg_names << arg_name end end # Backfill default values so that trigger arguments # match query arguments. arg_owner.arguments.each do |name, arg_defn| if arg_defn.default_value? && !normalized_args.key?(arg_defn.name) default_value = arg_defn.default_value # We don't have an underlying "object" here, so it can't call methods. # This is broken. normalized_args[arg_defn.name] = arg_defn.prepare_value(nil, default_value, context: GraphQL::Query::NullContext) end end if missing_arg_names.any? arg_owner_name = if arg_owner.is_a?(GraphQL::Field) "Subscription.#{arg_owner.name}" elsif arg_owner.is_a?(GraphQL::Schema::Field) arg_owner.path elsif arg_owner.is_a?(Class) arg_owner.graphql_name else arg_owner.to_s end raise InvalidTriggerError, "Can't trigger Subscription.#{event_name}, received undefined arguments: #{missing_arg_names.join(", ")}. (Should match arguments of #{arg_owner_name}.)" end normalized_args when GraphQL::ListType, GraphQL::Schema::List args.map { |a| normalize_arguments(event_name, arg_owner.of_type, a) } when GraphQL::NonNullType, GraphQL::Schema::NonNull normalize_arguments(event_name, arg_owner.of_type, args) else args end end end end graphql-ruby-1.11.10/lib/graphql/subscriptions/000077500000000000000000000000001414121453000213765ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/subscriptions/action_cable_subscriptions.rb000066400000000000000000000204651414121453000273240ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Subscriptions # A subscriptions implementation that sends data # as ActionCable broadcastings. # # Some things to keep in mind: # # - No queueing system; ActiveJob should be added # - Take care to reload context when re-delivering the subscription. (see {Query#subscription_update?}) # - Avoid the async ActionCable adapter and use the redis or PostgreSQL adapters instead. Otherwise calling #trigger won't work from background jobs or the Rails console. # # @example Adding ActionCableSubscriptions to your schema # class MySchema < GraphQL::Schema # # ... # use GraphQL::Subscriptions::ActionCableSubscriptions # end # # @example Implementing a channel for GraphQL Subscriptions # class GraphqlChannel < ApplicationCable::Channel # def subscribed # @subscription_ids = [] # end # # def execute(data) # query = data["query"] # variables = ensure_hash(data["variables"]) # operation_name = data["operationName"] # context = { # # Re-implement whatever context methods you need # # in this channel or ApplicationCable::Channel # # current_user: current_user, # # Make sure the channel is in the context # channel: self, # } # # result = MySchema.execute({ # query: query, # context: context, # variables: variables, # operation_name: operation_name # }) # # payload = { # result: result.to_h, # more: result.subscription?, # } # # # Track the subscription here so we can remove it # # on unsubscribe. # if result.context[:subscription_id] # @subscription_ids << result.context[:subscription_id] # end # # transmit(payload) # end # # def unsubscribed # @subscription_ids.each { |sid| # MySchema.subscriptions.delete_subscription(sid) # } # end # # private # # def ensure_hash(ambiguous_param) # case ambiguous_param # when String # if ambiguous_param.present? # ensure_hash(JSON.parse(ambiguous_param)) # else # {} # end # when Hash, ActionController::Parameters # ambiguous_param # when nil # {} # else # raise ArgumentError, "Unexpected parameter: #{ambiguous_param}" # end # end # end # class ActionCableSubscriptions < GraphQL::Subscriptions SUBSCRIPTION_PREFIX = "graphql-subscription:" EVENT_PREFIX = "graphql-event:" # @param serializer [<#dump(obj), #load(string)] Used for serializing messages before handing them to `.broadcast(msg)` # @param namespace [string] Used to namespace events and subscriptions (default: '') def initialize(serializer: Serialize, namespace: '', action_cable: ActionCable, action_cable_coder: ActiveSupport::JSON, **rest) # A per-process map of subscriptions to deliver. # This is provided by Rails, so let's use it @subscriptions = Concurrent::Map.new @events = Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new { |h2, k2| h2[k2] = Concurrent::Array.new } } @action_cable = action_cable @action_cable_coder = action_cable_coder @serializer = serializer @transmit_ns = namespace super end # An event was triggered; Push the data over ActionCable. # Subscribers will re-evaluate locally. def execute_all(event, object) stream = stream_event_name(event) message = @serializer.dump(object) @action_cable.server.broadcast(stream, message) end # This subscription was re-evaluated. # Send it to the specific stream where this client was waiting. def deliver(subscription_id, result) payload = { result: result.to_h, more: true } @action_cable.server.broadcast(stream_subscription_name(subscription_id), payload) end # A query was run where these events were subscribed to. # Store them in memory in _this_ ActionCable frontend. # It will receive notifications when events come in # and re-evaluate the query locally. def write_subscription(query, events) channel = query.context.fetch(:channel) subscription_id = query.context[:subscription_id] ||= build_id stream = stream_subscription_name(subscription_id) channel.stream_from(stream) @subscriptions[subscription_id] = query events.each do |event| # Setup a new listener to run all events with this topic in this process setup_stream(channel, event) # Add this event to the list of events to be updated @events[event.topic][event.fingerprint] << event end end # Every subscribing channel is listening here, but only one of them takes any action. # This is so we can reuse payloads when possible, and make one payload to send to # all subscribers. # # But the problem is, any channel could close at any time, so each channel has to # be ready to take over the primary position. # # To make sure there's always one-and-only-one channel building payloads, # let the listener belonging to the first event on the list be # the one to build and publish payloads. # def setup_stream(channel, initial_event) topic = initial_event.topic channel.stream_from(stream_event_name(initial_event), coder: @action_cable_coder) do |message| object = @serializer.load(message) events_by_fingerprint = @events[topic] events_by_fingerprint.each do |_fingerprint, events| if events.any? && events.first == initial_event # The fingerprint has told us that this response should be shared by all subscribers, # so just run it once, then deliver the result to every subscriber first_event = events.first first_subscription_id = first_event.context.fetch(:subscription_id) result = execute_update(first_subscription_id, first_event, object) # Having calculated the result _once_, send the same payload to all subscribers events.each do |event| subscription_id = event.context.fetch(:subscription_id) deliver(subscription_id, result) end end end nil end end # Return the query from "storage" (in memory) def read_subscription(subscription_id) query = @subscriptions[subscription_id] if query.nil? # This can happen when a subscription is triggered from an unsubscribed channel, # see https://github.com/rmosolgo/graphql-ruby/issues/2478. # (This `nil` is handled by `#execute_update`) nil else { query_string: query.query_string, variables: query.provided_variables, context: query.context.to_h, operation_name: query.operation_name, } end end # The channel was closed, forget about it. def delete_subscription(subscription_id) query = @subscriptions.delete(subscription_id) # This can be `nil` when `.trigger` happens inside an unsubscribed ActionCable channel, # see https://github.com/rmosolgo/graphql-ruby/issues/2478 if query events = query.context.namespace(:subscriptions)[:events] events.each do |event| ev_by_fingerprint = @events[event.topic] ev_for_fingerprint = ev_by_fingerprint[event.fingerprint] ev_for_fingerprint.delete(event) if ev_for_fingerprint.empty? ev_by_fingerprint.delete(event.fingerprint) end end end end private def stream_subscription_name(subscription_id) [SUBSCRIPTION_PREFIX, @transmit_ns, subscription_id].join end def stream_event_name(event) [EVENT_PREFIX, @transmit_ns, event.topic].join end end end end graphql-ruby-1.11.10/lib/graphql/subscriptions/broadcast_analyzer.rb000066400000000000000000000062701414121453000255770ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Subscriptions # Detect whether the current operation: # - Is a subscription operation # - Is completely broadcastable # # Assign the result to `context.namespace(:subscriptions)[:subscription_broadcastable]` # @api private # @see Subscriptions#broadcastable? for a public API class BroadcastAnalyzer < GraphQL::Analysis::AST::Analyzer def initialize(subject) super @default_broadcastable = subject.schema.subscriptions.default_broadcastable # Maybe this will get set to false while analyzing @subscription_broadcastable = true end # Only analyze subscription operations def analyze? @query.subscription? end def on_enter_field(node, parent, visitor) if (@subscription_broadcastable == false) || visitor.skipping? return end current_field = visitor.field_definition apply_broadcastable(current_field) current_type = visitor.parent_type_definition if current_type.kind.interface? pt = @query.possible_types(current_type) pt.each do |object_type| ot_field = @query.get_field(object_type, current_field.graphql_name) if !ot_field binding.pry end # Inherited fields would be exactly the same object; # only check fields that are overrides of the inherited one if ot_field && ot_field != current_field apply_broadcastable(ot_field) end end end end # Assign the result to context. # (This method is allowed to return an error, but we don't need to) # @return [void] def result query.context.namespace(:subscriptions)[:subscription_broadcastable] = @subscription_broadcastable nil end private # Modify `@subscription_broadcastable` based on `field_defn`'s configuration (and/or the default value) def apply_broadcastable(field_defn) current_field_broadcastable = field_defn.introspection? || field_defn.broadcastable? case current_field_broadcastable when nil # If the value wasn't set, mix in the default value: # - If the default is false and the current value is true, make it false # - If the default is true and the current value is true, it stays true # - If the default is false and the current value is false, keep it false # - If the default is true and the current value is false, keep it false @subscription_broadcastable = @subscription_broadcastable && @default_broadcastable when false # One non-broadcastable field is enough to make the whole subscription non-broadcastable @subscription_broadcastable = false when true # Leave `@broadcastable_query` true if it's already true, # but don't _set_ it to true if it was set to false by something else. # Actually, just leave it! else raise ArgumentError, "Unexpected `.broadcastable?` value for #{field_defn.path}: #{current_field_broadcastable}" end end end end end graphql-ruby-1.11.10/lib/graphql/subscriptions/default_subscription_resolve_extension.rb000066400000000000000000000011071414121453000320050ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Subscriptions class DefaultSubscriptionResolveExtension < GraphQL::Subscriptions::SubscriptionRoot::Extension def resolve(context:, object:, arguments:) has_override_implementation = @field.resolver || object.respond_to?(@field.resolver_method) if !has_override_implementation if context.query.subscription_update? object.object else context.skip end else yield(object, arguments) end end end end end graphql-ruby-1.11.10/lib/graphql/subscriptions/event.rb000066400000000000000000000072561414121453000230560ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../subscriptions.rb module GraphQL class Subscriptions # This thing can be: # - Subscribed to by `subscription { ... }` # - Triggered by `MySchema.subscriber.trigger(name, arguments, obj)` # class Event # @return [String] Corresponds to the Subscription root field name attr_reader :name # @return [GraphQL::Query::Arguments] attr_reader :arguments # @return [GraphQL::Query::Context] attr_reader :context # @return [String] An opaque string which identifies this event, derived from `name` and `arguments` attr_reader :topic def initialize(name:, arguments:, field: nil, context: nil, scope: nil) @name = name @arguments = arguments @context = context field ||= context.field scope_val = scope || (context && field.subscription_scope && context[field.subscription_scope]) @topic = self.class.serialize(name, arguments, field, scope: scope_val) end # @return [String] an identifier for this unit of subscription def self.serialize(name, arguments, field, scope:) normalized_args = case arguments when GraphQL::Query::Arguments arguments when Hash if field.is_a?(GraphQL::Schema::Field) stringify_args(field, arguments) else GraphQL::Query::LiteralInput.from_arguments( arguments, field, nil, ) end else raise ArgumentError, "Unexpected arguments: #{arguments}, must be Hash or GraphQL::Arguments" end sorted_h = stringify_args(field, normalized_args.to_h) Serialize.dump_recursive([scope, name, sorted_h]) end # @return [String] a logical identifier for this event. (Stable when the query is broadcastable.) def fingerprint @fingerprint ||= begin # When this query has been flagged as broadcastable, # use a generalized, stable fingerprint so that # duplicate subscriptions can be evaluated and distributed in bulk. # (`@topic` includes field, args, and subscription scope already.) if @context.namespace(:subscriptions)[:subscription_broadcastable] "#{@topic}/#{@context.query.fingerprint}" else # not broadcastable, build a unique ID for this event @context.schema.subscriptions.build_id end end end class << self private def stringify_args(arg_owner, args) case args when Hash next_args = {} args.each do |k, v| arg_name = k.to_s camelized_arg_name = GraphQL::Schema::Member::BuildType.camelize(arg_name) arg_defn = get_arg_definition(arg_owner, camelized_arg_name) if arg_defn normalized_arg_name = camelized_arg_name else normalized_arg_name = arg_name arg_defn = get_arg_definition(arg_owner, normalized_arg_name) end next_args[normalized_arg_name] = stringify_args(arg_defn.type, v) end # Make sure they're deeply sorted next_args.sort.to_h when Array args.map { |a| stringify_args(arg_owner, a) } when GraphQL::Schema::InputObject stringify_args(arg_owner, args.to_h) else args end end def get_arg_definition(arg_owner, arg_name) arg_owner.arguments[arg_name] || arg_owner.arguments.each_value.find { |v| v.keyword.to_s == arg_name } end end end end end graphql-ruby-1.11.10/lib/graphql/subscriptions/instrumentation.rb000066400000000000000000000050351414121453000251710ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../subscriptions.rb module GraphQL class Subscriptions # Wrap the root fields of the subscription type with special logic for: # - Registering the subscription during the first execution # - Evaluating the triggered portion(s) of the subscription during later execution class Instrumentation def initialize(schema:) @schema = schema end def instrument(type, field) if type == @schema.subscription.graphql_definition # This is a root field of `subscription` subscribing_resolve_proc = SubscriptionRegistrationResolve.new(field.resolve_proc) field.redefine(resolve: subscribing_resolve_proc) else field end end # If needed, prepare to gather events which this query subscribes to def before_query(query) if query.subscription? && !query.subscription_update? query.context.namespace(:subscriptions)[:events] = [] end end # After checking the root fields, pass the gathered events to the store def after_query(query) events = query.context.namespace(:subscriptions)[:events] if events && events.any? @schema.subscriptions.write_subscription(query, events) end end private class SubscriptionRegistrationResolve def initialize(inner_proc) @inner_proc = inner_proc end # Wrap the proc with subscription registration logic def call(obj, args, ctx) result = nil if @inner_proc && !@inner_proc.is_a?(GraphQL::Field::Resolve::BuiltInResolve) result = @inner_proc.call(obj, args, ctx) end events = ctx.namespace(:subscriptions)[:events] if events # This is the first execution, so gather an Event # for the backend to register: events << Subscriptions::Event.new( name: ctx.field.name, arguments: args, context: ctx, ) result elsif ctx.irep_node.subscription_topic == ctx.query.subscription_topic if !result.nil? result elsif obj.is_a?(GraphQL::Schema::Object) # The root object is _already_ the subscription update: obj.object else obj end else # This is a subscription update, but this event wasn't triggered. ctx.skip end end end end end end graphql-ruby-1.11.10/lib/graphql/subscriptions/serialize.rb000066400000000000000000000103571414121453000237200ustar00rootroot00000000000000# frozen_string_literal: true # test_via: ../subscriptions.rb require "set" module GraphQL class Subscriptions # Serialization helpers for passing subscription data around. # @api private module Serialize GLOBALID_KEY = "__gid__" SYMBOL_KEY = "__sym__" SYMBOL_KEYS_KEY = "__sym_keys__" TIMESTAMP_KEY = "__timestamp__" TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%N%Z" # eg '2020-01-01 23:59:59.123456789+05:00' OPEN_STRUCT_KEY = "__ostruct__" module_function # @param str [String] A serialized object from {.dump} # @return [Object] An object equivalent to the one passed to {.dump} def load(str) parsed_obj = JSON.parse(str) load_value(parsed_obj) end # @param obj [Object] Some subscription-related data to dump # @return [String] The stringified object def dump(obj) JSON.generate(dump_value(obj), quirks_mode: true) end # This is for turning objects into subscription scopes. # It's a one-way transformation, can't reload this :'( # @param obj [Object] # @return [String] def dump_recursive(obj) case when obj.is_a?(Array) obj.map { |i| dump_recursive(i) }.join(':') when obj.is_a?(Hash) obj.map { |k, v| "#{dump_recursive(k)}:#{dump_recursive(v)}" }.join(":") when obj.is_a?(GraphQL::Schema::InputObject) dump_recursive(obj.to_h) when obj.respond_to?(:to_gid_param) obj.to_gid_param when obj.respond_to?(:to_param) obj.to_param else obj.to_s end end class << self private # @param value [Object] A parsed JSON object # @return [Object] An object that load Global::Identification recursive def load_value(value) if value.is_a?(Array) value.map{|item| load_value(item)} elsif value.is_a?(Hash) if value.size == 1 case value.keys.first # there's only 1 key when GLOBALID_KEY GlobalID::Locator.locate(value[GLOBALID_KEY]) when SYMBOL_KEY value[SYMBOL_KEY].to_sym when TIMESTAMP_KEY timestamp_class_name, timestamp_s = value[TIMESTAMP_KEY] timestamp_class = Object.const_get(timestamp_class_name) timestamp_class.strptime(timestamp_s, TIMESTAMP_FORMAT) when OPEN_STRUCT_KEY ostruct_values = load_value(value[OPEN_STRUCT_KEY]) OpenStruct.new(ostruct_values) end else loaded_h = {} sym_keys = value.fetch(SYMBOL_KEYS_KEY, []) value.each do |k, v| if k == SYMBOL_KEYS_KEY next end if sym_keys.include?(k) k = k.to_sym end loaded_h[k] = load_value(v) end loaded_h end else value end end # @param obj [Object] Some subscription-related data to dump # @return [Object] The object that converted Global::Identification def dump_value(obj) if obj.is_a?(Array) obj.map{|item| dump_value(item)} elsif obj.is_a?(Hash) symbol_keys = nil dumped_h = {} obj.each do |k, v| dumped_h[k.to_s] = dump_value(v) if k.is_a?(Symbol) symbol_keys ||= Set.new symbol_keys << k.to_s end end if symbol_keys dumped_h[SYMBOL_KEYS_KEY] = symbol_keys.to_a end dumped_h elsif obj.is_a?(Symbol) { SYMBOL_KEY => obj.to_s } elsif obj.respond_to?(:to_gid_param) {GLOBALID_KEY => obj.to_gid_param} elsif obj.is_a?(Date) || obj.is_a?(Time) # DateTime extends Date; for TimeWithZone, call `.utc` first. { TIMESTAMP_KEY => [obj.class.name, obj.strftime(TIMESTAMP_FORMAT)] } elsif obj.is_a?(OpenStruct) { OPEN_STRUCT_KEY => dump_value(obj.to_h) } else obj end end end end end end graphql-ruby-1.11.10/lib/graphql/subscriptions/subscription_root.rb000066400000000000000000000051501414121453000255130ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class Subscriptions # @api private # @deprecated This module is no longer needed. module SubscriptionRoot def self.extended(child_cls) warn "`extend GraphQL::Subscriptions::SubscriptionRoot` is no longer required; you can remove it from your Subscription type (#{child_cls})" child_cls.include(InstanceMethods) end # This is for maintaining backwards compatibility: # if a subscription field is created without a `subscription:` resolver class, # then implement the method with the previous default behavior. module InstanceMethods def skip_subscription_root(*) if context.query.subscription_update? object else context.skip end end end def field(*args, extensions: [], **rest, &block) extensions += [Extension] # Backwards-compat for schemas if !rest[:subscription] name = args.first alias_method(name, :skip_subscription_root) end super(*args, extensions: extensions, **rest, &block) end class Extension < GraphQL::Schema::FieldExtension def after_resolve(value:, context:, object:, arguments:, **rest) if value.is_a?(GraphQL::ExecutionError) value elsif (events = context.namespace(:subscriptions)[:events]) # This is the first execution, so gather an Event # for the backend to register: event = Subscriptions::Event.new( name: field.name, arguments: arguments_without_field_extras(arguments: arguments), context: context, field: field, ) events << event value elsif context.query.subscription_topic == Subscriptions::Event.serialize( field.name, arguments_without_field_extras(arguments: arguments), field, scope: (field.subscription_scope ? context[field.subscription_scope] : nil), ) # This is a subscription update. The resolver returned `skip` if it should be skipped, # or else it returned an object to resolve the update. value else # This is a subscription update, but this event wasn't triggered. context.skip end end private def arguments_without_field_extras(arguments:) arguments.dup.tap do |event_args| field.extras.each { |k| event_args.delete(k) } end end end end end end graphql-ruby-1.11.10/lib/graphql/tracing.rb000066400000000000000000000075031414121453000204500ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/tracing/active_support_notifications_tracing" require "graphql/tracing/platform_tracing" require "graphql/tracing/appoptics_tracing" require "graphql/tracing/appsignal_tracing" require "graphql/tracing/data_dog_tracing" require "graphql/tracing/new_relic_tracing" require "graphql/tracing/scout_tracing" require "graphql/tracing/skylight_tracing" require "graphql/tracing/statsd_tracing" require "graphql/tracing/prometheus_tracing" if defined?(PrometheusExporter::Server) require "graphql/tracing/prometheus_tracing/graphql_collector" end module GraphQL # Library entry point for performance metric reporting. # # @example Sending custom events # query.trace("my_custom_event", { ... }) do # # do stuff ... # end # # @example Adding a tracer to a schema # class MySchema < GraphQL::Schema # tracer MyTracer # <= responds to .trace(key, data, &block) # end # # @example Adding a tracer to a single query # MySchema.execute(query_str, context: { backtrace: true }) # # Events: # # Key | Metadata # ----|--------- # lex | `{ query_string: String }` # parse | `{ query_string: String }` # validate | `{ query: GraphQL::Query, validate: Boolean }` # analyze_multiplex | `{ multiplex: GraphQL::Execution::Multiplex }` # analyze_query | `{ query: GraphQL::Query }` # execute_multiplex | `{ multiplex: GraphQL::Execution::Multiplex }` # execute_query | `{ query: GraphQL::Query }` # execute_query_lazy | `{ query: GraphQL::Query?, multiplex: GraphQL::Execution::Multiplex? }` # execute_field | `{ context: GraphQL::Query::Context::FieldResolutionContext?, owner: Class?, field: GraphQL::Schema::Field?, query: GraphQL::Query?, path: Array?}` # execute_field_lazy | `{ context: GraphQL::Query::Context::FieldResolutionContext?, owner: Class?, field: GraphQL::Schema::Field?, query: GraphQL::Query?, path: Array?}` # authorized | `{ context: GraphQL::Query::Context, type: Class, object: Object, path: Array }` # authorized_lazy | `{ context: GraphQL::Query::Context, type: Class, object: Object, path: Array }` # resolve_type | `{ context: GraphQL::Query::Context, type: Class, object: Object, path: Array }` # resolve_type_lazy | `{ context: GraphQL::Query::Context, type: Class, object: Object, path: Array }` # # Note that `execute_field` and `execute_field_lazy` receive different data in different settings: # # - When using {GraphQL::Execution::Interpreter}, they receive `{field:, path:, query:}` # - Otherwise, they receive `{context: ...}` # module Tracing # Objects may include traceable to gain a `.trace(...)` method. # The object must have a `@tracers` ivar of type `Array<<#trace(k, d, &b)>>`. # @api private module Traceable # @param key [String] The name of the event in GraphQL internals # @param metadata [Hash] Event-related metadata (can be anything) # @return [Object] Must return the value of the block def trace(key, metadata, &block) return yield if @tracers.empty? call_tracers(0, key, metadata, &block) end private # If there's a tracer at `idx`, call it and then increment `idx`. # Otherwise, yield. # # @param idx [Integer] Which tracer to call # @param key [String] The current event name # @param metadata [Object] The current event object # @return Whatever the block returns def call_tracers(idx, key, metadata, &block) if idx == @tracers.length yield else @tracers[idx].trace(key, metadata) { call_tracers(idx + 1, key, metadata, &block) } end end end module NullTracer module_function def trace(k, v) yield end end end end graphql-ruby-1.11.10/lib/graphql/tracing/000077500000000000000000000000001414121453000201165ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/tracing/active_support_notifications_tracing.rb000066400000000000000000000022561414121453000301570ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing # This implementation forwards events to ActiveSupport::Notifications # with a `graphql.` prefix. # module ActiveSupportNotificationsTracing # A cache of frequently-used keys to avoid needless string allocations KEYS = { "lex" => "lex.graphql", "parse" => "parse.graphql", "validate" => "validate.graphql", "analyze_multiplex" => "analyze_multiplex.graphql", "analyze_query" => "analyze_query.graphql", "execute_query" => "execute_query.graphql", "execute_query_lazy" => "execute_query_lazy.graphql", "execute_field" => "execute_field.graphql", "execute_field_lazy" => "execute_field_lazy.graphql", "authorized" => "authorized.graphql", "authorized_lazy" => "authorized_lazy.graphql", "resolve_type" => "resolve_type.graphql", "resolve_type_lazy" => "resolve_type.graphql", } def self.trace(key, metadata) prefixed_key = KEYS[key] || "#{key}.graphql" ActiveSupport::Notifications.instrument(prefixed_key, metadata) do yield end end end end end graphql-ruby-1.11.10/lib/graphql/tracing/appoptics_tracing.rb000066400000000000000000000130301414121453000241510ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing # This class uses the AppopticsAPM SDK from the appoptics_apm gem to create # traces for GraphQL. # # There are 4 configurations available. They can be set in the # appoptics_apm config file or in code. Please see: # {https://docs.appoptics.com/kb/apm_tracing/ruby/configure} # # AppOpticsAPM::Config[:graphql][:enabled] = true|false # AppOpticsAPM::Config[:graphql][:transaction_name] = true|false # AppOpticsAPM::Config[:graphql][:sanitize_query] = true|false # AppOpticsAPM::Config[:graphql][:remove_comments] = true|false class AppOpticsTracing < GraphQL::Tracing::PlatformTracing # These GraphQL events will show up as 'graphql.prep' spans PREP_KEYS = ['lex', 'parse', 'validate', 'analyze_query', 'analyze_multiplex'].freeze # These GraphQL events will show up as 'graphql.execute' spans EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze # During auto-instrumentation this version of AppOpticsTracing is compared # with the version provided in the appoptics_apm gem, so that the newer # version of the class can be used def self.version Gem::Version.new('1.0.0') end self.platform_keys = { 'lex' => 'lex', 'parse' => 'parse', 'validate' => 'validate', 'analyze_query' => 'analyze_query', 'analyze_multiplex' => 'analyze_multiplex', 'execute_multiplex' => 'execute_multiplex', 'execute_query' => 'execute_query', 'execute_query_lazy' => 'execute_query_lazy' } def platform_trace(platform_key, _key, data) return yield if !defined?(AppOpticsAPM) || gql_config[:enabled] == false layer = span_name(platform_key) kvs = metadata(data, layer) kvs[:Key] = platform_key if (PREP_KEYS + EXEC_KEYS).include?(platform_key) transaction_name(kvs[:InboundQuery]) if kvs[:InboundQuery] && layer == 'graphql.execute' ::AppOpticsAPM::SDK.trace(layer, kvs) do kvs.clear # we don't have to send them twice yield end end def platform_field_key(type, field) "graphql.#{type.graphql_name}.#{field.graphql_name}" end def platform_authorized_key(type) "graphql.authorized.#{type.graphql_name}" end def platform_resolve_type_key(type) "graphql.resolve_type.#{type.graphql_name}" end private def gql_config ::AppOpticsAPM::Config[:graphql] ||= {} end def transaction_name(query) return if gql_config[:transaction_name] == false || ::AppOpticsAPM::SDK.get_transaction_name split_query = query.strip.split(/\W+/, 3) split_query[0] = 'query' if split_query[0].empty? name = "graphql.#{split_query[0..1].join('.')}" ::AppOpticsAPM::SDK.set_transaction_name(name) end def multiplex_transaction_name(names) return if gql_config[:transaction_name] == false || ::AppOpticsAPM::SDK.get_transaction_name name = "graphql.multiplex.#{names.join('.')}" name = "#{name[0..251]}..." if name.length > 254 ::AppOpticsAPM::SDK.set_transaction_name(name) end def span_name(key) return 'graphql.prep' if PREP_KEYS.include?(key) return 'graphql.execute' if EXEC_KEYS.include?(key) key[/^graphql\./] ? key : "graphql.#{key}" end # rubocop:disable Metrics/AbcSize, Metrics/MethodLength def metadata(data, layer) data.keys.map do |key| case key when :context graphql_context(data[key], layer) when :query graphql_query(data[key]) when :query_string graphql_query_string(data[key]) when :multiplex graphql_multiplex(data[key]) else [key, data[key]] end end.flatten(2).each_slice(2).to_h.merge(Spec: 'graphql') end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength def graphql_context(context, layer) context.errors && context.errors.each do |err| AppOpticsAPM::API.log_exception(layer, err) end [[:Path, context.path.join('.')]] end def graphql_query(query) return [] unless query query_string = query.query_string query_string = remove_comments(query_string) if gql_config[:remove_comments] != false query_string = sanitize(query_string) if gql_config[:sanitize_query] != false [[:InboundQuery, query_string], [:Operation, query.selected_operation_name]] end def graphql_query_string(query_string) query_string = remove_comments(query_string) if gql_config[:remove_comments] != false query_string = sanitize(query_string) if gql_config[:sanitize_query] != false [:InboundQuery, query_string] end def graphql_multiplex(data) names = data.queries.map(&:operations).map(&:keys).flatten.compact multiplex_transaction_name(names) if names.size > 1 [:Operations, names.join(', ')] end def sanitize(query) return unless query # remove arguments query.gsub(/"[^"]*"/, '"?"') # strings .gsub(/-?[0-9]*\.?[0-9]+e?[0-9]*/, '?') # ints + floats .gsub(/\[[^\]]*\]/, '[?]') # arrays end def remove_comments(query) return unless query query.gsub(/#[^\n\r]*/, '') end end end end graphql-ruby-1.11.10/lib/graphql/tracing/appsignal_tracing.rb000066400000000000000000000017131414121453000241320ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class AppsignalTracing < PlatformTracing self.platform_keys = { "lex" => "lex.graphql", "parse" => "parse.graphql", "validate" => "validate.graphql", "analyze_query" => "analyze.graphql", "analyze_multiplex" => "analyze.graphql", "execute_multiplex" => "execute.graphql", "execute_query" => "execute.graphql", "execute_query_lazy" => "execute.graphql", } def platform_trace(platform_key, key, data) Appsignal.instrument(platform_key) do yield end end def platform_field_key(type, field) "#{type.graphql_name}.#{field.graphql_name}.graphql" end def platform_authorized_key(type) "#{type.graphql_name}.authorized.graphql" end def platform_resolve_type_key(type) "#{type.graphql_name}.resolve_type.graphql" end end end end graphql-ruby-1.11.10/lib/graphql/tracing/data_dog_tracing.rb000066400000000000000000000045061414121453000237210ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class DataDogTracing < PlatformTracing self.platform_keys = { 'lex' => 'lex.graphql', 'parse' => 'parse.graphql', 'validate' => 'validate.graphql', 'analyze_query' => 'analyze.graphql', 'analyze_multiplex' => 'analyze.graphql', 'execute_multiplex' => 'execute.graphql', 'execute_query' => 'execute.graphql', 'execute_query_lazy' => 'execute.graphql', } def platform_trace(platform_key, key, data) tracer.trace(platform_key, service: service_name) do |span| span.span_type = 'custom' if key == 'execute_multiplex' operations = data[:multiplex].queries.map(&:selected_operation_name).join(', ') span.resource = operations unless operations.empty? # For top span of query, set the analytics sample rate tag, if available. if analytics_enabled? Datadog::Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) end end if key == 'execute_query' span.set_tag(:selected_operation_name, data[:query].selected_operation_name) span.set_tag(:selected_operation_type, data[:query].selected_operation.operation_type) span.set_tag(:query_string, data[:query].query_string) end yield end end def service_name options.fetch(:service, 'ruby-graphql') end def tracer options.fetch(:tracer, Datadog.tracer) end def analytics_available? defined?(Datadog::Contrib::Analytics) \ && Datadog::Contrib::Analytics.respond_to?(:enabled?) \ && Datadog::Contrib::Analytics.respond_to?(:set_sample_rate) end def analytics_enabled? analytics_available? && Datadog::Contrib::Analytics.enabled?(options.fetch(:analytics_enabled, false)) end def analytics_sample_rate options.fetch(:analytics_sample_rate, 1.0) end def platform_field_key(type, field) "#{type.graphql_name}.#{field.graphql_name}" end def platform_authorized_key(type) "#{type.graphql_name}.authorized" end def platform_resolve_type_key(type) "#{type.graphql_name}.resolve_type" end end end end graphql-ruby-1.11.10/lib/graphql/tracing/new_relic_tracing.rb000066400000000000000000000034171414121453000241260ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class NewRelicTracing < PlatformTracing self.platform_keys = { "lex" => "GraphQL/lex", "parse" => "GraphQL/parse", "validate" => "GraphQL/validate", "analyze_query" => "GraphQL/analyze", "analyze_multiplex" => "GraphQL/analyze", "execute_multiplex" => "GraphQL/execute", "execute_query" => "GraphQL/execute", "execute_query_lazy" => "GraphQL/execute", } # @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name. # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing. # It can also be specified per-query with `context[:set_new_relic_transaction_name]`. def initialize(options = {}) @set_transaction_name = options.fetch(:set_transaction_name, false) super end def platform_trace(platform_key, key, data) if key == "execute_query" set_this_txn_name = data[:query].context[:set_new_relic_transaction_name] if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name) NewRelic::Agent.set_transaction_name(transaction_name(data[:query])) end end NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(platform_key) do yield end end def platform_field_key(type, field) "GraphQL/#{type.graphql_name}/#{field.graphql_name}" end def platform_authorized_key(type) "GraphQL/Authorize/#{type.graphql_name}" end def platform_resolve_type_key(type) "GraphQL/ResolveType/#{type.graphql_name}" end end end end graphql-ruby-1.11.10/lib/graphql/tracing/platform_tracing.rb000066400000000000000000000101271414121453000237770ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing # Each platform provides: # - `.platform_keys` # - `#platform_trace` # - `#platform_field_key(type, field)` # @api private class PlatformTracing class << self attr_accessor :platform_keys end def initialize(options = {}) @options = options @platform_keys = self.class.platform_keys @trace_scalars = options.fetch(:trace_scalars, false) end def trace(key, data) case key when "lex", "parse", "validate", "analyze_query", "analyze_multiplex", "execute_query", "execute_query_lazy", "execute_multiplex" platform_key = @platform_keys.fetch(key) platform_trace(platform_key, key, data) do yield end when "execute_field", "execute_field_lazy" if data[:context] field = data[:context].field platform_key = field.metadata[:platform_key] trace_field = true # implemented with instrumenter else field = data[:field] return_type = field.type.unwrap trace_field = if return_type.kind.scalar? || return_type.kind.enum? (field.trace.nil? && @trace_scalars) || field.trace else true end platform_key = if trace_field context = data.fetch(:query).context cached_platform_key(context, field) { platform_field_key(data[:owner], field) } else nil end end if platform_key && trace_field platform_trace(platform_key, key, data) do yield end else yield end when "authorized", "authorized_lazy" type = data.fetch(:type) context = data.fetch(:context) platform_key = cached_platform_key(context, type) { platform_authorized_key(type) } platform_trace(platform_key, key, data) do yield end when "resolve_type", "resolve_type_lazy" type = data.fetch(:type) context = data.fetch(:context) platform_key = cached_platform_key(context, type) { platform_resolve_type_key(type) } platform_trace(platform_key, key, data) do yield end else # it's a custom key yield end end def instrument(type, field) return_type = field.type.unwrap case return_type when GraphQL::ScalarType, GraphQL::EnumType if field.trace || (field.trace.nil? && @trace_scalars) trace_field(type, field) else field end else trace_field(type, field) end end def trace_field(type, field) new_f = field.redefine new_f.metadata[:platform_key] = platform_field_key(type, field) new_f end def self.use(schema_defn, options = {}) tracer = self.new(**options) schema_defn.instrument(:field, tracer) schema_defn.tracer(tracer) end private # Get the transaction name based on the operation type and name def transaction_name(query) selected_op = query.selected_operation if selected_op op_type = selected_op.operation_type op_name = selected_op.name || "anonymous" else op_type = "query" op_name = "anonymous" end "GraphQL/#{op_type}.#{op_name}" end attr_reader :options # Different kind of schema objects have different kinds of keys: # # - Object types: `.authorized` # - Union/Interface types: `.resolve_type` # - Fields: execution # # So, they can all share one cache. # # If the key isn't present, the given block is called and the result is cached for `key`. # # @return [String] def cached_platform_key(ctx, key) cache = ctx.namespace(self.class)[:platform_key_cache] ||= {} cache.fetch(key) { cache[key] = yield } end end end end graphql-ruby-1.11.10/lib/graphql/tracing/prometheus_tracing.rb000066400000000000000000000037621414121453000243550ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class PrometheusTracing < PlatformTracing DEFAULT_WHITELIST = ['execute_field', 'execute_field_lazy'].freeze DEFAULT_COLLECTOR_TYPE = 'graphql'.freeze self.platform_keys = { 'lex' => "graphql.lex", 'parse' => "graphql.parse", 'validate' => "graphql.validate", 'analyze_query' => "graphql.analyze", 'analyze_multiplex' => "graphql.analyze", 'execute_multiplex' => "graphql.execute", 'execute_query' => "graphql.execute", 'execute_query_lazy' => "graphql.execute", 'execute_field' => "graphql.execute", 'execute_field_lazy' => "graphql.execute" } def initialize(opts = {}) @client = opts[:client] || PrometheusExporter::Client.default @keys_whitelist = opts[:keys_whitelist] || DEFAULT_WHITELIST @collector_type = opts[:collector_type] || DEFAULT_COLLECTOR_TYPE super opts end def platform_trace(platform_key, key, data, &block) return yield unless @keys_whitelist.include?(key) instrument_execution(platform_key, key, data, &block) end def platform_field_key(type, field) "#{type.graphql_name}.#{field.graphql_name}" end def platform_authorized_key(type) "#{type.graphql_name}.authorized" end def platform_resolve_type_key(type) "#{type.graphql_name}.resolve_type" end private def instrument_execution(platform_key, key, data, &block) start = ::Process.clock_gettime ::Process::CLOCK_MONOTONIC result = block.call duration = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start observe platform_key, key, duration result end def observe(platform_key, key, duration) @client.send_json( type: @collector_type, duration: duration, platform_key: platform_key, key: key ) end end end end graphql-ruby-1.11.10/lib/graphql/tracing/prometheus_tracing/000077500000000000000000000000001414121453000240205ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb000066400000000000000000000015201414121453000300470ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class PrometheusTracing < PlatformTracing class GraphQLCollector < ::PrometheusExporter::Server::TypeCollector def initialize @graphql_gauge = PrometheusExporter::Metric::Summary.new( 'graphql_duration_seconds', 'Time spent in GraphQL operations, in seconds' ) end def type 'graphql' end def collect(object) default_labels = { key: object['key'], platform_key: object['platform_key'] } custom = object['custom_labels'] labels = custom.nil? ? default_labels : default_labels.merge(custom) @graphql_gauge.observe object['duration'], labels end def metrics [@graphql_gauge] end end end end end graphql-ruby-1.11.10/lib/graphql/tracing/scout_tracing.rb000066400000000000000000000034711414121453000233140ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class ScoutTracing < PlatformTracing INSTRUMENT_OPTS = { scope: true } self.platform_keys = { "lex" => "lex.graphql", "parse" => "parse.graphql", "validate" => "validate.graphql", "analyze_query" => "analyze.graphql", "analyze_multiplex" => "analyze.graphql", "execute_multiplex" => "execute.graphql", "execute_query" => "execute.graphql", "execute_query_lazy" => "execute.graphql", } # @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name. # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing. # It can also be specified per-query with `context[:set_scout_transaction_name]`. def initialize(options = {}) self.class.include ScoutApm::Tracer @set_transaction_name = options.fetch(:set_transaction_name, false) super(options) end def platform_trace(platform_key, key, data) if key == "execute_query" set_this_txn_name = data[:query].context[:set_scout_transaction_name] if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name) ScoutApm::Transaction.rename(transaction_name(data[:query])) end end self.class.instrument("GraphQL", platform_key, INSTRUMENT_OPTS) do yield end end def platform_field_key(type, field) "#{type.graphql_name}.#{field.graphql_name}" end def platform_authorized_key(type) "#{type.graphql_name}.authorized" end def platform_resolve_type_key(type) "#{type.graphql_name}.resolve_type" end end end end graphql-ruby-1.11.10/lib/graphql/tracing/skylight_tracing.rb000066400000000000000000000051551414121453000240160ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class SkylightTracing < PlatformTracing self.platform_keys = { "lex" => "graphql.language", "parse" => "graphql.language", "validate" => "graphql.prepare", "analyze_query" => "graphql.prepare", "analyze_multiplex" => "graphql.prepare", "execute_multiplex" => "graphql.execute", "execute_query" => "graphql.execute", "execute_query_lazy" => "graphql.execute", } # @param set_endpoint_name [Boolean] If true, the GraphQL operation name will be used as the endpoint name. # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing. # It can also be specified per-query with `context[:set_skylight_endpoint_name]`. def initialize(options = {}) warn("GraphQL::Tracing::SkylightTracing is deprecated, please enable Skylight's GraphQL probe instead: https://www.skylight.io/support/getting-more-from-skylight#graphql.") @set_endpoint_name = options.fetch(:set_endpoint_name, false) super end def platform_trace(platform_key, key, data) if key == "execute_query" query = data[:query] title = query.selected_operation_name || "" category = platform_key set_endpoint_name_override = query.context[:set_skylight_endpoint_name] if set_endpoint_name_override == true || (set_endpoint_name_override.nil? && @set_endpoint_name) # Assign the endpoint so that queries will be grouped instrumenter = Skylight.instrumenter if instrumenter current_trace = instrumenter.current_trace if current_trace op_type = query.selected_operation ? query.selected_operation.operation_type : "query" endpoint = "GraphQL/#{op_type}.#{title}" current_trace.endpoint = endpoint end end end elsif key.start_with?("execute_field") title = platform_key category = key else title = key category = platform_key end Skylight.instrument(category: category, title: title) do yield end end def platform_field_key(type, field) "graphql.#{type.graphql_name}.#{field.graphql_name}" end def platform_authorized_key(type) "graphql.authorized.#{type.graphql_name}" end def platform_resolve_type_key(type) "graphql.resolve_type.#{type.graphql_name}" end end end end graphql-ruby-1.11.10/lib/graphql/tracing/statsd_tracing.rb000066400000000000000000000021721414121453000234560ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Tracing class StatsdTracing < PlatformTracing self.platform_keys = { 'lex' => "graphql.lex", 'parse' => "graphql.parse", 'validate' => "graphql.validate", 'analyze_query' => "graphql.analyze_query", 'analyze_multiplex' => "graphql.analyze_multiplex", 'execute_multiplex' => "graphql.execute_multiplex", 'execute_query' => "graphql.execute_query", 'execute_query_lazy' => "graphql.execute_query_lazy", } # @param statsd [Object] A statsd client def initialize(statsd:, **rest) @statsd = statsd super(**rest) end def platform_trace(platform_key, key, data) @statsd.time(platform_key) do yield end end def platform_field_key(type, field) "graphql.#{type.graphql_name}.#{field.graphql_name}" end def platform_authorized_key(type) "graphql.authorized.#{type.graphql_name}" end def platform_resolve_type_key(type) "graphql.resolve_type.#{type.graphql_name}" end end end end graphql-ruby-1.11.10/lib/graphql/type_kinds.rb000066400000000000000000000056111414121453000211700ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Type kinds are the basic categories which a type may belong to (`Object`, `Scalar`, `Union`...) module TypeKinds # These objects are singletons, eg `GraphQL::TypeKinds::UNION`, `GraphQL::TypeKinds::SCALAR`. class TypeKind attr_reader :name, :description def initialize(name, abstract: false, fields: false, wraps: false, input: false, description: nil) @name = name @abstract = abstract @fields = fields @wraps = wraps @input = input @composite = fields? || abstract? @description = description end # Does this TypeKind have multiple possible implementors? # @deprecated Use `abstract?` instead of `resolves?`. def resolves?; @abstract; end # Is this TypeKind abstract? def abstract?; @abstract; end # Does this TypeKind have queryable fields? def fields?; @fields; end # Does this TypeKind modify another type? def wraps?; @wraps; end # Is this TypeKind a valid query input? def input?; @input; end def to_s; @name; end # Is this TypeKind composed of many values? def composite?; @composite; end def scalar? self == TypeKinds::SCALAR end def object? self == TypeKinds::OBJECT end def interface? self == TypeKinds::INTERFACE end def union? self == TypeKinds::UNION end def enum? self == TypeKinds::ENUM end def input_object? self == TypeKinds::INPUT_OBJECT end def list? self == TypeKinds::LIST end def non_null? self == TypeKinds::NON_NULL end end TYPE_KINDS = [ SCALAR = TypeKind.new("SCALAR", input: true, description: 'Indicates this type is a scalar.'), OBJECT = TypeKind.new("OBJECT", fields: true, description: 'Indicates this type is an object. `fields` and `interfaces` are valid fields.'), INTERFACE = TypeKind.new("INTERFACE", abstract: true, fields: true, description: 'Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.'), UNION = TypeKind.new("UNION", abstract: true, description: 'Indicates this type is a union. `possibleTypes` is a valid field.'), ENUM = TypeKind.new("ENUM", input: true, description: 'Indicates this type is an enum. `enumValues` is a valid field.'), INPUT_OBJECT = TypeKind.new("INPUT_OBJECT", input: true, description: 'Indicates this type is an input object. `inputFields` is a valid field.'), LIST = TypeKind.new("LIST", wraps: true, description: 'Indicates this type is a list. `ofType` is a valid field.'), NON_NULL = TypeKind.new("NON_NULL", wraps: true, description: 'Indicates this type is a non-null. `ofType` is a valid field.'), ] end end graphql-ruby-1.11.10/lib/graphql/types.rb000066400000000000000000000005361414121453000201640ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/types/boolean" require "graphql/types/big_int" require "graphql/types/float" require "graphql/types/id" require "graphql/types/int" require "graphql/types/iso_8601_date" require "graphql/types/iso_8601_date_time" require "graphql/types/json" require "graphql/types/string" require "graphql/types/relay" graphql-ruby-1.11.10/lib/graphql/types/000077500000000000000000000000001414121453000176335ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/types/big_int.rb000066400000000000000000000007401414121453000215740ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types class BigInt < GraphQL::Schema::Scalar description "Represents non-fractional signed whole numeric values. Since the value may exceed the size of a 32-bit integer, it's encoded as a string." def self.coerce_input(value, _ctx) value && Integer(value) rescue ArgumentError nil end def self.coerce_result(value, _ctx) value.to_i.to_s end end end end graphql-ruby-1.11.10/lib/graphql/types/boolean.rb000066400000000000000000000005771414121453000216100ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types class Boolean < GraphQL::Schema::Scalar description "Represents `true` or `false` values." def self.coerce_input(value, _ctx) (value == true || value == false) ? value : nil end def self.coerce_result(value, _ctx) !!value end default_scalar true end end end graphql-ruby-1.11.10/lib/graphql/types/float.rb000066400000000000000000000007301414121453000212650ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types class Float < GraphQL::Schema::Scalar description "Represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point)." def self.coerce_input(value, _ctx) value.is_a?(Numeric) ? value.to_f : nil end def self.coerce_result(value, _ctx) value.to_f end default_scalar true end end end graphql-ruby-1.11.10/lib/graphql/types/id.rb000066400000000000000000000015051414121453000205550ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types class ID < GraphQL::Schema::Scalar graphql_name "ID" description "Represents a unique identifier that is Base64 obfuscated. It is often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"VXNlci0xMA==\"`) or integer (such as `4`) input value will be accepted as an ID." default_scalar true def self.coerce_result(value, _ctx) value.is_a?(::String) ? value : value.to_s end def self.coerce_input(value, _ctx) case value when ::String value when Integer value.to_s else nil end end end end end graphql-ruby-1.11.10/lib/graphql/types/int.rb000066400000000000000000000016341414121453000207560ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types # @see {Types::BigInt} for handling integers outside 32-bit range. class Int < GraphQL::Schema::Scalar description "Represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1." MIN = -(2**31) MAX = (2**31) - 1 def self.coerce_input(value, ctx) return if !value.is_a?(Integer) if value >= MIN && value <= MAX value else err = GraphQL::IntegerDecodingError.new(value) ctx.schema.type_error(err, ctx) end end def self.coerce_result(value, ctx) value = value.to_i if value >= MIN && value <= MAX value else err = GraphQL::IntegerEncodingError.new(value) ctx.schema.type_error(err, ctx) end end default_scalar true end end end graphql-ruby-1.11.10/lib/graphql/types/iso_8601_date.rb000066400000000000000000000016751414121453000224360ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types # This scalar takes `Date`s and transmits them as strings, # using ISO 8601 format. # # Use it for fields or arguments as follows: # # field :published_at, GraphQL::Types::ISO8601Date, null: false # # argument :deliver_at, GraphQL::Types::ISO8601Date, null: false # # Alternatively, use this built-in scalar as inspiration for your # own Date type. class ISO8601Date < GraphQL::Schema::Scalar description "An ISO 8601-encoded date" # @param value [Date,Time,DateTime,String] # @return [String] def self.coerce_result(value, _ctx) Date.parse(value.to_s).iso8601 end # @param str_value [String] # @return [Date] def self.coerce_input(str_value, _ctx) Date.iso8601(str_value) rescue ArgumentError, TypeError # Invalid input nil end end end end graphql-ruby-1.11.10/lib/graphql/types/iso_8601_date_time.rb000066400000000000000000000037071414121453000234520ustar00rootroot00000000000000# frozen_string_literal: true require 'time' module GraphQL module Types # This scalar takes `Time`s and transmits them as strings, # using ISO 8601 format. # # Use it for fields or arguments as follows: # # field :created_at, GraphQL::Types::ISO8601DateTime, null: false # # argument :deliver_at, GraphQL::Types::ISO8601DateTime, null: false # # Alternatively, use this built-in scalar as inspiration for your # own DateTime type. class ISO8601DateTime < GraphQL::Schema::Scalar description "An ISO 8601-encoded datetime" # It's not compatible with Rails' default, # i.e. ActiveSupport::JSON::Encoder.time_precision (3 by default) DEFAULT_TIME_PRECISION = 0 # @return [Integer] def self.time_precision @time_precision || DEFAULT_TIME_PRECISION end # @param [Integer] value def self.time_precision=(value) @time_precision = value end # @param value [Time,Date,DateTime,String] # @return [String] def self.coerce_result(value, _ctx) case value when Date return value.to_time.iso8601(time_precision) when ::String return Time.parse(value).iso8601(time_precision) else # Time, DateTime or compatible is given: return value.iso8601(time_precision) end rescue StandardError => error raise GraphQL::Error, "An incompatible object (#{value.class}) was given to #{self}. Make sure that only Times, Dates, DateTimes, and well-formatted Strings are used with this type. (#{error.message})" end # @param str_value [String] # @return [Time] def self.coerce_input(str_value, _ctx) Time.iso8601(str_value) rescue ArgumentError, TypeError begin Date.iso8601(str_value).to_time rescue ArgumentError, TypeError # Invalid input nil end end end end end graphql-ruby-1.11.10/lib/graphql/types/json.rb000066400000000000000000000013141414121453000211300ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types # An untyped JSON scalar that maps to Ruby hashes, arrays, strings, integers, floats, booleans and nils. # This should be used judiciously because it subverts the GraphQL type system. # # Use it for fields or arguments as follows: # # field :template_parameters, GraphQL::Types::JSON, null: false # # argument :template_parameters, GraphQL::Types::JSON, null: false # class JSON < GraphQL::Schema::Scalar description "Represents untyped JSON" def self.coerce_input(value, _context) value end def self.coerce_result(value, _context) value end end end end graphql-ruby-1.11.10/lib/graphql/types/relay.rb000066400000000000000000000025121414121453000212740ustar00rootroot00000000000000# frozen_string_literal: true require "graphql/types/relay/base_field" require "graphql/types/relay/base_object" require "graphql/types/relay/base_interface" require "graphql/types/relay/page_info" require "graphql/types/relay/base_connection" require "graphql/types/relay/base_edge" require "graphql/types/relay/node" require "graphql/types/relay/node_field" require "graphql/types/relay/nodes_field" module GraphQL module Types # This module contains some types and fields that could support Relay conventions in GraphQL. # # You can use these classes out of the box if you want, but if you want to use your _own_ # GraphQL extensions along with the features in this code, you could also # open up the source files and copy the relevant methods and configuration into # your own classes. # # For example, the provided object types extend {Types::Relay::BaseObject}, # but you might want to: # # 1. Migrate the extensions from {Types::Relay::BaseObject} into _your app's_ base object # 2. Copy {Relay::BaseConnection}, {Relay::BaseEdge}, etc into _your app_, and # change them to extend _your_ base object. # # Similarly, `BaseField`'s extensions could be migrated to your app # and `Node` could be implemented to mix in your base interface module. module Relay end end end graphql-ruby-1.11.10/lib/graphql/types/relay/000077500000000000000000000000001414121453000207475ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/types/relay/base_connection.rb000066400000000000000000000102161414121453000244250ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay # Use this to implement Relay connections, or take it as inspiration # for Relay classes in your own app. # # You may wish to copy this code into your own base class, # so you can extend your own `BaseObject` instead of `GraphQL::Schema::Object`. # # @example Implementation a connection and edge # # Given some object in your app ... # class Types::Post < BaseObject # end # # # Make a couple of base classes: # class Types::BaseEdge < GraphQL::Types::Relay::BaseEdge; end # class Types::BaseConnection < GraphQL::Types::Relay::BaseConnection; end # # # Then extend them for the object in your app # class Types::PostEdge < Types::BaseEdge # node_type(Types::Post) # end # class Types::PostConnection < Types::BaseConnection # edge_type(Types::PostEdge) # end # # @see Relay::BaseEdge for edge types class BaseConnection < Types::Relay::BaseObject extend Forwardable def_delegators :@object, :cursor_from_node, :parent class << self # @return [Class] attr_reader :node_type # @return [Class] attr_reader :edge_class # Configure this connection to return `edges` and `nodes` based on `edge_type_class`. # # This method will use the inputs to create: # - `edges` field # - `nodes` field # - description # # It's called when you subclass this base connection, trying to use the # class name to set defaults. You can call it again in the class definition # to override the default (or provide a value, if the default lookup failed). def edge_type(edge_type_class, edge_class: GraphQL::Relay::Edge, node_type: edge_type_class.node_type, nodes_field: true, node_nullable: true) # Set this connection's graphql name node_type_name = node_type.graphql_name @node_type = node_type @edge_type = edge_type_class @edge_class = edge_class field :edges, [edge_type_class, null: true], null: true, description: "A list of edges.", edge_class: edge_class define_nodes_field(node_nullable) if nodes_field description("The connection type for #{node_type_name}.") end # Filter this list according to the way its node type would scope them def scope_items(items, context) node_type.scope_items(items, context) end # Add the shortcut `nodes` field to this connection and its subclasses def nodes_field define_nodes_field end def authorized?(obj, ctx) true # Let nodes be filtered out end def accessible?(ctx) node_type.accessible?(ctx) end def visible?(ctx) node_type.visible?(ctx) end private def define_nodes_field(nullable = true) type = nullable ? [@node_type, null: true] : [@node_type] field :nodes, type, null: nullable, description: "A list of nodes.", connection: false end end field :page_info, GraphQL::Types::Relay::PageInfo, null: false, description: "Information to aid in pagination." # By default this calls through to the ConnectionWrapper's edge nodes method, # but sometimes you need to override it to support the `nodes` field def nodes @object.edge_nodes end def edges if @object.is_a?(GraphQL::Pagination::Connection) @object.edges elsif context.interpreter? context.schema.after_lazy(object.edge_nodes) do |nodes| nodes.map { |n| self.class.edge_class.new(n, object) } end else # This is done by edges_instrumentation @object.edge_nodes end end end end end end graphql-ruby-1.11.10/lib/graphql/types/relay/base_edge.rb000066400000000000000000000033741414121453000232010ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay # A class-based definition for Relay edges. # # Use this as a parent class in your app, or use it as inspiration for your # own base `Edge` class. # # For example, you may want to extend your own `BaseObject` instead of the # built-in `GraphQL::Schema::Object`. # # @example Making a UserEdge type # # Make a base class for your app # class Types::BaseEdge < GraphQL::Types::Relay::BaseEdge # end # # # Then extend your own base class # class Types::UserEdge < Types::BaseEdge # node_type(Types::User) # end # # @see {Relay::BaseConnection} for connection types class BaseEdge < Types::Relay::BaseObject description "An edge in a connection." class << self # Get or set the Object type that this edge wraps. # # @param node_type [Class] A `Schema::Object` subclass # @param null [Boolean] def node_type(node_type = nil, null: true) if node_type @node_type = node_type # Add a default `node` field field :node, node_type, null: null, description: "The item at the end of the edge.", connection: false end @node_type end def authorized?(obj, ctx) true end def accessible?(ctx) node_type.accessible?(ctx) end def visible?(ctx) node_type.visible?(ctx) end end field :cursor, String, null: false, description: "A cursor for use in pagination." end end end end graphql-ruby-1.11.10/lib/graphql/types/relay/base_field.rb000066400000000000000000000006761414121453000233620ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay class BaseField < GraphQL::Schema::Field def initialize(edge_class: nil, **rest, &block) @edge_class = edge_class super(**rest, &block) end def to_graphql field = super if @edge_class field.edge_class = @edge_class end field end end end end end graphql-ruby-1.11.10/lib/graphql/types/relay/base_interface.rb000066400000000000000000000010771414121453000242330ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay module BaseInterface include GraphQL::Schema::Interface field_class(Types::Relay::BaseField) definition_methods do def default_relay(new_value) @default_relay = new_value end def default_relay? !!@default_relay end def to_graphql type_defn = super type_defn.default_relay = default_relay? type_defn end end end end end end graphql-ruby-1.11.10/lib/graphql/types/relay/base_object.rb000066400000000000000000000010401414121453000235270ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay class BaseObject < GraphQL::Schema::Object field_class(Types::Relay::BaseField) class << self def default_relay(new_value) @default_relay = new_value end def default_relay? !!@default_relay end def to_graphql type_defn = super type_defn.default_relay = default_relay? type_defn end end end end end end graphql-ruby-1.11.10/lib/graphql/types/relay/node.rb000066400000000000000000000007241414121453000222240ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay # This can be used for Relay's `Node` interface, # or you can take it as inspiration for your own implementation # of the `Node` interface. module Node include Types::Relay::BaseInterface default_relay(true) description "An object with an ID." field(:id, ID, null: false, description: "ID of the object.") end end end end graphql-ruby-1.11.10/lib/graphql/types/relay/node_field.rb000066400000000000000000000023431414121453000233660ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay # This can be used for implementing `Query.node(id: ...)`, # or use it for inspiration for your own field definition. # # @example Adding this field directly # add_field(GraphQL::Types::Relay::NodeField) # # @example Implementing a similar field in your own Query root # # field :node, GraphQL::Types::Relay::Node, null: true, # description: "Fetches an object given its ID" do # argument :id, ID, required: true # end # # def node(id:) # context.schema.object_from_id(id, context) # end # NodeField = GraphQL::Schema::Field.new( name: "node", owner: nil, type: GraphQL::Types::Relay::Node, null: true, description: "Fetches an object given its ID.", relay_node_field: true, ) do argument :id, "ID!", required: true, description: "ID of the object." def resolve(obj, args, ctx) ctx.schema.object_from_id(args[:id], ctx) end def resolve_field(obj, args, ctx) resolve(obj, args, ctx) end end end end end graphql-ruby-1.11.10/lib/graphql/types/relay/nodes_field.rb000066400000000000000000000025601414121453000235520ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay # This can be used for implementing `Query.nodes(ids: ...)`, # or use it for inspiration for your own field definition. # # @example Adding this field directly # add_field(GraphQL::Types::Relay::NodesField) # # @example Implementing a similar field in your own Query root # # field :nodes, [GraphQL::Types::Relay::Node, null: true], null: false, # description: Fetches a list of objects given a list of IDs." do # argument :ids, [ID], required: true # end # # def nodes(ids:) # ids.map do |id| # context.schema.object_from_id(context, id) # end # end # NodesField = GraphQL::Schema::Field.new( name: "nodes", owner: nil, type: [GraphQL::Types::Relay::Node, null: true], null: false, description: "Fetches a list of objects given a list of IDs.", relay_nodes_field: true, ) do argument :ids, "[ID!]!", required: true, description: "IDs of the objects." def resolve(obj, args, ctx) args[:ids].map { |id| ctx.schema.object_from_id(id, ctx) } end def resolve_field(obj, args, ctx) resolve(obj, args, ctx) end end end end end graphql-ruby-1.11.10/lib/graphql/types/relay/page_info.rb000066400000000000000000000014541414121453000232270ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types module Relay # The return type of a connection's `pageInfo` field class PageInfo < Types::Relay::BaseObject default_relay true description "Information about pagination in a connection." field :has_next_page, Boolean, null: false, description: "When paginating forwards, are there more items?" field :has_previous_page, Boolean, null: false, description: "When paginating backwards, are there more items?" field :start_cursor, String, null: true, description: "When paginating backwards, the cursor to continue." field :end_cursor, String, null: true, description: "When paginating forwards, the cursor to continue." end end end end graphql-ruby-1.11.10/lib/graphql/types/string.rb000066400000000000000000000014201414121453000214630ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Types class String < GraphQL::Schema::Scalar description "Represents textual data as UTF-8 character sequences. This type is most often used by GraphQL to represent free-form human-readable text." def self.coerce_result(value, ctx) str = value.to_s if str.encoding == Encoding::UTF_8 str elsif str.frozen? str.encode(Encoding::UTF_8) else str.encode!(Encoding::UTF_8) end rescue EncodingError err = GraphQL::StringEncodingError.new(str) ctx.schema.type_error(err, ctx) end def self.coerce_input(value, _ctx) value.is_a?(::String) ? value : nil end default_scalar true end end end graphql-ruby-1.11.10/lib/graphql/unauthorized_error.rb000066400000000000000000000021141414121453000227440ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # When an `authorized?` hook returns false, this error is used to communicate the failure. # It's passed to {Schema.unauthorized_object}. # # Alternatively, custom code in `authorized?` may raise this error. It will be routed the same way. class UnauthorizedError < GraphQL::Error # @return [Object] the application object that failed the authorization check attr_reader :object # @return [Class] the GraphQL object type whose `.authorized?` method was called (and returned false) attr_reader :type # @return [GraphQL::Query::Context] the context for the current query attr_reader :context def initialize(message = nil, object: nil, type: nil, context: nil) if message.nil? && object.nil? && type.nil? raise ArgumentError, "#{self.class.name} requires either a message or keywords" end @object = object @type = type @context = context message ||= "An instance of #{object.class} failed #{type.graphql_name}'s authorization check" super(message) end end end graphql-ruby-1.11.10/lib/graphql/unauthorized_field_error.rb000066400000000000000000000014421414121453000241120ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL class UnauthorizedFieldError < GraphQL::UnauthorizedError # @return [Field] the field that failed the authorization check attr_accessor :field def initialize(message = nil, object: nil, type: nil, context: nil, field: nil) if message.nil? && [field, type].any?(&:nil?) raise ArgumentError, "#{self.class.name} requires either a message or keywords" end @field = field message ||= begin if object "An instance of #{object.class} failed #{type.name}'s authorization check on field #{field.name}" else "Failed #{type.name}'s authorization check on field #{field.name}" end end super(message, object: object, type: type, context: context) end end end graphql-ruby-1.11.10/lib/graphql/union_type.rb000066400000000000000000000074151414121453000212140ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # @api deprecated class UnionType < GraphQL::BaseType # Rubocop was unhappy about the syntax when this was a proc literal class AcceptPossibleTypesDefinition def self.call(target, possible_types, options = {}) target.add_possible_types(possible_types, **options) end end accepts_definitions :resolve_type, :type_membership_class, possible_types: AcceptPossibleTypesDefinition ensure_defined :possible_types, :resolve_type, :resolve_type_proc, :type_membership_class attr_accessor :resolve_type_proc attr_reader :type_memberships attr_accessor :type_membership_class def initialize super @type_membership_class = GraphQL::Schema::TypeMembership @type_memberships = [] @cached_possible_types = nil @resolve_type_proc = nil end def initialize_copy(other) super @type_membership_class = other.type_membership_class @type_memberships = other.type_memberships.dup @cached_possible_types = nil end def kind GraphQL::TypeKinds::UNION end # @return [Boolean] True if `child_type_defn` is a member of this {UnionType} def include?(child_type_defn, ctx = GraphQL::Query::NullContext) possible_types(ctx).include?(child_type_defn) end # @return [Array] Types which may be found in this union def possible_types(ctx = GraphQL::Query::NullContext) if ctx == GraphQL::Query::NullContext # Only cache the default case; if we cached for every `ctx`, it would be a memory leak # (The warden should cache calls to this method, so it's called only once per query, # unless user code calls it directly.) @cached_possible_types ||= possible_types_for_context(ctx) else possible_types_for_context(ctx) end end def possible_types=(types) # This is a re-assignment, so clear the previous values @type_memberships = [] @cached_possible_types = nil add_possible_types(types, **{}) end def add_possible_types(types, **options) @type_memberships ||= [] Array(types).each { |t| @type_memberships << self.type_membership_class.new(self, t, **options) } nil end # Get a possible type of this {UnionType} by type name # @param type_name [String] # @param ctx [GraphQL::Query::Context] The context for the current query # @return [GraphQL::ObjectType, nil] The type named `type_name` if it exists and is a member of this {UnionType}, (else `nil`) def get_possible_type(type_name, ctx) type = ctx.query.get_type(type_name) type if type && ctx.query.warden.possible_types(self).include?(type) end # Check if a type is a possible type of this {UnionType} # @param type [String, GraphQL::BaseType] Name of the type or a type definition # @param ctx [GraphQL::Query::Context] The context for the current query # @return [Boolean] True if the `type` exists and is a member of this {UnionType}, (else `nil`) def possible_type?(type, ctx) type_name = type.is_a?(String) ? type : type.graphql_name !get_possible_type(type_name, ctx).nil? end def resolve_type(value, ctx) ctx.query.resolve_type(self, value) end def resolve_type=(new_resolve_type_proc) @resolve_type_proc = new_resolve_type_proc end def type_memberships=(type_memberships) @type_memberships = type_memberships end private def possible_types_for_context(ctx) visible_types = [] @type_memberships.each do |type_membership| if type_membership.visible?(ctx) visible_types << BaseType.resolve_related_type(type_membership.object_type) end end visible_types end end end graphql-ruby-1.11.10/lib/graphql/unresolved_type_error.rb000066400000000000000000000031501414121453000234530ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL # Error raised when the value provided for a field # can't be resolved to one of the possible types for the field. class UnresolvedTypeError < GraphQL::RuntimeTypeError # @return [Object] The runtime value which couldn't be successfully resolved with `resolve_type` attr_reader :value # @return [GraphQL::Field] The field whose value couldn't be resolved (`field.type` is type which couldn't be resolved) attr_reader :field # @return [GraphQL::BaseType] The owner of `field` attr_reader :parent_type # @return [Object] The return of {Schema#resolve_type} for `value` attr_reader :resolved_type # @return [Array] The allowed options for resolving `value` to `field.type` attr_reader :possible_types def initialize(value, field, parent_type, resolved_type, possible_types) @value = value @field = field @parent_type = parent_type @resolved_type = resolved_type @possible_types = possible_types message = "The value from \"#{field.graphql_name}\" on \"#{parent_type.graphql_name}\" could not be resolved to \"#{field.type.to_type_signature}\". " \ "(Received: `#{resolved_type.inspect}`, Expected: [#{possible_types.map(&:graphql_name).join(", ")}]) " \ "Make sure you have defined a `resolve_type` proc on your schema and that value `#{value.inspect}` " \ "gets resolved to a valid type. You may need to add your type to `orphan_types` if it implements an " \ "interface but isn't a return type of any other field." super(message) end end end graphql-ruby-1.11.10/lib/graphql/upgrader/000077500000000000000000000000001414121453000203005ustar00rootroot00000000000000graphql-ruby-1.11.10/lib/graphql/upgrader/member.rb000066400000000000000000001113061414121453000220760ustar00rootroot00000000000000# frozen_string_literal: true begin require 'parser/current' rescue LoadError raise LoadError, "GraphQL::Upgrader requires the 'parser' gem, please install it and/or add it to your Gemfile" end module GraphQL module Upgrader GRAPHQL_TYPES = '(Object|InputObject|Interface|Enum|Scalar|Union)' class Transform # @param input_text [String] Untransformed GraphQL-Ruby code # @return [String] The input text, with a transformation applied if necessary def apply(input_text) raise GraphQL::RequiredImplementationMissingError, "Return transformed text here" end # Recursively transform a `.define`-DSL-based type expression into a class-ready expression, for example: # # - `types[X]` -> `[X, null: true]` # - `types[X.to_non_null_type]` -> `[X]` # - `Int` -> `Integer` # - `X!` -> `X` # # Notice that `!` is removed sometimes, because it doesn't exist in the class API. # # @param type_expr [String] A `.define`-ready expression of a return type or input type # @return [String] A class-ready expression of the same type` def normalize_type_expression(type_expr, preserve_bang: false) case type_expr when /\A!/ # Handle the bang, normalize the inside "#{preserve_bang ? "!" : ""}#{normalize_type_expression(type_expr[1..-1], preserve_bang: preserve_bang)}" when /\Atypes\[.*\]\Z/ # Unwrap the brackets, normalize, then re-wrap inner_type = type_expr[6..-2] if inner_type.start_with?("!") nullable = false inner_type = inner_type[1..-1] elsif inner_type.end_with?(".to_non_null_type") nullable = false inner_type = inner_type[0...-17] else nullable = true end "[#{normalize_type_expression(inner_type, preserve_bang: preserve_bang)}#{nullable ? ", null: true" : ""}]" when /\Atypes\./ # Remove the prefix normalize_type_expression(type_expr[6..-1], preserve_bang: preserve_bang) when /\A->/ # Remove the proc wrapper, don't re-apply it # because stabby is not supported in class-based definition # (and shouldn't ever be necessary) unwrapped = type_expr .sub(/\A->\s?\{\s*/, "") .sub(/\s*\}/, "") normalize_type_expression(unwrapped, preserve_bang: preserve_bang) when "Int" "Integer" else type_expr end end def underscorize(str) str .gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2') # URLDecoder -> URL_Decoder .gsub(/([a-z\d])([A-Z])/,'\1_\2') # someThing -> some_Thing .downcase end def apply_processor(input_text, processor) ruby_ast = Parser::CurrentRuby.parse(input_text) processor.process(ruby_ast) processor rescue Parser::SyntaxError puts "Error text:" puts input_text raise end def reindent_lines(input_text, from_indent:, to_indent:) prev_indent = " " * from_indent next_indent = " " * to_indent # For each line, remove the previous indent, then add the new indent lines = input_text.split("\n").map do |line| line = line.sub(prev_indent, "") "#{next_indent}#{line}".rstrip end lines.join("\n") end # Remove trailing whitespace def trim_lines(input_text) input_text.gsub(/ +$/, "") end end # Turns `{X} = GraphQL::{Y}Type.define do` into `class {X} < Types::Base{Y}`. class TypeDefineToClassTransform < Transform # @param base_class_pattern [String] Replacement pattern for the base class name. Use this if your base classes have nonstandard names. def initialize(base_class_pattern: "Types::Base\\3") @find_pattern = /( *)([a-zA-Z_0-9:]*) = GraphQL::#{GRAPHQL_TYPES}Type\.define do/ @replace_pattern = "\\1class \\2 < #{base_class_pattern}" @interface_replace_pattern = "\\1module \\2\n\\1 include #{base_class_pattern}" end def apply(input_text) if input_text.include?("GraphQL::InterfaceType.define") input_text.sub(@find_pattern, @interface_replace_pattern) else input_text.sub(@find_pattern, @replace_pattern) end end end # Turns `{X} = GraphQL::Relay::Mutation.define do` into `class {X} < Mutations::BaseMutation` class MutationDefineToClassTransform < Transform # @param base_class_name [String] Replacement pattern for the base class name. Use this if your Mutation base class has a nonstandard name. def initialize(base_class_name: "Mutations::BaseMutation") @find_pattern = /([a-zA-Z_0-9:]*) = GraphQL::Relay::Mutation.define do/ @replace_pattern = "class \\1 < #{base_class_name}" end def apply(input_text) input_text.gsub(@find_pattern, @replace_pattern) end end # Remove `name "Something"` if it is redundant with the class name. # Or, if it is not redundant, move it to `graphql_name "Something"`. class NameTransform < Transform def apply(transformable) last_type_defn = transformable .split("\n") .select { |line| line.include?("class ") || line.include?("module ")} .last if last_type_defn && (matches = last_type_defn.match(/(class|module) (?[a-zA-Z_0-9:]*)( <|$)/)) type_name = matches[:type_name] # Get the name without any prefixes or suffixes type_name_without_the_type_part = type_name.split('::').last.gsub(/Type$/, '') # Find an overridden name value if matches = transformable.match(/ name ('|")(?.*)('|")/) name = matches[:overridden_name] if type_name_without_the_type_part != name # If the overridden name is still required, use `graphql_name` for it transformable = transformable.gsub(/ name (.*)/, ' graphql_name \1') else # Otherwise, remove it altogether transformable = transformable.gsub(/\s+name ('|").*('|")/, '') end end end transformable end end # Remove newlines -- normalize the text for processing class RemoveNewlinesTransform def apply(input_text) keep_looking = true while keep_looking do keep_looking = false # Find the `field` call (or other method), and an open paren, but not a close paren, or a comma between arguments input_text = input_text.gsub(/(?(?:field|input_field|return_field|connection|argument)(?:\([^)]*|.*,))\n\s*(?.+)/) do keep_looking = true field = $~[:field].chomp next_line = $~[:next_line] "#{field} #{next_line}" end end input_text end end # Remove parens from method call - normalize for processing class RemoveMethodParensTransform < Transform def apply(input_text) input_text.sub( /(field|input_field|return_field|connection|argument)\( *(.*?) *\)( *)/, '\1 \2\3' ) end end # Move `type X` to be the second positional argument to `field ...` class PositionalTypeArgTransform < Transform def apply(input_text) input_text.gsub( /(?(?:field|input_field|return_field|connection|argument) :(?:[a-zA-Z_0-9]*)) do(?.*?)[ ]*type (?.*?)\n/m ) do field = $~[:field] block_contents = $~[:block_contents] return_type = normalize_type_expression($~[:return_type], preserve_bang: true) "#{field}, #{return_type} do#{block_contents}" end end end # Find a configuration in the block and move it to a kwarg, # for example # ``` # do # property :thing # end # ``` # becomes: # ``` # property: thing # ``` class ConfigurationToKwargTransform < Transform def initialize(kwarg:) @kwarg = kwarg end def apply(input_text) input_text.gsub( /(?(?:field|return_field|input_field|connection|argument).*) do(?.*?)[ ]*#{@kwarg} (?.*?)\n/m ) do field = $~[:field] block_contents = $~[:block_contents] kwarg_value = $~[:kwarg_value].strip "#{field}, #{@kwarg}: #{kwarg_value} do#{block_contents}" end end end # Transform `property:` kwarg to `method:` kwarg class PropertyToMethodTransform < Transform def apply(input_text) input_text.gsub /property:/, 'method:' end end # Find a keyword whose value is a string or symbol, # and if the value is equivalent to the field name, # remove the keyword altogether. class RemoveRedundantKwargTransform < Transform def initialize(kwarg:) @kwarg = kwarg @finder_pattern = /(field|return_field|input_field|connection|argument) :(?[a-zA-Z_0-9]*).*#{@kwarg}: ['":](?[a-zA-Z_0-9?!]+)['"]?/ end def apply(input_text) if input_text =~ @finder_pattern field_name = $~[:name] kwarg_value = $~[:kwarg_value] if field_name == kwarg_value # It's redundant, remove it input_text = input_text.sub(/, #{@kwarg}: ['":]#{kwarg_value}['"]?/, "") end end input_text end end # Take camelized field names and convert them to underscore case. # (They'll be automatically camelized later.) class UnderscoreizeFieldNameTransform < Transform def apply(input_text) input_text.gsub /(?input_field|return_field|field|connection|argument) :(?[a-zA-Z_0-9_]*)/ do field_type = $~[:field_type] camelized_name = $~[:name] underscored_name = underscorize(camelized_name) "#{field_type} :#{underscored_name}" end end end class ProcToClassMethodTransform < Transform # @param proc_name [String] The name of the proc to be moved to `def self.#{proc_name}` def initialize(proc_name) @proc_name = proc_name # This will tell us whether to operate on the input or not @proc_check_pattern = /#{proc_name}\s?->/ end def apply(input_text) if input_text =~ @proc_check_pattern processor = apply_processor(input_text, NamedProcProcessor.new(@proc_name)) processor.proc_to_method_sections.reverse.each do |proc_to_method_section| proc_body = input_text[proc_to_method_section.proc_body_start..proc_to_method_section.proc_body_end] method_defn_indent = " " * proc_to_method_section.proc_defn_indent method_defn = "def self.#{@proc_name}(#{proc_to_method_section.proc_arg_names.join(", ")})\n#{method_defn_indent} #{proc_body}\n#{method_defn_indent}end\n" method_defn = trim_lines(method_defn) # replace the proc with the new method input_text[proc_to_method_section.proc_defn_start..proc_to_method_section.proc_defn_end] = method_defn end end input_text end class NamedProcProcessor < Parser::AST::Processor attr_reader :proc_to_method_sections def initialize(proc_name) @proc_name_sym = proc_name.to_sym @proc_to_method_sections = [] end class ProcToMethodSection attr_accessor :proc_arg_names, :proc_defn_start, :proc_defn_end, :proc_defn_indent, :proc_body_start, :proc_body_end, :inside_proc def initialize # @proc_name_sym = proc_name.to_sym @proc_arg_names = nil # Beginning of the `#{proc_name} -> {...}` call @proc_defn_start = nil # End of the last `end/}` @proc_defn_end = nil # Amount of whitespace to insert to the rewritten body @proc_defn_indent = nil # First statement of the proc @proc_body_start = nil # End of last statement in the proc @proc_body_end = nil # Used for identifying the proper block @inside_proc = false end end def on_send(node) receiver, method_name, _args = *node if method_name == @proc_name_sym && receiver.nil? proc_section = ProcToMethodSection.new source_exp = node.loc.expression proc_section.proc_defn_start = source_exp.begin.begin_pos proc_section.proc_defn_end = source_exp.end.end_pos proc_section.proc_defn_indent = source_exp.column proc_section.inside_proc = true @proc_to_method_sections << proc_section end res = super(node) @inside_proc = false res end def on_block(node) send_node, args_node, body_node = node.children _receiver, method_name, _send_args_node = *send_node if method_name == :lambda && !@proc_to_method_sections.empty? && @proc_to_method_sections[-1].inside_proc proc_to_method_section = @proc_to_method_sections[-1] source_exp = body_node.loc.expression proc_to_method_section.proc_arg_names = args_node.children.map { |arg_node| arg_node.children[0].to_s } proc_to_method_section.proc_body_start = source_exp.begin.begin_pos proc_to_method_section.proc_body_end = source_exp.end.end_pos proc_to_method_section.inside_proc = false end super(node) end end end class MutationResolveProcToMethodTransform < Transform # @param proc_name [String] The name of the proc to be moved to `def self.#{proc_name}` def initialize(proc_name: "resolve") @proc_name = proc_name end # TODO dedup with ResolveProcToMethodTransform def apply(input_text) if input_text =~ /GraphQL::Relay::Mutation\.define/ named_proc_processor = apply_processor(input_text, ProcToClassMethodTransform::NamedProcProcessor.new(@proc_name)) resolve_proc_processor = apply_processor(input_text, ResolveProcToMethodTransform::ResolveProcProcessor.new) named_proc_processor.proc_to_method_sections.zip(resolve_proc_processor.resolve_proc_sections).reverse.each do |pair| proc_to_method_section, resolve_proc_section = *pair proc_body = input_text[proc_to_method_section.proc_body_start..proc_to_method_section.proc_body_end] method_defn_indent = " " * proc_to_method_section.proc_defn_indent obj_arg_name, args_arg_name, ctx_arg_name = resolve_proc_section.proc_arg_names # This is not good, it will hit false positives # Should use AST to make this substitution if obj_arg_name != "_" proc_body.gsub!(/([^\w:.]|^)#{obj_arg_name}([^\w:]|$)/, '\1object\2') end if ctx_arg_name != "_" proc_body.gsub!(/([^\w:.]|^)#{ctx_arg_name}([^\w:]|$)/, '\1context\2') end method_defn = "def #{@proc_name}(**#{args_arg_name})\n#{method_defn_indent} #{proc_body}\n#{method_defn_indent}end\n" method_defn = trim_lines(method_defn) # Update usage of args keys method_defn = method_defn.gsub(/#{args_arg_name}(?\.key\?\(?|\[)["':](?[a-zA-Z0-9_]+)["']?(?\]|\))?/) do method_begin = $~[:method_begin] arg_name = underscorize($~[:arg_name]) method_end = $~[:method_end] "#{args_arg_name}#{method_begin}:#{arg_name}#{method_end}" end # replace the proc with the new method input_text[proc_to_method_section.proc_defn_start..proc_to_method_section.proc_defn_end] = method_defn end end input_text end end # Find hash literals which are returned from mutation resolves, # and convert their keys to underscores. This catches a lot of cases but misses # hashes which are initialized anywhere except in the return expression. class UnderscorizeMutationHashTransform < Transform def apply(input_text) if input_text =~ /def resolve\(\*\*/ processor = apply_processor(input_text, ReturnedHashLiteralProcessor.new) # Use reverse_each to avoid messing up positions processor.keys_to_upgrade.reverse_each do |key_data| underscored_key = underscorize(key_data[:key].to_s) if key_data[:operator] == ":" input_text[key_data[:start]...key_data[:end]] = underscored_key else input_text[key_data[:start]...key_data[:end]] = ":#{underscored_key}" end end end input_text end class ReturnedHashLiteralProcessor < Parser::AST::Processor attr_reader :keys_to_upgrade def initialize @keys_to_upgrade = [] end def on_def(node) method_name, _args, body = *node if method_name == :resolve possible_returned_hashes = find_returned_hashes(body, returning: false) possible_returned_hashes.each do |hash_node| pairs = *hash_node pairs.each do |pair_node| if pair_node.type == :pair # Skip over :kwsplat pair_k, _pair_v = *pair_node if pair_k.type == :sym && pair_k.children[0].to_s =~ /[a-z][A-Z]/ # Does it have any camelcase boundaries? source_exp = pair_k.loc.expression @keys_to_upgrade << { start: source_exp.begin.begin_pos, end: source_exp.end.end_pos, key: pair_k.children[0], operator: pair_node.loc.operator.source, } end end end end end end private # Look for hash nodes, starting from `node`. # Return hash nodes that are valid candiates for returning from this method. def find_returned_hashes(node, returning:) if node.is_a?(Array) *possible_returns, last_expression = *node return possible_returns.map { |c| find_returned_hashes(c, returning: false) }.flatten + # Check the last expression of a method body find_returned_hashes(last_expression, returning: returning) end case node.type when :hash if returning [node] else # This is some random hash literal [] end when :begin # Check the last expression of a method body find_returned_hashes(node.children, returning: true) when :resbody _condition, _assign, body = *node find_returned_hashes(body, returning: returning) when :kwbegin find_returned_hashes(node.children, returning: returning) when :rescue try_body, rescue_body, _ensure_body = *node find_returned_hashes(try_body, returning: returning) + find_returned_hashes(rescue_body, returning: returning) when :block # Check methods with blocks for possible returns method_call, _args, *body = *node if method_call.type == :send find_returned_hashes(body, returning: returning) end when :if # Check each branch of a conditional _condition, *branches = *node branches.compact.map { |b| find_returned_hashes(b, returning: returning) }.flatten when :return find_returned_hashes(node.children.first, returning: true) else [] end rescue p "--- UnderscorizeMutationHashTransform crashed on node: ---" p node raise end end end class ResolveProcToMethodTransform < Transform def apply(input_text) if input_text =~ /resolve\(? ?->/ # - Find the proc literal # - Get the three argument names (obj, arg, ctx) # - Get the proc body # - Find and replace: # - The ctx argument becomes `context` # - The obj argument becomes `object` # - Args is trickier: # - If it's not used, remove it # - If it's used, abandon ship and make it `**args` # - Convert string args access to symbol access, since it's a Ruby **splat # - Convert camelized arg names to underscored arg names # - (It would be nice to correctly become Ruby kwargs, but that might be too hard) # - Add a `# TODO` comment to the method source? # - Rebuild the method: # - use the field name as the method name # - handle args as described above # - put the modified proc body as the method body input_text.match(/(?input_field|field|connection|argument) :(?[a-zA-Z_0-9_]*)/) field_name = $~[:name] processor = apply_processor(input_text, ResolveProcProcessor.new) processor.resolve_proc_sections.reverse.each do |resolve_proc_section| proc_body = input_text[resolve_proc_section.proc_start..resolve_proc_section.proc_end] obj_arg_name, args_arg_name, ctx_arg_name = resolve_proc_section.proc_arg_names # This is not good, it will hit false positives # Should use AST to make this substitution if obj_arg_name != "_" proc_body.gsub!(/([^\w:.]|^)#{obj_arg_name}([^\w:]|$)/, '\1object\2') end if ctx_arg_name != "_" proc_body.gsub!(/([^\w:.]|^)#{ctx_arg_name}([^\w:]|$)/, '\1context\2') end method_def_indent = " " * (resolve_proc_section.resolve_indent - 2) # Turn the proc body into a method body method_body = reindent_lines(proc_body, from_indent: resolve_proc_section.resolve_indent + 2, to_indent: resolve_proc_section.resolve_indent) # Add `def... end` method_def = if input_text.include?("argument ") # This field has arguments "def #{field_name}(**#{args_arg_name})" else # No field arguments, so, no method arguments "def #{field_name}" end # Wrap the body in def ... end method_body = "\n#{method_def_indent}#{method_def}\n#{method_body}\n#{method_def_indent}end\n" # Update Argument access to be underscore and symbols # Update `args[...]` and `args.key?` method_body = method_body.gsub(/#{args_arg_name}(?\.key\?\(?|\[)["':](?[a-zA-Z0-9_]+)["']?(?\]|\))?/) do method_begin = $~[:method_begin] arg_name = underscorize($~[:arg_name]) method_end = $~[:method_end] "#{args_arg_name}#{method_begin}:#{arg_name}#{method_end}" end # Replace the resolve proc with the method input_text[resolve_proc_section.resolve_start..resolve_proc_section.resolve_end] = "" # The replacement above might have left some preceeding whitespace, # so remove it by deleting all whitespace chars before `resolve`: preceeding_whitespace = resolve_proc_section.resolve_start - 1 while input_text[preceeding_whitespace] == " " && preceeding_whitespace > 0 input_text[preceeding_whitespace] = "" preceeding_whitespace -= 1 end input_text += method_body input_text end end input_text end class ResolveProcProcessor < Parser::AST::Processor attr_reader :resolve_proc_sections def initialize @resolve_proc_sections = [] end class ResolveProcSection attr_accessor :proc_start, :proc_end, :proc_arg_names, :resolve_start, :resolve_end, :resolve_indent def initialize @proc_arg_names = nil @resolve_start = nil @resolve_end = nil @resolve_indent = nil @proc_start = nil @proc_end = nil end end def on_send(node) receiver, method_name, _args = *node if method_name == :resolve && receiver.nil? resolve_proc_section = ResolveProcSection.new source_exp = node.loc.expression resolve_proc_section.resolve_start = source_exp.begin.begin_pos resolve_proc_section.resolve_end = source_exp.end.end_pos resolve_proc_section.resolve_indent = source_exp.column @resolve_proc_sections << resolve_proc_section end super(node) end def on_block(node) send_node, args_node, body_node = node.children _receiver, method_name, _send_args_node = *send_node # Assume that the first three-argument proc we enter is the resolve if ( method_name == :lambda && args_node.children.size == 3 && !@resolve_proc_sections.empty? && @resolve_proc_sections[-1].proc_arg_names.nil? ) resolve_proc_section = @resolve_proc_sections[-1] source_exp = body_node.loc.expression resolve_proc_section.proc_arg_names = args_node.children.map { |arg_node| arg_node.children[0].to_s } resolve_proc_section.proc_start = source_exp.begin.begin_pos resolve_proc_section.proc_end = source_exp.end.end_pos end super(node) end end end # Transform `interfaces [A, B, C]` to `implements A\nimplements B\nimplements C\n` class InterfacesToImplementsTransform < Transform PATTERN = /(?\s*)(?:interfaces) \[\s*(?(?:[a-zA-Z_0-9:\.,\s]+))\]/m def apply(input_text) input_text.gsub(PATTERN) do indent = $~[:indent] interfaces = $~[:interfaces].split(',').map(&:strip).reject(&:empty?) # Preserve leading newlines before the `interfaces ...` # call, but don't re-insert them between `implements` calls. extra_leading_newlines = "\n" * (indent[/^\n*/].length - 1) indent = indent.sub(/^\n*/m, "") interfaces_calls = interfaces .map { |interface| "\n#{indent}implements #{interface}" } .join extra_leading_newlines + interfaces_calls end end end # Transform `possible_types [A, B, C]` to `possible_types(A, B, C)` class PossibleTypesTransform < Transform PATTERN = /(?\s*)(?:possible_types) \[\s*(?(?:[a-zA-Z_0-9:\.,\s]+))\]/m def apply(input_text) input_text.gsub(PATTERN) do indent = $~[:indent] possible_types = $~[:possible_types].split(',').map(&:strip).reject(&:empty?) extra_leading_newlines = indent[/^\n*/] method_indent = indent.sub(/^\n*/m, "") type_indent = " " + method_indent possible_types_call = "#{method_indent}possible_types(\n#{possible_types.map { |t| "#{type_indent}#{t},"}.join("\n")}\n#{method_indent})" extra_leading_newlines + trim_lines(possible_types_call) end end end class UpdateMethodSignatureTransform < Transform def apply(input_text) input_text.scan(/(?:input_field|field|return_field|connection|argument) .*$/).each do |field| matches = /(?input_field|return_field|field|connection|argument) :(?[a-zA-Z_0-9_]*)?(:?, +(?([A-Za-z\[\]\.\!_0-9\(\)]|::|-> ?\{ ?| ?\})+))?(?( |,|$).*)/.match(field) if matches name = matches[:name] return_type = matches[:return_type] remainder = matches[:remainder] field_type = matches[:field_type] with_block = remainder.gsub!(/\ do$/, '') remainder.gsub! /,$/, '' remainder.gsub! /^,/, '' remainder.chomp! if return_type non_nullable = return_type.sub! /(^|[^\[])!/, '\1' non_nullable ||= return_type.sub! /([^\[])\.to_non_null_type([^\]]|$)/, '\1' nullable = !non_nullable return_type = normalize_type_expression(return_type) else non_nullable = nil nullable = nil end input_text.sub!(field) do is_argument = ['argument', 'input_field'].include?(field_type) f = "#{is_argument ? 'argument' : 'field'} :#{name}" if return_type f += ", #{return_type}" end unless remainder.empty? f += ',' + remainder end if is_argument if nullable f += ', required: false' elsif non_nullable f += ', required: true' end else if nullable f += ', null: true' elsif non_nullable f += ', null: false' end end if field_type == 'connection' f += ', connection: true' end if with_block f += ' do' end f end end end input_text end end class RemoveEmptyBlocksTransform < Transform def apply(input_text) input_text.gsub(/\s*do\s*end/m, "") end end # Remove redundant newlines, which may have trailing spaces # Remove double newline after `do` # Remove double newline before `end` # Remove lines with whitespace only class RemoveExcessWhitespaceTransform < Transform def apply(input_text) input_text .gsub(/\n{3,}/m, "\n\n") .gsub(/do\n{2,}/m, "do\n") .gsub(/\n{2,}(\s*)end/m, "\n\\1end") .gsub(/\n +\n/m, "\n\n") end end # Skip this file if you see any `field` # helpers with `null: true` or `null: false` keywords # or `argument` helpers with `required:` keywords, # because it's already been transformed class SkipOnNullKeyword def skip?(input_text) input_text =~ /field.*null: (true|false)/ || input_text =~ /argument.*required: (true|false)/ end end class Member def initialize(member, skip: SkipOnNullKeyword, type_transforms: DEFAULT_TYPE_TRANSFORMS, field_transforms: DEFAULT_FIELD_TRANSFORMS, clean_up_transforms: DEFAULT_CLEAN_UP_TRANSFORMS) @member = member @skip = skip @type_transforms = type_transforms @field_transforms = field_transforms @clean_up_transforms = clean_up_transforms end DEFAULT_TYPE_TRANSFORMS = [ TypeDefineToClassTransform, MutationResolveProcToMethodTransform, # Do this before switching to class, so we can detect that its a mutation UnderscorizeMutationHashTransform, MutationDefineToClassTransform, NameTransform, InterfacesToImplementsTransform, PossibleTypesTransform, ProcToClassMethodTransform.new("coerce_input"), ProcToClassMethodTransform.new("coerce_result"), ProcToClassMethodTransform.new("resolve_type"), ] DEFAULT_FIELD_TRANSFORMS = [ RemoveNewlinesTransform, RemoveMethodParensTransform, PositionalTypeArgTransform, ConfigurationToKwargTransform.new(kwarg: "property"), ConfigurationToKwargTransform.new(kwarg: "description"), ConfigurationToKwargTransform.new(kwarg: "deprecation_reason"), ConfigurationToKwargTransform.new(kwarg: "hash_key"), PropertyToMethodTransform, UnderscoreizeFieldNameTransform, ResolveProcToMethodTransform, UpdateMethodSignatureTransform, RemoveRedundantKwargTransform.new(kwarg: "hash_key"), RemoveRedundantKwargTransform.new(kwarg: "method"), ] DEFAULT_CLEAN_UP_TRANSFORMS = [ RemoveExcessWhitespaceTransform, RemoveEmptyBlocksTransform, ] def upgrade type_source = @member.dup should_skip = @skip.new.skip?(type_source) # return the unmodified code if should_skip return type_source end # Transforms on type defn code: type_source = apply_transforms(type_source, @type_transforms) # Transforms on each field: field_sources = find_fields(type_source) field_sources.each do |field_source| transformed_field_source = apply_transforms(field_source.dup, @field_transforms) # Replace the original source code with the transformed source code: type_source = type_source.gsub(field_source, transformed_field_source) end # Clean-up: type_source = apply_transforms(type_source, @clean_up_transforms) # Return the transformed source: type_source end def upgradeable? return false if @member.include? '< GraphQL::Schema::' return false if @member =~ /< Types::Base#{GRAPHQL_TYPES}/ true end private def apply_transforms(source_code, transforms, idx: 0) next_transform = transforms[idx] case next_transform when nil # We got to the end of the list source_code when Class # Apply a class next_source_code = next_transform.new.apply(source_code) apply_transforms(next_source_code, transforms, idx: idx + 1) else # Apply an already-initialized object which responds to `apply` next_source_code = next_transform.apply(source_code) apply_transforms(next_source_code, transforms, idx: idx + 1) end end # Parse the type, find calls to `field` and `connection` # Return strings containing those calls def find_fields(type_source) type_ast = Parser::CurrentRuby.parse(type_source) finder = FieldFinder.new finder.process(type_ast) field_sources = [] # For each of the locations we found, extract the text for that definition. # The text will be transformed independently, # then the transformed text will replace the original text. FieldFinder::DEFINITION_METHODS.each do |def_method| finder.locations[def_method].each do |name, (starting_idx, ending_idx)| field_source = type_source[starting_idx..ending_idx] field_sources << field_source end end # Here's a crazy thing: the transformation is pure, # so definitions like `argument :id, types.ID` can be transformed once # then replaced everywhere. So: # - make a unique array here # - use `gsub` after performing the transformation. field_sources.uniq! field_sources rescue Parser::SyntaxError puts "Error Source:" puts type_source raise end class FieldFinder < Parser::AST::Processor # These methods are definition DSLs which may accept a block, # each of these definitions is passed for transformation in its own right. # `field` and `connection` take priority. In fact, they upgrade their # own arguments, so those upgrades turn out to be no-ops. DEFINITION_METHODS = [:field, :connection, :input_field, :return_field, :argument] attr_reader :locations def initialize # Pairs of `{ { method_name => { name => [start, end] } }`, # since fields/arguments are unique by name, within their category @locations = Hash.new { |h,k| h[k] = {} } end # @param send_node [node] The node which might be a `field` call, etc # @param source_node [node] The node whose source defines the bounds of the definition (eg, the surrounding block) def add_location(send_node:,source_node:) receiver_node, method_name, *arg_nodes = *send_node # Implicit self and one of the recognized methods if receiver_node.nil? && DEFINITION_METHODS.include?(method_name) name = arg_nodes[0] # This field may have already been added because # we find `(block ...)` nodes _before_ we find `(send ...)` nodes. if @locations[method_name][name].nil? starting_idx = source_node.loc.expression.begin.begin_pos ending_idx = source_node.loc.expression.end.end_pos @locations[method_name][name] = [starting_idx, ending_idx] end end end def on_block(node) send_node, _args_node, _body_node = *node add_location(send_node: send_node, source_node: node) super(node) end def on_send(node) add_location(send_node: node, source_node: node) super(node) end end end end end graphql-ruby-1.11.10/lib/graphql/upgrader/schema.rb000066400000000000000000000013641414121453000220710ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL module Upgrader class Schema def initialize(schema) @schema = schema end def upgrade transformable = schema.dup transformable.sub!( /([a-zA-Z_0-9]*) = GraphQL::Schema\.define do/, 'class \1 < GraphQL::Schema' ) transformable.sub!( /object_from_id ->\s?\((.*)\) do/, 'def self.object_from_id(\1)' ) transformable.sub!( /resolve_type ->\s?\((.*)\) do/, 'def self.resolve_type(\1)' ) transformable.sub!( /id_from_object ->\s?\((.*)\) do/, 'def self.id_from_object(\1)' ) transformable end private attr_reader :schema end end end graphql-ruby-1.11.10/lib/graphql/version.rb000066400000000000000000000001071414121453000204770ustar00rootroot00000000000000# frozen_string_literal: true module GraphQL VERSION = "1.11.10" end graphql-ruby-1.11.10/readme.md000066400000000000000000000051121414121453000160410ustar00rootroot00000000000000# graphql graphql-ruby [![Build Status](https://travis-ci.org/rmosolgo/graphql-ruby.svg?branch=master)](https://travis-ci.org/rmosolgo/graphql-ruby) [![Gem Version](https://badge.fury.io/rb/graphql.svg)](https://rubygems.org/gems/graphql) [![Code Climate](https://codeclimate.com/github/rmosolgo/graphql-ruby/badges/gpa.svg)](https://codeclimate.com/github/rmosolgo/graphql-ruby) [![Test Coverage](https://codeclimate.com/github/rmosolgo/graphql-ruby/badges/coverage.svg)](https://codeclimate.com/github/rmosolgo/graphql-ruby) [![built with love](https://cloud.githubusercontent.com/assets/2231765/6766607/d07992c6-cfc9-11e4-813f-d9240714dd50.png)](https://rmosolgo.github.io/react-badges/) A Ruby implementation of [GraphQL](https://graphql.org/). - [Website](https://graphql-ruby.org/) - [API Documentation](https://www.rubydoc.info/github/rmosolgo/graphql-ruby) - [Newsletter](https://tinyletter.com/graphql-ruby) ## Installation Install from RubyGems by adding it to your `Gemfile`, then bundling. ```ruby # Gemfile gem 'graphql' ``` ``` $ bundle install ``` ## Getting Started ``` $ rails generate graphql:install ``` After this, you may need to run `bundle install` again, as by default graphiql-rails is added on installation. Or, see ["Getting Started"](https://graphql-ruby.org/getting_started.html). ## Upgrade I also sell [GraphQL::Pro](https://graphql.pro) which provides several features on top of the GraphQL runtime, including [Pundit authorization](https://graphql-ruby.org/authorization/pundit_integration), [CanCan authorization](https://graphql-ruby.org/authorization/can_can_integration), [Pusher-based subscriptions](https://graphql-ruby.org/subscriptions/pusher_implementation) and [persisted queries](https://graphql-ruby.org/operation_store/overview). Besides that, Pro customers get email support and an opportunity to support graphql-ruby's development! ## Goals - Implement the GraphQL spec & support a Relay front end - Provide idiomatic, plain-Ruby API with similarities to reference implementation where possible - Support Ruby on Rails and Relay ## Getting Involved - __Say hi & ask questions__ in the [#ruby channel on Slack](https://graphql-slack.herokuapp.com/) or [on Twitter](https://twitter.com/rmosolgo)! - __Report bugs__ by posting a description, full stack trace, and all relevant code in a [GitHub issue](https://github.com/rmosolgo/graphql-ruby/issues). - __Start hacking__ with the [Development guide](https://graphql-ruby.org/development). graphql-ruby-1.11.10/spec/000077500000000000000000000000001414121453000152155ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/000077500000000000000000000000001414121453000163505ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/.gitignore000066400000000000000000000006651414121453000203470ustar00rootroot00000000000000# See https://help.github.com/articles/ignoring-files for more about ignoring files. # # If you find yourself ignoring temporary files generated by your text editor # or operating system, you probably want to add a global ignore instead: # git config --global core.excludesfile '~/.gitignore_global' # Ignore bundler config. /.bundle # Ignore all logfiles and tempfiles. /log/* /tmp/* /node_modules /yarn-error.log .byebug_history graphql-ruby-1.11.10/spec/dummy/Gemfile000066400000000000000000000004071414121453000176440ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gem 'rails', '~> 5.2.1' gem 'puma' gem 'capybara' gem 'selenium-webdriver' gem 'graphql', path: File.expand_path('../../', __dir__) group :development do gem 'listen' end gem "webdrivers", "~> 4.1" graphql-ruby-1.11.10/spec/dummy/README.md000066400000000000000000000005661414121453000176360ustar00rootroot00000000000000# README This README would normally document whatever steps are necessary to get the application up and running. Things you may want to cover: * Ruby version * System dependencies * Configuration * Database creation * Database initialization * How to run the test suite * Services (job queues, cache servers, search engines, etc.) * Deployment instructions * ... graphql-ruby-1.11.10/spec/dummy/Rakefile000066400000000000000000000004011414121453000200100ustar00rootroot00000000000000# frozen_string_literal: true # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. require_relative 'config/application' Rails.application.load_tasks graphql-ruby-1.11.10/spec/dummy/app/000077500000000000000000000000001414121453000171305ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/app/assets/000077500000000000000000000000001414121453000204325ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/app/assets/config/000077500000000000000000000000001414121453000216775ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/app/assets/config/manifest.js000066400000000000000000000000461414121453000240430ustar00rootroot00000000000000//= link_directory ../javascripts .js graphql-ruby-1.11.10/spec/dummy/app/assets/javascripts/000077500000000000000000000000001414121453000227635ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/app/assets/javascripts/application.js000066400000000000000000000036251414121453000256320ustar00rootroot00000000000000// This is a manifest file that'll be compiled into application.js, which will include all the files // listed below. // // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's // vendor/assets/javascripts directory can be referenced here using a relative path. // // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // compiled file. JavaScript code in this file should be added after the last require_* statement. // // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details // about supported directives. // //= require rails-ujs // Action Cable provides the framework to deal with WebSockets in Rails. // You can generate new channels where WebSocket features live using the `rails generate channel` command. // //= require action_cable //= require_self (function() { this.App || (this.App = {}); App.cable = ActionCable.createConsumer(); App.subscribe = function(options) { var query = options.query var variables = options.variables var receivedCallback = options.received // Unique-ish var uuid = Math.round(Date.now() + Math.random() * 100000).toString(16) return { subscription: App.cable.subscriptions.create({ channel: "GraphqlChannel", id: uuid, }, { connected: function() { this.perform("execute", { query: query, variables: variables, }) console.log("Connected", query, variables) }, received: function(data) { console.log("received", query, variables, data) receivedCallback(data) } } ), trigger: function(options) { this.subscription.perform("make_trigger", options) }, unsubscribe: function() { this.subscription.unsubscribe() }, } } }).call(this); graphql-ruby-1.11.10/spec/dummy/app/channels/000077500000000000000000000000001414121453000207235ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/app/channels/application_cable/000077500000000000000000000000001414121453000243545ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/app/channels/application_cable/channel.rb000066400000000000000000000001551414121453000263120ustar00rootroot00000000000000# frozen_string_literal: true module ApplicationCable class Channel < ActionCable::Channel::Base end end graphql-ruby-1.11.10/spec/dummy/app/channels/application_cable/connection.rb000066400000000000000000000001631414121453000270400ustar00rootroot00000000000000# frozen_string_literal: true module ApplicationCable class Connection < ActionCable::Connection::Base end end graphql-ruby-1.11.10/spec/dummy/app/channels/graphql_channel.rb000066400000000000000000000062511414121453000244020ustar00rootroot00000000000000# frozen_string_literal: true class GraphqlChannel < ActionCable::Channel::Base class QueryType < GraphQL::Schema::Object field :value, Integer, null: false def value 3 end end class PayloadType < GraphQL::Schema::Object field :value, Integer, null: false end class CounterIncremented < GraphQL::Schema::Subscription @@call_count = 0 subscription_scope :subscriber_id field :new_value, Integer, null: false def update result = { new_value: @@call_count += 1 } puts " -> CounterIncremented#update(#{context[:subscriber_id]}): #{result}" result end end class SubscriptionType < GraphQL::Schema::Object field :payload, PayloadType, null: false do argument :id, ID, required: true end field :counter_incremented, subscription: CounterIncremented end # Wacky behavior around the number 4 # so we can confirm it's used by the UI module CustomSerializer def self.load(value) if value == "4x" ExamplePayload.new(400) else GraphQL::Subscriptions::Serialize.load(value) end end def self.dump(obj) if obj.is_a?(ExamplePayload) && obj.value == 4 "4x" else GraphQL::Subscriptions::Serialize.dump(obj) end end end class GraphQLSchema < GraphQL::Schema query(QueryType) subscription(SubscriptionType) use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST use GraphQL::Subscriptions::ActionCableSubscriptions, serializer: CustomSerializer, broadcast: true, default_broadcastable: true end def subscribed @subscription_ids = [] end def execute(data) query = data["query"] variables = data["variables"] || {} operation_name = data["operationName"] context = { # Make sure the channel is in the context channel: self, } puts "[GraphQLSchema.execute] #{query} || #{variables}" result = GraphQLSchema.execute({ query: query, context: context, variables: variables, operation_name: operation_name }) payload = { result: result.to_h, more: result.subscription?, } # Track the subscription here so we can remove it # on unsubscribe. if result.context[:subscription_id] @subscription_ids << result.context[:subscription_id] end puts " -> [transmit(#{result.context[:subscription_id]})] #{payload.inspect}" transmit(payload) end def make_trigger(data) field = data["field"] args = data["arguments"] value = data["value"] value = value && ExamplePayload.new(value) puts "[make_trigger] #{[field, args, value]}" GraphQLSchema.subscriptions.trigger(field, args, value) end def unsubscribed @subscription_ids.each { |sid| puts "[delete_subscription] #{sid}" GraphQLSchema.subscriptions.delete_subscription(sid) } end # This is to make sure that GlobalID is used to load and dump this object class ExamplePayload include GlobalID::Identification def initialize(value) @value = value end def self.find(value) self.new(value) end attr_reader :value alias :id :value end end graphql-ruby-1.11.10/spec/dummy/app/controllers/000077500000000000000000000000001414121453000214765ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/app/controllers/application_controller.rb000066400000000000000000000001771414121453000265760ustar00rootroot00000000000000# frozen_string_literal: true class ApplicationController < ActionController::Base protect_from_forgery with: :exception end graphql-ruby-1.11.10/spec/dummy/app/controllers/pages_controller.rb000066400000000000000000000001411414121453000253610ustar00rootroot00000000000000# frozen_string_literal: true class PagesController < ApplicationController def show end end graphql-ruby-1.11.10/spec/dummy/app/helpers/000077500000000000000000000000001414121453000205725ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/app/helpers/application_helper.rb000066400000000000000000000000731414121453000247610ustar00rootroot00000000000000# frozen_string_literal: true module ApplicationHelper end graphql-ruby-1.11.10/spec/dummy/app/jobs/000077500000000000000000000000001414121453000200655ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/app/jobs/application_job.rb000066400000000000000000000001111414121453000235400ustar00rootroot00000000000000# frozen_string_literal: true class ApplicationJob < ActiveJob::Base end graphql-ruby-1.11.10/spec/dummy/app/views/000077500000000000000000000000001414121453000202655ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/app/views/layouts/000077500000000000000000000000001414121453000217655ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/app/views/layouts/application.html.erb000066400000000000000000000002721414121453000257260ustar00rootroot00000000000000 Dummy <%= csrf_meta_tags %> <%= javascript_include_tag 'application' %> <%= yield %> graphql-ruby-1.11.10/spec/dummy/app/views/pages/000077500000000000000000000000001414121453000213645ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/app/views/pages/show.html000066400000000000000000000066551414121453000232460ustar00rootroot00000000000000

ActionCable Test Page

ActionCable updates:


Fingerprint test


graphql-ruby-1.11.10/spec/dummy/bin/000077500000000000000000000000001414121453000171205ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/bin/bundle000077500000000000000000000002371414121453000203210ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) load Gem.bin_path('bundler', 'bundle') graphql-ruby-1.11.10/spec/dummy/bin/rails000077500000000000000000000002531414121453000201600ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true APP_PATH = File.expand_path('../config/application', __dir__) require_relative '../config/boot' require 'rails/commands' graphql-ruby-1.11.10/spec/dummy/bin/rake000077500000000000000000000001701414121453000177660ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require_relative '../config/boot' require 'rake' Rake.application.run graphql-ruby-1.11.10/spec/dummy/bin/setup000077500000000000000000000014411414121453000202060ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require 'pathname' require 'fileutils' include FileUtils # path to your application root. APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") end chdir APP_ROOT do # This script is a starting point to setup your application. # Add necessary setup steps to this file. puts '== Installing dependencies ==' system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') # Install JavaScript dependencies if using Yarn # system('bin/yarn') puts "\n== Removing old logs and tempfiles ==" system! 'bin/rails log:clear tmp:clear' puts "\n== Restarting application server ==" system! 'bin/rails restart' end graphql-ruby-1.11.10/spec/dummy/bin/update000077500000000000000000000013471414121453000203350ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require 'pathname' require 'fileutils' include FileUtils # path to your application root. APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") end chdir APP_ROOT do # This script is a way to update your development environment automatically. # Add necessary update steps to this file. puts '== Installing dependencies ==' system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') puts "\n== Removing old logs and tempfiles ==" system! 'bin/rails log:clear tmp:clear' puts "\n== Restarting application server ==" system! 'bin/rails restart' end graphql-ruby-1.11.10/spec/dummy/bin/yarn000077500000000000000000000005361414121453000200230ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true VENDOR_PATH = File.expand_path('..', __dir__) Dir.chdir(VENDOR_PATH) do begin exec "yarnpkg #{ARGV.join(" ")}" rescue Errno::ENOENT $stderr.puts "Yarn executable was not detected in the system." $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" exit 1 end end graphql-ruby-1.11.10/spec/dummy/config.ru000066400000000000000000000002401414121453000201610ustar00rootroot00000000000000# frozen_string_literal: true # This file is used by Rack-based servers to start the application. require_relative 'config/environment' run Rails.application graphql-ruby-1.11.10/spec/dummy/config/000077500000000000000000000000001414121453000176155ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/config/application.rb000066400000000000000000000016661414121453000224560ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'boot' require "rails" # Pick the frameworks you want: require "active_model/railtie" require "active_job/railtie" require "action_controller/railtie" require "action_view/railtie" require "action_cable/engine" require "sprockets/railtie" require "rails/test_unit/railtie" # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) module Dummy class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 5.1 # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. # Don't generate system test files. config.generators.system_tests = nil end end graphql-ruby-1.11.10/spec/dummy/config/boot.rb000066400000000000000000000002441414121453000211050ustar00rootroot00000000000000# frozen_string_literal: true ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__) require 'bundler/setup' # Set up gems listed in the Gemfile. graphql-ruby-1.11.10/spec/dummy/config/cable.yml000066400000000000000000000002271414121453000214070ustar00rootroot00000000000000development: adapter: async test: adapter: async production: adapter: redis url: redis://localhost:6379/1 channel_prefix: dummy_production graphql-ruby-1.11.10/spec/dummy/config/environment.rb000066400000000000000000000002361414121453000225070ustar00rootroot00000000000000# frozen_string_literal: true # Load the Rails application. require_relative 'application' # Initialize the Rails application. Rails.application.initialize! graphql-ruby-1.11.10/spec/dummy/config/environments/000077500000000000000000000000001414121453000223445ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/config/environments/development.rb000066400000000000000000000025541414121453000252210ustar00rootroot00000000000000# frozen_string_literal: true Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. config.cache_classes = false # Do not eager load code on boot. config.eager_load = false # Show full error reports. config.consider_all_requests_local = true # Enable/disable caching. By default caching is disabled. if Rails.root.join('tmp/caching-dev.txt').exist? config.action_controller.perform_caching = true config.cache_store = :memory_store config.public_file_server.headers = { 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}" } else config.action_controller.perform_caching = false config.cache_store = :null_store end # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log # Raises error for missing translations # config.action_view.raise_on_missing_translations = true # Use an evented file watcher to asynchronously detect changes in source code, # routes, locales, etc. This feature depends on the listen gem. config.file_watcher = ActiveSupport::EventedFileUpdateChecker end graphql-ruby-1.11.10/spec/dummy/config/environments/production.rb000066400000000000000000000060461414121453000250650ustar00rootroot00000000000000# frozen_string_literal: true Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # Code is not reloaded between requests. config.cache_classes = true # Eager load code on boot. This eager loads most of Rails and # your application in memory, allowing both threaded web servers # and those relying on copy on write to perform better. # Rake tasks automatically ignore this option for performance. config.eager_load = true # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false config.action_controller.perform_caching = true # Attempt to read encrypted secrets from `config/secrets.yml.enc`. # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or # `config/secrets.yml.key`. config.read_encrypted_secrets = true # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.action_controller.asset_host = 'http://assets.example.com' # Specifies the header that your server uses for sending files. # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # Mount Action Cable outside main process or domain # config.action_cable.mount_path = nil # config.action_cable.url = 'wss://example.com/cable' # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true # Use the lowest log level to ensure availability of diagnostic information # when problems arise. config.log_level = :debug # Prepend all log lines with the following tags. config.log_tags = [ :request_id ] # Use a different cache store in production. # config.cache_store = :mem_cache_store # Use a real queuing backend for Active Job (and separate queues per environment) # config.active_job.queue_adapter = :resque # config.active_job.queue_name_prefix = "dummy_#{Rails.env}" # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). config.i18n.fallbacks = true # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify # Use default logging formatter so that PID and timestamp are not suppressed. config.log_formatter = ::Logger::Formatter.new # Use a different logger for distributed setups. # require 'syslog/logger' # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') if ENV["RAILS_LOG_TO_STDOUT"].present? logger = ActiveSupport::Logger.new(STDOUT) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) end end graphql-ruby-1.11.10/spec/dummy/config/environments/test.rb000066400000000000000000000027771414121453000236650ustar00rootroot00000000000000# frozen_string_literal: true Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! config.cache_classes = true # Do not eager load code on boot. This avoids loading your whole application # just for the purpose of running a single test. If you are using a tool that # preloads Rails for running tests, you may have to set it to true. config.eager_load = false # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true config.public_file_server.headers = { 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" } # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false # Raise exceptions instead of rendering exception templates. config.action_dispatch.show_exceptions = false # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr # Raises error for missing translations # config.action_view.raise_on_missing_translations = true end graphql-ruby-1.11.10/spec/dummy/config/initializers/000077500000000000000000000000001414121453000223235ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/config/initializers/application_controller_renderer.rb000066400000000000000000000003661414121453000313110ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # ActiveSupport::Reloader.to_prepare do # ApplicationController.renderer.defaults.merge!( # http_host: 'example.org', # https: false # ) # end graphql-ruby-1.11.10/spec/dummy/config/initializers/backtrace_silencers.rb000066400000000000000000000006621414121453000266420ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. # Rails.backtrace_cleaner.remove_silencers! graphql-ruby-1.11.10/spec/dummy/config/initializers/cookies_serializer.rb000066400000000000000000000004221414121453000265330ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Specify a serializer for the signed and encrypted cookie jars. # Valid options are :json, :marshal, and :hybrid. Rails.application.config.action_dispatch.cookies_serializer = :json graphql-ruby-1.11.10/spec/dummy/config/initializers/filter_parameter_logging.rb000066400000000000000000000003401414121453000277000ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Configure sensitive parameters which will be filtered from the log file. Rails.application.config.filter_parameters += [:password] graphql-ruby-1.11.10/spec/dummy/config/initializers/inflections.rb000066400000000000000000000012451414121453000251670ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Add new inflection rules using the following format. Inflections # are locale specific, and you may define rules for as many different # locales as you wish. All of these examples are active by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.plural /^(ox)$/i, '\1en' # inflect.singular /^(ox)en/i, '\1' # inflect.irregular 'person', 'people' # inflect.uncountable %w( fish sheep ) # end # These inflection rules are supported but not enabled by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.acronym 'RESTful' # end graphql-ruby-1.11.10/spec/dummy/config/initializers/mime_types.rb000066400000000000000000000002721414121453000250240ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Add new mime types for use in respond_to blocks: # Mime::Type.register "text/richtext", :rtf graphql-ruby-1.11.10/spec/dummy/config/initializers/wrap_parameters.rb000066400000000000000000000005611414121453000260460ustar00rootroot00000000000000# frozen_string_literal: true # Be sure to restart your server when you modify this file. # This file contains settings for ActionController::ParamsWrapper which # is enabled by default. # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. ActiveSupport.on_load(:action_controller) do wrap_parameters format: [:json] end graphql-ruby-1.11.10/spec/dummy/config/locales/000077500000000000000000000000001414121453000212375ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/config/locales/en.yml000066400000000000000000000015211414121453000223630ustar00rootroot00000000000000# Files in the config/locales directory are used for internationalization # and are automatically loaded by Rails. If you want to use locales other # than English, add the necessary files in this directory. # # To use the locales, use `I18n.t`: # # I18n.t 'hello' # # In views, this is aliased to just `t`: # # <%= t('hello') %> # # To use a different locale, set it with `I18n.locale`: # # I18n.locale = :es # # This would use the information in config/locales/es.yml. # # The following keys must be escaped otherwise they will not be retrieved by # the default I18n backend: # # true, false, on, off, yes, no # # Instead, surround them with single quotes. # # en: # 'true': 'foo' # # To learn more, please read the Rails Internationalization guide # available at https://guides.rubyonrails.org/i18n.html. en: hello: "Hello world" graphql-ruby-1.11.10/spec/dummy/config/puma.rb000066400000000000000000000044401414121453000211060ustar00rootroot00000000000000# frozen_string_literal: true # Puma can serve each request in a thread from an internal thread pool. # The `threads` method setting takes two numbers: a minimum and maximum. # Any libraries that use thread pools should be configured to match # the maximum value specified for Puma. Default is set to 5 threads for minimum # and maximum; this matches the default thread size of Active Record. # threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } threads threads_count, threads_count # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # port ENV.fetch("PORT") { 3000 } # Specifies the `environment` that Puma will run in. # environment ENV.fetch("RAILS_ENV") { "development" } # Specifies the number of `workers` to boot in clustered mode. # Workers are forked webserver processes. If using threads and workers together # the concurrency of the application would be max `threads` * `workers`. # Workers do not work on JRuby or Windows (both of which do not support # processes). # # workers ENV.fetch("WEB_CONCURRENCY") { 2 } # Use the `preload_app!` method when specifying a `workers` number. # This directive tells Puma to first boot the application and load code # before forking the application. This takes advantage of Copy On Write # process behavior so workers use less memory. If you use this option # you need to make sure to reconnect any threads in the `on_worker_boot` # block. # # preload_app! # If you are preloading your application and using Active Record, it's # recommended that you close any connections to the database before workers # are forked to prevent connection leakage. # # before_fork do # ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) # end # The code in the `on_worker_boot` will be called if you are using # clustered mode by specifying a number of `workers`. After each worker # process is booted, this block will be run. If you are using the `preload_app!` # option, you will want to use this block to reconnect to any threads # or connections that may have been created at application boot, as Ruby # cannot share connections between processes. # # on_worker_boot do # ActiveRecord::Base.establish_connection if defined?(ActiveRecord) # end # # Allow puma to be restarted by `rails restart` command. plugin :tmp_restart graphql-ruby-1.11.10/spec/dummy/config/routes.rb000066400000000000000000000001331414121453000214600ustar00rootroot00000000000000# frozen_string_literal: true Rails.application.routes.draw do root to: "pages#show" end graphql-ruby-1.11.10/spec/dummy/config/secrets.yml000066400000000000000000000023751414121453000220170ustar00rootroot00000000000000# Be sure to restart your server when you modify this file. # Your secret key is used for verifying the integrity of signed cookies. # If you change this key, all old signed cookies will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. # You can use `rails secret` to generate a secure secret key. # Make sure the secrets in this file are kept private # if you're sharing your code publicly. # Shared secrets are available across all environments. # shared: # api_key: a1B2c3D4e5F6 # Environmental secrets are only available for that specific environment. development: secret_key_base: 32be83294ab8a3b21fce26b6e48db6c5d84cfa3c89e686dcb39369806e8daba53215caed302b89ce4a54b5c67ff2fa6be797f0f5c937a71fbb9f396ef108e867 test: secret_key_base: 88c4ea138f1ad39c9eaa3196361a539e8aba1fcbffa3c2383045799cefe150fadaa7256ea680a912d27a878fdff55debd3f920388de68aa4f81828b0bd2ac072 # Do not keep production secrets in the unencrypted secrets file. # Instead, either read values from the environment. # Or, use `bin/rails secrets:setup` to configure encrypted secrets # and move the `production:` environment over there. production: secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> graphql-ruby-1.11.10/spec/dummy/package.json000066400000000000000000000000771414121453000206420ustar00rootroot00000000000000{ "name": "dummy", "private": true, "dependencies": {} } graphql-ruby-1.11.10/spec/dummy/public/000077500000000000000000000000001414121453000176265ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/public/404.html000066400000000000000000000032721414121453000210270ustar00rootroot00000000000000 The page you were looking for doesn't exist (404)

The page you were looking for doesn't exist.

You may have mistyped the address or the page may have moved.

If you are the application owner check the logs for more information.

graphql-ruby-1.11.10/spec/dummy/public/422.html000066400000000000000000000032511414121453000210240ustar00rootroot00000000000000 The change you wanted was rejected (422)

The change you wanted was rejected.

Maybe you tried to change something you didn't have access to.

If you are the application owner check the logs for more information.

graphql-ruby-1.11.10/spec/dummy/public/500.html000066400000000000000000000031431414121453000210210ustar00rootroot00000000000000 We're sorry, but something went wrong (500)

We're sorry, but something went wrong.

If you are the application owner check the logs for more information.

graphql-ruby-1.11.10/spec/dummy/public/apple-touch-icon-precomposed.png000066400000000000000000000000001414121453000260070ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/public/apple-touch-icon.png000066400000000000000000000000001414121453000234710ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/public/favicon.ico000066400000000000000000000000001414121453000217350ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/public/robots.txt000066400000000000000000000001421414121453000216740ustar00rootroot00000000000000# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file graphql-ruby-1.11.10/spec/dummy/test/000077500000000000000000000000001414121453000173275ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/test/application_system_test_case.rb000066400000000000000000000011371414121453000256170ustar00rootroot00000000000000# frozen_string_literal: true require "test_helper" class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :selenium, using: :chrome, screen_size: [1400, 1400], options: { args: ["headless", "disable-gpu", "no-sandbox", "disable-dev-shm-usage"] } teardown do # Adapted from https://medium.com/@coorasse/catch-javascript-errors-in-your-system-tests-89c2fe6773b1 errors = page.driver.browser.manage.logs.get(:browser) if errors.present? errors.each do |error| assert_nil "#{error.level}: #{error.message}" end end end end graphql-ruby-1.11.10/spec/dummy/test/system/000077500000000000000000000000001414121453000206535ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/dummy/test/system/action_cable_subscription_test.rb000066400000000000000000000122411414121453000274460ustar00rootroot00000000000000# frozen_string_literal: true require "application_system_test_case" class ActionCableSubscriptionsTest < ApplicationSystemTestCase # This test covers a lot of ground! test "it handles subscriptions" do # Load the page and let the subscriptions happen visit "/" # make sure they connect successfully assert_selector "#updates-1-connected" assert_selector "#updates-2-connected" # Trigger a few updates, make sure we get a client update: click_on("Trigger 1") click_on("Trigger 1") click_on("Trigger 1") assert_selector "#updates-1-3", text: "3" # Make sure there aren't any unexpected elements: refute_selector "#updates-1-4" refute_selector "#updates-2-1" # Now, trigger updates to a different stream # and make sure the previous stream is not affected click_on("Trigger 2") click_on("Trigger 2") assert_selector "#updates-2-1", text: "1" assert_selector "#updates-2-2", text: "2" refute_selector "#updates-2-3" refute_selector "#updates-1-4" # Now unsubscribe one, it should not receive updates but the other should click_on("Unsubscribe 1") click_on("Trigger 1") # This should not have changed refute_selector "#updates-1-4" click_on("Trigger 2") assert_selector "#updates-2-3", text: "3" refute_selector "#updates-1-4" # wacky behavior to make sure the custom serializer is used: click_on("Trigger 2") assert_selector "#updates-2-400", text: "400" end # Wrap an `assert_selector` call in a debugging check def debug_assert_selector(selector) if !page.has_css?(selector) puts "[debug_assert_selector(#{selector.inspect})] Failed to find #{selector.inspect} in:" puts page.html else puts "[debug_assert_selector(#{selector.inspect})] Found #{selector.inspect}" end assert_selector(selector) end # It seems like ActionCable's order of evaluation here is non-deterministic, # so detect which order to make the assertions. # (They still both have to pass, but we don't know exactly what order the evaluations went in.) def detect_update_values(possibility_1, possibility_2) if page.has_css?("#fingerprint-updates-1-update-1-value-#{possibility_1}") [possibility_1, possibility_2] else [possibility_2, possibility_1] end end test "it only re-runs queries once for subscriptions with matching fingerprints" do visit "/" using_wait_time 30 do sleep 1 # Make 3 subscriptions to the same payload click_on("Subscribe with fingerprint 1") debug_assert_selector "#fingerprint-updates-1-connected-1" click_on("Subscribe with fingerprint 1") debug_assert_selector "#fingerprint-updates-1-connected-2" click_on("Subscribe with fingerprint 1") debug_assert_selector "#fingerprint-updates-1-connected-3" # And two to the next payload click_on("Subscribe with fingerprint 2") debug_assert_selector "#fingerprint-updates-2-connected-1" click_on("Subscribe with fingerprint 2") debug_assert_selector "#fingerprint-updates-2-connected-2" # Now trigger. We expect a total of two updates: # - One is built & delivered to the first three subscribers # - Another is built & delivered to the next two click_on("Trigger with fingerprint 2") # The order here is random, I think depending on ActionCable's internal storage: fingerprint_1_value, fingerprint_2_value = detect_update_values(1, 2) # These all share the first value: debug_assert_selector "#fingerprint-updates-1-update-1-value-#{fingerprint_1_value}" debug_assert_selector "#fingerprint-updates-1-update-2-value-#{fingerprint_1_value}" debug_assert_selector "#fingerprint-updates-1-update-3-value-#{fingerprint_1_value}" # and these share the second value: debug_assert_selector "#fingerprint-updates-2-update-1-value-#{fingerprint_2_value}" debug_assert_selector "#fingerprint-updates-2-update-2-value-#{fingerprint_2_value}" click_on("Unsubscribe with fingerprint 2") click_on("Trigger with fingerprint 1") fingerprint_1_value_2, fingerprint_2_value_2 = detect_update_values(3, 4) # These get an update debug_assert_selector "#fingerprint-updates-1-update-1-value-#{fingerprint_1_value_2}" debug_assert_selector "#fingerprint-updates-1-update-2-value-#{fingerprint_1_value_2}" debug_assert_selector "#fingerprint-updates-1-update-3-value-#{fingerprint_1_value_2}" # But these are unsubscribed: refute_selector "#fingerprint-updates-2-update-1-value-#{fingerprint_2_value_2}" refute_selector "#fingerprint-updates-2-update-2-value-#{fingerprint_2_value_2}" click_on("Unsubscribe with fingerprint 1") # Make a new subscription and make sure it's updated: click_on("Subscribe with fingerprint 2") click_on("Trigger with fingerprint 2") debug_assert_selector "#fingerprint-updates-2-update-1-value-#{fingerprint_2_value_2}" # But this one was unsubscribed: refute_selector "#fingerprint-updates-1-update-1-value-#{fingerprint_1_value_2 + 1}" refute_selector "#fingerprint-updates-1-update-1-value-#{fingerprint_1_value_2 + 2}" end end end graphql-ruby-1.11.10/spec/dummy/test/test_helper.rb000066400000000000000000000002221414121453000221660ustar00rootroot00000000000000# frozen_string_literal: true TESTING_INTERPRETER = true require File.expand_path('../../config/environment', __FILE__) require 'rails/test_help' graphql-ruby-1.11.10/spec/fixtures/000077500000000000000000000000001414121453000170665ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/fixtures/upgrader/000077500000000000000000000000001414121453000206775ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/fixtures/upgrader/account.original.rb000066400000000000000000000005631414121453000244670ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Unions Account = GraphQL::UnionType.define do name "Account" description "Users and organizations." visibility :internal possible_types [ Objects::User, Objects::Organization, Objects::Bot ] resolve_type ->(obj, ctx) { :stand_in } end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/account.transformed.rb000066400000000000000000000005621414121453000252060ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Unions class Account < Platform::Unions::Base description "Users and organizations." visibility :internal possible_types( Objects::User, Objects::Organization, Objects::Bot, ) def self.resolve_type(obj, ctx) :stand_in end end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/blame_range.original.rb000066400000000000000000000022431414121453000252640ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Objects BlameRange = GraphQL::ObjectType.define do name "BlameRange" description "Represents a range of information from a Git blame." scopeless_tokens_as_minimum interfaces [ Interfaces::A, Interfaces::B, ] field :startingLine, !types.Int do description "The starting line for the range" resolve ->(range, args, context) { range.lines.first[:lineno] } end field :endingLine, !types.Int do description "The ending line for the range" resolve ->(range, args, context) { range.lines.first[:lineno] + (range.lines.length - 1) } end field :commit, -> { !Objects::Commit } do description "Identifies the line author" end field :age, !types.Int do description "Identifies the recency of the change, from 1 (new) to 10 (old). This is calculated as a 2-quantile and determines the length of distance between the median age of all the changes in the file and the recency of the current range's change." property :scale end end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/blame_range.transformed.rb000066400000000000000000000020471414121453000260060ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Objects class BlameRange < Platform::Objects::Base description "Represents a range of information from a Git blame." scopeless_tokens_as_minimum implements Interfaces::A implements Interfaces::B field :starting_line, Integer, description: "The starting line for the range", null: false def starting_line object.lines.first[:lineno] end field :ending_line, Integer, description: "The ending line for the range", null: false def ending_line object.lines.first[:lineno] + (object.lines.length - 1) end field :commit, Objects::Commit, description: "Identifies the line author", null: false field :age, Integer, method: :scale, description: "Identifies the recency of the change, from 1 (new) to 10 (old). This is calculated as a 2-quantile and determines the length of distance between the median age of all the changes in the file and the recency of the current range's change.", null: false end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/date_time.original.rb000066400000000000000000000010731414121453000247630ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Scalars DateTime = GraphQL::ScalarType.define do name "DateTime" description "An ISO-8601 encoded UTC date string." # rubocop:disable Layout/SpaceInLambdaLiteral coerce_input -> (value, context) do begin Time.iso8601(value) rescue ArgumentError, ::TypeError end end # rubocop:enable Layout/SpaceInLambdaLiteral coerce_result ->(value, context) do return nil unless value value.utc.iso8601 end end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/date_time.transformed.rb000066400000000000000000000010521414121453000255000ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Scalars class DateTime < Platform::Scalars::Base description "An ISO-8601 encoded UTC date string." # rubocop:disable Layout/SpaceInLambdaLiteral def self.coerce_input(value, context) begin Time.iso8601(value) rescue ArgumentError, ::TypeError end end # rubocop:enable Layout/SpaceInLambdaLiteral def self.coerce_result(value, context) return nil unless value value.utc.iso8601 end end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/delete_project.original.rb000066400000000000000000000016001414121453000260140ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Mutations DeleteProject = GraphQL::Relay::Mutation.define do name "DeleteProject" description "Deletes a project." minimum_accepted_scopes ["public_repo"] input_field :projectId, !types.ID, "The Project ID to update." return_field :owner, !Interfaces::ProjectOwner, "The repository or organization the project was removed from." resolve ->(root_obj, inputs, context) do project = Platform::Helpers::NodeIdentification.typed_object_from_id( [Objects::Project], inputs[:projectId], context ) context[:permission].can_modify?("DeleteProject", project).sync context[:abilities].authorize_content(:project, :destroy, owner: project.owner) project.enqueue_delete(actor: context[:viewer]) { owner: project.owner } end end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/delete_project.transformed.rb000066400000000000000000000015321414121453000265400ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Mutations class DeleteProject < Mutations::BaseMutation description "Deletes a project." minimum_accepted_scopes ["public_repo"] argument :project_id, ID, "The Project ID to update.", required: true field :owner, Interfaces::ProjectOwner, "The repository or organization the project was removed from.", null: false def resolve(**inputs) project = Platform::Helpers::NodeIdentification.typed_object_from_id( [Objects::Project], inputs[:project_id], context ) context[:permission].can_modify?("DeleteProject", project).sync context[:abilities].authorize_content(:project, :destroy, owner: project.owner) project.enqueue_delete(actor: context[:viewer]) { owner: project.owner } end end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/gist_order_field.original.rb000066400000000000000000000007111414121453000263320ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Enums GistOrderField = GraphQL::EnumType.define do name "GistOrderField" description "Properties by which gist connections can be ordered." value "CREATED_AT", "Order gists by creation time", value: "created_at" value "UPDATED_AT", "Order gists by update time", value: "updated_at" value "PUSHED_AT", "Order gists by push time", value: "pushed_at" end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/gist_order_field.transformed.rb000066400000000000000000000006551414121453000270610ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Enums class GistOrderField < Platform::Enums::Base description "Properties by which gist connections can be ordered." value "CREATED_AT", "Order gists by creation time", value: "created_at" value "UPDATED_AT", "Order gists by update time", value: "updated_at" value "PUSHED_AT", "Order gists by push time", value: "pushed_at" end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/increment_count.original.rb000066400000000000000000000026401414121453000262250ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Mutations IncrementThing = GraphQL::Relay::Mutation.define do name "IncrementThing" description "increments the thing by 1." visibility :internal minimum_accepted_scopes ["repo"] input_field(:thingId, !types.ID, "Thing ID to log.", option: :setting) return_field( :thingId, !types.ID, "Thing ID to log." ) resolve ->(root_obj, inputs, context) do if some_early_check return { thingId: "000" } end # These shouldn't be modified: { abcDef: 1 } some_method do { xyzAbc: 1 } end thing = Platform::Helpers::NodeIdentification.typed_object_from_id(Objects::Thing, inputs[:thingId], context) raise Errors::Validation.new("Thing not found.") unless thing ThingActivity.track(thing.id, Time.now.change(min: 0, sec: 0)) if random_condition { thingId: thing.global_relay_id } elsif other_random_thing { :thingId => "abc" } elsif something_else method_with_block { { thingId: "pqr" } } elsif yet_another_thing begin { thingId: "987" } rescue { thingId: "789" } end else return { thingId: "xyz" } end end end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/increment_count.transformed.rb000066400000000000000000000025001414121453000267400ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Mutations class IncrementThing < Mutations::BaseMutation description "increments the thing by 1." visibility :internal minimum_accepted_scopes ["repo"] argument :thing_id, ID, "Thing ID to log.", option: :setting, required: true field :thing_id, ID, "Thing ID to log.", null: false def resolve(**inputs) if some_early_check return { thing_id: "000" } end # These shouldn't be modified: { abcDef: 1 } some_method do { xyzAbc: 1 } end thing = Platform::Helpers::NodeIdentification.typed_object_from_id(Objects::Thing, inputs[:thing_id], context) raise Errors::Validation.new("Thing not found.") unless thing ThingActivity.track(thing.id, Time.now.change(min: 0, sec: 0)) if random_condition { thing_id: thing.global_relay_id } elsif other_random_thing { :thing_id => "abc" } elsif something_else method_with_block { { thing_id: "pqr" } } elsif yet_another_thing begin { thing_id: "987" } rescue { thing_id: "789" } end else return { thing_id: "xyz" } end end end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/mutation.original.rb000066400000000000000000000010541414121453000246670ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Mutations Echo = GraphQL::Relay::Mutation.define do name 'EchoMutation' input_field :message, types.String field :data, types.String resolve ->(_obj, inputs, _ctx) { { data: inputs[:message] } } end Repeat = GraphQL::Relay::Mutation.define do name 'RepeatMutation' input_field :message, types.String field :data, types.String resolve ->(_obj, inputs, _ctx) { { data: inputs[:message] } } end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/mutation.transformed.rb000066400000000000000000000010741414121453000254110ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Mutations class Echo < Mutations::BaseMutation graphql_name 'EchoMutation' argument :message, String, required: false field :data, String, null: true def resolve(**inputs) { data: inputs[:message] } end end class Repeat < Mutations::BaseMutation graphql_name 'RepeatMutation' argument :message, String, required: false field :data, String, null: true def resolve(**inputs) { data: inputs[:message] } end end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/photo.original.rb000066400000000000000000000003421414121453000241570ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Objects Photo = GraphQL::ObjectType.define do field(:caption, types.String) do resolve(->(obj, _args, _ctx) { obj.caption }) end end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/photo.transformed.rb000066400000000000000000000003301414121453000246740ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Objects class Photo < Platform::Objects::Base field :caption, String, null: true def caption object.caption end end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/release_order.original.rb000066400000000000000000000007471414121453000256520ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Inputs ReleaseOrder = GraphQL::InputObjectType.define do name "ReleaseOrder" description "Ways in which lists of releases can be ordered upon return." input_field :field, types[!Enums::ReleaseOrderField], <<-MD The field in which to order releases by. MD input_field :direction, !Enums::OrderDirection, "The direction in which to order releases by the specified field." end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/release_order.transformed.rb000066400000000000000000000007331414121453000263650ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Inputs class ReleaseOrder < Platform::Inputs::Base description "Ways in which lists of releases can be ordered upon return." argument :field, [Enums::ReleaseOrderField], <<-MD, required: false The field in which to order releases by. MD argument :direction, Enums::OrderDirection, "The direction in which to order releases by the specified field.", required: true end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/starrable.original.rb000066400000000000000000000024701414121453000250110ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Interfaces Starrable = GraphQL::InterfaceType.define do name "Starrable" description "Things that can be starred." global_id_field :id field :viewerHasStarred, !types.Boolean do argument :preceedsConnectionMethod, types.Boolean description "Returns a boolean indicating whether the viewing user has starred this starrable." resolve ->(object, arguments, context) do if context[:viewer] ->(test_inner_proc) do context[:viewer].starred?(object) end else false end end end connection :stargazers, -> { !Connections::Stargazer } do description "A list of users who have starred this starrable." argument :orderBy, Inputs::StarOrder, "Order for connection" resolve ->(object, arguments, context) do scope = case object when Repository object.stars when Gist GistStar.where(gist_id: object.id) end table = scope.table_name if order_by = arguments["orderBy"] scope = scope.order("#{table}.#{order_by["field"]} #{order_by["direction"]}") end scope end end end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/starrable.transformed.rb000066400000000000000000000024321414121453000255270ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Interfaces module Starrable include Platform::Interfaces::Base description "Things that can be starred." global_id_field :id field :viewer_has_starred, Boolean, description: "Returns a boolean indicating whether the viewing user has starred this starrable.", null: false do argument :preceeds_connection_method, Boolean, required: false end def viewer_has_starred(**arguments) if context[:viewer] ->(test_inner_proc) do context[:viewer].starred?(object) end else false end end field :stargazers, Connections::Stargazer, description: "A list of users who have starred this starrable.", null: false, connection: true do argument :order_by, Inputs::StarOrder, "Order for connection", required: false end def stargazers(**arguments) scope = case object when Repository object.stars when Gist GistStar.where(gist_id: object.id) end table = scope.table_name if order_by = arguments[:order_by] scope = scope.order("#{table}.#{order_by["field"]} #{order_by["direction"]}") end scope end end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/subscribable.original.rb000066400000000000000000000041111414121453000254640ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Interfaces Subscribable = GraphQL::InterfaceType.define do name "Subscribable" description "Entities that can be subscribed to for web and email notifications." field :id, !GraphQL::ID_TYPE, property: :global_relay_id field :viewerSubscription, -> { !Enums::SubscriptionState } do description "Identifies if the viewer is watching, not watching, or ignoring the subscribable entity." resolve ->(subscribable, arguments, context) do if context[:viewer].nil? return "unsubscribed" end subscription_status_response = subscribable.async_subscription_status(context[:viewer]).sync if subscription_status_response.failed? error = Platform::Errors::ServiceUnavailable.new("Subscriptions are currently unavailable. Please try again later.") error.ast_node = context.irep_node.ast_node error.path = context.path context.errors << error return "unavailable" end subscription = subscription_status_response.value if subscription.included? "unsubscribed" elsif subscription.subscribed? "subscribed" elsif subscription.ignored? "ignored" end end end field :viewerCanSubscribe, !types.Boolean do description "Check if the viewer is able to change their subscription status for the repository." resolve ->(subscribable, arguments, context) do return false if context[:viewer].nil? subscribable.async_subscription_status(context[:viewer]).then(&:success?) end end connection :issues, function: Platform::Functions::Issues.new, description: "A list of issues associated with the milestone." connection :files, -> { !Connections.define(PackageFile) }, description: "List of files associated with this registry package version" field :enabled, !types.Boolean, "Whether enabled for this project", property: :enabled? end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/subscribable.transformed.rb000066400000000000000000000037441414121453000262170ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Interfaces module Subscribable include Platform::Interfaces::Base description "Entities that can be subscribed to for web and email notifications." field :id, GraphQL::ID_TYPE, method: :global_relay_id, null: false field :viewer_subscription, Enums::SubscriptionState, description: "Identifies if the viewer is watching, not watching, or ignoring the subscribable entity.", null: false def viewer_subscription if context[:viewer].nil? return "unsubscribed" end subscription_status_response = object.async_subscription_status(context[:viewer]).sync if subscription_status_response.failed? error = Platform::Errors::ServiceUnavailable.new("Subscriptions are currently unavailable. Please try again later.") error.ast_node = context.irep_node.ast_node error.path = context.path context.errors << error return "unavailable" end subscription = subscription_status_response.value if subscription.included? "unsubscribed" elsif subscription.subscribed? "subscribed" elsif subscription.ignored? "ignored" end end field :viewer_can_subscribe, Boolean, description: "Check if the viewer is able to change their subscription status for the repository.", null: false def viewer_can_subscribe return false if context[:viewer].nil? object.async_subscription_status(context[:viewer]).then(&:success?) end field :issues, function: Platform::Functions::Issues.new, description: "A list of issues associated with the milestone.", connection: true field :files, Connections.define(PackageFile), description: "List of files associated with this registry package version", null: false, connection: true field :enabled, Boolean, "Whether enabled for this project", method: :enabled?, null: false end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/type_x.original.rb000066400000000000000000000031541414121453000243420ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Objects X = define_active_record_type(-> { ::X }) do name "X" description "An x on a y." visibility :internal minimum_accepted_scopes ["z"] global_id_field :id interfaces [GraphQL::Relay::Node.interface] field :f1, !Objects::O1, "The x being y." field :f2, !Enums::E1, "x for the y.", property: :field_2 field :f3, Enums::E2, "x for y." field :details, types.String, "Details." field :f4, !Objects::O2, "x as a y inside the z." do argument :a1, !Inputs::I1 resolve ->(obj_x, arguments, context) do Class1.new( a: Class2.new( b: obj_x.b_1, c: obj_x.c_1 ), d: Class3.new( b: obj_x.b_2, c: obj_x.c_3, ) ) end end field :f5, -> { !types.String } do description "The thing" property :custom_property visibility :custom_value end field :f6, -> { !types.String } do description "The thing" property :custom_property visibility :custom_value end field :f7, field: SomeField field :f8, function: SomeFunction field :f9, types[Objects::O2] field :fieldField, types.String, hash_key: "fieldField" field :fieldField2, types.String, property: :field_field2 field :f10, types.String do resolve ->(obj, _, _) do obj.something do |_| xyz_obj.obj obj.f10 end end end end end end graphql-ruby-1.11.10/spec/fixtures/upgrader/type_x.transformed.rb000066400000000000000000000030301414121453000250530ustar00rootroot00000000000000# frozen_string_literal: true module Platform module Objects class X < Platform::Objects::Base model_name "X" description "An x on a y." visibility :internal minimum_accepted_scopes ["z"] global_id_field :id implements GraphQL::Relay::Node.interface field :f1, Objects::O1, "The x being y.", null: false field :f2, Enums::E1, "x for the y.", method: :field_2, null: false field :f3, Enums::E2, "x for y.", null: true field :details, String, "Details.", null: true field :f4, Objects::O2, "x as a y inside the z.", null: false do argument :a1, Inputs::I1, required: true end def f4(**arguments) Class1.new( a: Class2.new( b: object.b_1, c: object.c_1 ), d: Class3.new( b: object.b_2, c: object.c_3, ) ) end field :f5, String, visibility: :custom_value, method: :custom_property, description: "The thing", null: false field :f6, String, visibility: :custom_value, method: :custom_property, description: "The thing", null: false field :f7, field: SomeField field :f8, function: SomeFunction field :f9, [Objects::O2, null: true], null: true field :field_field, String, hash_key: "fieldField", null: true field :field_field2, String, null: true field :f10, String, null: true def f10 object.something do |_| xyz_obj.obj object.f10 end end end end end graphql-ruby-1.11.10/spec/graphql/000077500000000000000000000000001414121453000166535ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/graphql/analysis/000077500000000000000000000000001414121453000204765ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/graphql/analysis/analyze_query_spec.rb000066400000000000000000000166621414121453000247400ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Analysis do class TypeCollector def initial_value(query) [] end def call(memo, visit_type, irep_node) if visit_type == :enter memo + [irep_node.return_type.unwrap] else memo end end end class ConditionalAnalyzer def analyze?(query) !!query.context[:analyze] end def initial_value(query) {} end def call(memo, visit_type, irep_node) memo[:i_have_been_called] ||= true memo end end let(:schema) { Class.new(Dummy::Schema) } describe ".analyze_query" do let(:node_counter) { ->(memo, visit_type, irep_node) { memo ||= Hash.new { |h,k| h[k] = 0 } visit_type == :enter && memo[irep_node.ast_node.class] += 1 memo } } let(:type_collector) { TypeCollector.new } let(:analyzers) { [type_collector, node_counter] } let(:reduce_result) { GraphQL::Analysis.analyze_query(query, analyzers) } let(:variables) { {} } let(:query) { GraphQL::Query.new(schema.graphql_definition, query_string, variables: variables) } let(:query_string) {%| { cheese(id: 1) { id flavor } } |} describe "conditional analysis" do let(:conditional_analyzer) { ConditionalAnalyzer.new } let(:analyzers) { [type_collector, conditional_analyzer] } describe "when analyze? returns false" do let(:query) { GraphQL::Query.new(schema.graphql_definition, query_string, variables: variables, context: { analyze: false }) } it "does not run the analyzer" do # Only type_collector ran assert_equal 1, reduce_result.size end end describe "when analyze? returns true" do let(:query) { GraphQL::Query.new(schema.graphql_definition, query_string, variables: variables, context: { analyze: true }) } it "it runs the analyzer" do # Both analyzers ran assert_equal 2, reduce_result.size end end end it "calls the defined analyzers" do collected_types, node_counts = reduce_result expected_visited_types = [Dummy::DairyAppQuery.graphql_definition, Dummy::Cheese.graphql_definition, GraphQL::INT_TYPE, GraphQL::STRING_TYPE] assert_equal expected_visited_types, collected_types expected_node_counts = { GraphQL::Language::Nodes::OperationDefinition => 1, GraphQL::Language::Nodes::Field => 3, } assert_equal expected_node_counts, node_counts end describe "tracing" do let(:query_string) { "{ t: __typename }"} it "emits traces" do traces = TestTracing.with_trace do ctx = { tracers: [TestTracing] } schema.execute(query_string, context: ctx) end # The query_trace is on the list _first_ because it finished first _lex, _parse, _validate, query_trace, multiplex_trace, *_rest = traces assert_equal "analyze_multiplex", multiplex_trace[:key] assert_instance_of GraphQL::Execution::Multiplex, multiplex_trace[:multiplex] assert_equal "analyze_query", query_trace[:key] assert_instance_of GraphQL::Query, query_trace[:query] end end describe "when a variable is missing" do let(:query_string) {%| query something($cheeseId: Int!){ cheese(id: $cheeseId) { id flavor } } |} let(:variable_accessor) { ->(memo, visit_type, irep_node) { query.variables["cheeseId"] } } before do schema.query_analyzer(variable_accessor) end it "returns an error" do error = query.result["errors"].first assert_equal "Variable $cheeseId of type Int! was provided invalid value", error["message"] end end describe "when processing fields" do let(:connection_counter) { ->(memo, visit_type, irep_node) { memo ||= Hash.new { |h,k| h[k] = 0 } if visit_type == :enter if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field) if irep_node.definition.connection? memo[:connection] ||= 0 memo[:connection] += 1 else memo[:field] ||= 0 memo[:field] += 1 end end end memo } } let(:analyzers) { [connection_counter] } let(:reduce_result) { GraphQL::Analysis.analyze_query(query, analyzers) } let(:query) { GraphQL::Query.new(StarWars::Schema.graphql_definition, query_string, variables: variables) } let(:query_string) {%| query getBases { empire { basesByName(first: 30) { edges { cursor } } bases(first: 30) { edges { cursor } } } } |} it "knows which fields are connections" do connection_counts = reduce_result.first expected_connection_counts = { :field => 5, :connection => 2 } assert_equal expected_connection_counts, connection_counts end end end describe ".visit_analyzers" do class IdCatcher def call(memo, visit_type, irep_node) if visit_type == :enter if irep_node.ast_node.name == "id" raise GraphQL::AnalysisError.new("Don't use the id field.", ast_node: irep_node.ast_node) end end memo end end class FlavorCatcher def initial_value(query) { :errors => [] } end def call(memo, visit_type, irep_node) if visit_type == :enter if irep_node.ast_node.name == "flavor" memo[:errors] << GraphQL::AnalysisError.new("Don't use the flavor field.", ast_node: irep_node.ast_node) end end memo end def final_value(memo) memo[:errors] end end let(:id_catcher) { IdCatcher.new } let(:flavor_catcher) { FlavorCatcher.new } let(:analyzers) { [id_catcher, flavor_catcher] } let(:reduce_result) { GraphQL::Analysis.analyze_query(query, analyzers) } let(:query) { GraphQL::Query.new(schema, query_string) } let(:query_string) {%| { cheese(id: 1) { id flavor } } |} let(:schema) do schema = Class.new(Dummy::Schema) do self.analysis_engine = GraphQL::Analysis end schema.query_analyzer(id_catcher) schema.query_analyzer(flavor_catcher) schema.graphql_definition end let(:result) { schema.execute(query_string) } let(:query_string) {%| { cheese(id: 1) { id flavor } } |} it "groups all errors together" do data = result["data"] errors = result["errors"] id_error_hash = errors[0] flavor_error_hash = errors[1] id_error_response = { "message"=>"Don't use the id field.", "locations"=>[{"line"=>4, "column"=>11}] } flavor_error_response = { "message"=>"Don't use the flavor field.", "locations"=>[{"line"=>5, "column"=>11}] } assert_nil data assert_equal id_error_response["message"], id_error_hash["message"] assert_equal id_error_response["locations"], id_error_hash["locations"] assert_equal flavor_error_response["message"], flavor_error_hash["message"] assert_equal flavor_error_response["locations"], flavor_error_hash["locations"] end end end graphql-ruby-1.11.10/spec/graphql/analysis/ast/000077500000000000000000000000001414121453000212655ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/graphql/analysis/ast/field_usage_spec.rb000066400000000000000000000046201414121453000250750ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Analysis::AST::FieldUsage do let(:result) { GraphQL::Analysis::AST.analyze_query(query, [GraphQL::Analysis::AST::FieldUsage]).first } let(:query) { GraphQL::Query.new(Dummy::Schema, query_string, variables: variables) } let(:variables) { {} } describe "query with deprecated fields" do let(:query_string) {%| query { cheese(id: 1) { id fatContent } } |} it "keeps track of used fields" do assert_equal ['Cheese.id', 'Cheese.fatContent', 'Query.cheese'], result[:used_fields] end it "keeps track of deprecated fields" do assert_equal ['Cheese.fatContent'], result[:used_deprecated_fields] end end describe "query with deprecated fields used more than once" do let(:query_string) {%| query { cheese1: cheese(id: 1) { id fatContent } cheese2: cheese(id: 2) { id fatContent } } |} it "omits duplicate usage of a field" do assert_equal ['Cheese.id', 'Cheese.fatContent', 'Query.cheese'], result[:used_fields] end it "omits duplicate usage of a deprecated field" do assert_equal ['Cheese.fatContent'], result[:used_deprecated_fields] end end describe "query with deprecated fields in a fragment" do let(:query_string) {%| query { cheese(id: 1) { id ...CheeseSelections } } fragment CheeseSelections on Cheese { fatContent } |} it "keeps track of fields used in the fragment" do assert_equal ['Cheese.id', 'Cheese.fatContent', 'Query.cheese'], result[:used_fields] end it "keeps track of deprecated fields used in the fragment" do assert_equal ['Cheese.fatContent'], result[:used_deprecated_fields] end end describe "query with deprecated fields in an inline fragment" do let(:query_string) {%| query { cheese(id: 1) { id ... on Cheese { fatContent } } } |} it "keeps track of fields used in the fragment" do assert_equal ['Cheese.id', 'Cheese.fatContent', 'Query.cheese'], result[:used_fields] end it "keeps track of deprecated fields used in the fragment" do assert_equal ['Cheese.fatContent'], result[:used_deprecated_fields] end end end graphql-ruby-1.11.10/spec/graphql/analysis/ast/max_query_complexity_spec.rb000066400000000000000000000073771414121453000271310ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Analysis::AST::MaxQueryComplexity do let(:schema) { Class.new(Dummy::Schema) } let(:query_string) {%| { a: cheese(id: 1) { id } b: cheese(id: 1) { id } c: cheese(id: 1) { id } d: cheese(id: 1) { id } e: cheese(id: 1) { id } } |} let(:query) { GraphQL::Query.new(schema, query_string, variables: {}, max_complexity: max_complexity) } let(:result) { GraphQL::Analysis::AST.analyze_query(query, [GraphQL::Analysis::AST::MaxQueryComplexity]).first } describe "when a query goes over max complexity" do let(:max_complexity) { 9 } it "returns an error" do assert_equal GraphQL::AnalysisError, result.class assert_equal "Query has complexity of 10, which exceeds max complexity of 9", result.message end end describe "when there is no max complexity" do let(:max_complexity) { nil } it "doesn't error" do assert_nil result end end describe "when the query is less than the max complexity" do let(:max_complexity) { 99 } it "doesn't error" do assert_nil result end end describe "when max_complexity is decreased at query-level" do before do schema.max_complexity(100) end let(:max_complexity) { 7 } it "is applied" do assert_equal GraphQL::AnalysisError, result.class assert_equal "Query has complexity of 10, which exceeds max complexity of 7", result.message end end describe "when max_complexity is increased at query-level" do before do schema.max_complexity(1) end let(:max_complexity) { 10 } it "doesn't error" do assert_nil result end end describe "when max_complexity is nil at query-level" do let(:max_complexity) { nil } before do schema.max_complexity(1) end it "is applied" do assert_nil result end end describe "when used with the max_depth plugin" do let(:schema) do Class.new(GraphQL::Schema) do query Dummy::DairyAppQuery max_depth 3 max_complexity 1 use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST end end let(:query_string) {%| { a: cheese(id: 1) { ...cheeseFields } b: cheese(id: 1) { ...cheeseFields } c: cheese(id: 1) { ...cheeseFields } d: cheese(id: 1) { ...cheeseFields } e: cheese(id: 1) { ...cheeseFields } } fragment cheeseFields on Cheese { id } |} let(:result) { schema.execute(query_string) } it "returns a complexity error" do assert_equal "Query has complexity of 10, which exceeds max complexity of 1", result["errors"].first["message"] end end describe "across a multiplex" do before do schema.analysis_engine = GraphQL::Analysis::AST end let(:queries) { 5.times.map { |n| GraphQL::Query.new(schema, "{ cheese(id: #{n}) { id } }", variables: {}) } } let(:max_complexity) { 9 } let(:multiplex) { GraphQL::Execution::Multiplex.new(schema: schema, queries: queries, context: {}, max_complexity: max_complexity) } let(:analyze_multiplex) { GraphQL::Analysis::AST.analyze_multiplex(multiplex, [GraphQL::Analysis::AST::MaxQueryComplexity]) } it "returns errors for all queries" do analyze_multiplex err_msg = "Query has complexity of 10, which exceeds max complexity of 9" queries.each do |query| assert_equal err_msg, query.analysis_errors[0].message end end describe "with a local override" do let(:max_complexity) { 10 } it "uses the override" do analyze_multiplex queries.each do |query| assert query.analysis_errors.empty? end end end end end graphql-ruby-1.11.10/spec/graphql/analysis/ast/max_query_depth_spec.rb000066400000000000000000000073161414121453000260310ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Analysis::AST::MaxQueryDepth do let(:schema) { schema = Class.new(Dummy::Schema) schema.analysis_engine = GraphQL::Analysis::AST schema } let(:query_string) { " { cheese(id: 1) { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { id } } } } } } } "} let(:max_depth) { nil } let(:query) { # Don't override `schema.max_depth` with `nil` options = max_depth ? { max_depth: max_depth } : {} GraphQL::Query.new( schema, query_string, variables: {}, **options ) } let(:result) { GraphQL::Analysis::AST.analyze_query(query, [GraphQL::Analysis::AST::MaxQueryDepth]).first } let(:multiplex) { GraphQL::Execution::Multiplex.new( schema: schema, queries: [query.dup, query.dup], context: {}, max_complexity: nil ) } let(:multiplex_result) { GraphQL::Analysis::AST.analyze_multiplex(multiplex, [GraphQL::Analysis::AST::MaxQueryDepth]).first } describe "when the query is deeper than max depth" do let(:max_depth) { 5 } it "adds an error message for a too-deep query" do assert_equal "Query has depth of 7, which exceeds max depth of 5", result.message end end describe "when a multiplex queries is deeper than max depth" do before do schema.max_depth = 5 end it "adds an error message for a too-deep query on from multiplex analyzer" do assert_equal "Query has depth of 7, which exceeds max depth of 5", multiplex_result.message end end describe "when the query specifies a different max_depth" do let(:max_depth) { 100 } it "obeys that max_depth" do assert_nil result end end describe "When the query is not deeper than max_depth" do before do schema.max_depth = 100 end it "doesn't add an error" do assert_nil result end end describe "when the max depth isn't set" do before do schema.max_depth = nil end it "doesn't add an error message" do assert_nil result end end describe "when a fragment exceeds max depth" do before do schema.max_depth = 4 end let(:query_string) { " { cheese(id: 1) { ...moreFields } } fragment moreFields on Cheese { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { ...evenMoreFields } } } } fragment evenMoreFields on Cheese { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { id } } } "} it "adds an error message for a too-deep query" do assert_equal "Query has depth of 7, which exceeds max depth of 4", result.message end end describe "when the query would cause a stack error" do let(:query_string) { str = "query { cheese(id: 1) { ".dup n = 10_000 n.times { str << "similarCheese(source: SHEEP) { " } str << "id " n.times { str << "} " } str << "} }" str } it "returns an error" do assert_equal ["This query is too large to execute."], query.result["errors"].map { |err| err["message"] } # Make sure `Schema.execute` works too execute_result = schema.execute(query_string) assert_equal ["This query is too large to execute."], execute_result["errors"].map { |err| err["message"] } end end end graphql-ruby-1.11.10/spec/graphql/analysis/ast/query_complexity_spec.rb000066400000000000000000000224631414121453000262550ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Analysis::AST::QueryComplexity do let(:schema) { Dummy::Schema } let(:reduce_result) { GraphQL::Analysis::AST.analyze_query(query, [GraphQL::Analysis::AST::QueryComplexity]) } let(:reduce_multiplex_result) { GraphQL::Analysis::AST.analyze_multiplex(multiplex, [GraphQL::Analysis::AST::QueryComplexity]) } let(:variables) { {} } let(:query) { GraphQL::Query.new(schema, query_string, variables: variables) } let(:multiplex) { GraphQL::Execution::Multiplex.new( schema: schema, queries: [query.dup, query.dup], context: {}, max_complexity: 10 ) } describe "simple queries" do let(:query_string) {%| query cheeses($isSkipped: Boolean = false){ # complexity of 3 cheese1: cheese(id: 1) { id flavor } # complexity of 4 cheese2: cheese(id: 2) @skip(if: $isSkipped) { similarCheese(source: SHEEP) { ... on Cheese { similarCheese(source: SHEEP) { id } } } } } |} it "sums the complexity" do complexities = reduce_result.first assert_equal 7, complexities end describe "when skipped by directives" do let(:variables) { { "isSkipped" => true } } it "doesn't include skipped fields" do complexity = reduce_result.first assert_equal 3, complexity end end end describe "query with fragments" do let(:query_string) {%| { # complexity of 3 cheese1: cheese(id: 1) { id flavor } # complexity of 7 cheese2: cheese(id: 2) { ... cheeseFields1 ... cheeseFields2 } } fragment cheeseFields1 on Cheese { similarCow: similarCheese(source: COW) { id ... cheeseFields2 } } fragment cheeseFields2 on Cheese { similarSheep: similarCheese(source: SHEEP) { id } } |} it "counts all fragment usages, not the definitions" do complexity = reduce_result.first assert_equal 10, complexity end describe "mutually exclusive types" do let(:query_string) {%| { favoriteEdible { # 1 for everybody fatContent # 1 for everybody ... on Edible { origin } # 1 for honey, aspartame ... on Sweetener { sweetness } # 2 for milk ... milkFields # 1 for cheese ... cheeseFields # 1 for honey ... honeyFields # 1 for milk + cheese ... dairyProductFields } } fragment milkFields on Milk { id source } fragment cheeseFields on Cheese { source } fragment honeyFields on Honey { flowerType } fragment dairyProductFields on DairyProduct { ... on Cheese { flavor } ... on Milk { flavors } } |} it "gets the max among options" do complexity = reduce_result.first assert_equal 6, complexity end end describe "when there are no selections on any object types" do let(:query_string) {%| { # 1 for everybody favoriteEdible { # 1 for everybody fatContent # 1 for everybody ... on Edible { origin } # 1 for honey, aspartame ... on Sweetener { sweetness } } } |} it "gets the max among interface types" do complexity = reduce_result.first assert_equal 4, complexity end end describe "redundant fields" do let(:query_string) {%| { favoriteEdible { fatContent # this is executed separately and counts separately: aliasedFatContent: fatContent ... on Edible { fatContent } ... edibleFields } } fragment edibleFields on Edible { fatContent } |} it "only counts them once" do complexity = reduce_result.first assert_equal 3, complexity end end describe "redundant fields not within a fragment" do let(:query_string) {%| { cheese { id } cheese { id } } |} it "only counts them once" do complexity = reduce_result.first assert_equal 2, complexity end end end describe "relay types" do let(:query) { GraphQL::Query.new(StarWars::Schema, query_string) } let(:query_string) {%| { rebels { ships { edges { node { id } } pageInfo { hasNextPage } } } } |} it "gets the complexity" do complexity = reduce_result.first assert_equal 7, complexity end end describe "calucation complexity for a multiplex" do let(:query_string) {%| query cheeses { cheese(id: 1) { id flavor source } } |} it "sums complexity for both queries" do complexity = reduce_multiplex_result.first assert_equal 8, complexity end describe "abstract type" do let(:query_string) {%| query Edible { allEdible { origin fatContent } } |} it "sums complexity for both queries" do complexity = reduce_multiplex_result.first assert_equal 6, complexity end end end describe "custom complexities" do class CustomComplexitySchema < GraphQL::Schema module ComplexityInterface include GraphQL::Schema::Interface field :value, Int, null: true end class SingleComplexity < GraphQL::Schema::Object field :value, Int, null: true, complexity: 0.1 field :complexity, SingleComplexity, null: true do argument :int_value, Int, required: false complexity(->(ctx, args, child_complexity) { args[:int_value] + child_complexity }) end implements ComplexityInterface end class DoubleComplexity < GraphQL::Schema::Object field :value, Int, null: true, complexity: 4 implements ComplexityInterface end class Query < GraphQL::Schema::Object field :complexity, SingleComplexity, null: true do argument :int_value, Int, required: false complexity ->(ctx, args, child_complexity) { args[:int_value] + child_complexity } end field :inner_complexity, ComplexityInterface, null: true do argument :value, Int, required: false end end query(Query) orphan_types(DoubleComplexity) use(GraphQL::Execution::Interpreter) use(GraphQL::Analysis::AST) end let(:query) { GraphQL::Query.new(complexity_schema, query_string) } let(:complexity_schema) { CustomComplexitySchema } let(:query_string) {%| { a: complexity(intValue: 3) { value } b: complexity(intValue: 6) { value complexity(intValue: 1) { value } } } |} it "sums the complexity" do complexity = reduce_result.first # 10 from `complexity`, `0.3` from `value` assert_equal 10.3, complexity end describe "same field on multiple types" do let(:query_string) {%| { innerComplexity(intValue: 2) { ... on SingleComplexity { value } ... on DoubleComplexity { value } } } |} it "picks them max for those fields" do complexity = reduce_result.first # 1 for innerComplexity + 4 for DoubleComplexity.value assert_equal 5, complexity end end end describe "field_complexity hook" do class CustomComplexityAnalyzer < GraphQL::Analysis::AST::QueryComplexity def initialize(query) super @field_complexities_by_query = {} end def result super @field_complexities_by_query[@query] end private def field_complexity(scoped_type_complexity, max_complexity:, child_complexity:) @field_complexities_by_query[scoped_type_complexity.query] ||= {} @field_complexities_by_query[scoped_type_complexity.query][scoped_type_complexity.response_path] = { max_complexity: max_complexity, child_complexity: child_complexity, } end end let(:reduce_result) { GraphQL::Analysis::AST.analyze_query(query, [CustomComplexityAnalyzer]) } let(:query_string) {%| { cheese { id } cheese { id flavor } } |} it "gets called for each field with complexity data" do field_complexities = reduce_result.first assert_equal({ ['cheese', 'id'] => { max_complexity: 1, child_complexity: nil }, ['cheese', 'flavor'] => { max_complexity: 1, child_complexity: nil }, ['cheese'] => { max_complexity: 3, child_complexity: 2 }, }, field_complexities) end end end graphql-ruby-1.11.10/spec/graphql/analysis/ast/query_depth_spec.rb000066400000000000000000000044541414121453000251640ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Analysis::AST::QueryDepth do let(:result) { GraphQL::Analysis::AST.analyze_query(query, [GraphQL::Analysis::AST::QueryDepth]) } let(:query) { GraphQL::Query.new(Dummy::Schema, query_string, variables: variables) } let(:variables) { {} } describe "multiple operations" do let(:query_string) {%| query Cheese1 { cheese1: cheese(id: 1) { id flavor } } query Cheese2 { cheese(id: 2) { similarCheese(source: SHEEP) { ... on Cheese { similarCheese(source: SHEEP) { id } } } } } |} let(:query) { GraphQL::Query.new(Dummy::Schema, query_string, variables: variables, operation_name: "Cheese1") } it "analyzes the selected operation only" do depth = result.first assert_equal 2, depth end end describe "simple queries" do let(:query_string) {%| query cheeses($isIncluded: Boolean = true){ # depth of 2 cheese1: cheese(id: 1) { id flavor } # depth of 4 cheese2: cheese(id: 2) @include(if: $isIncluded) { similarCheese(source: SHEEP) { ... on Cheese { similarCheese(source: SHEEP) { id } } } } } |} it "finds the max depth" do depth = result.first assert_equal 4, depth end describe "with directives" do let(:variables) { { "isIncluded" => false } } it "doesn't count skipped fields" do assert_equal 2, result.first end end end describe "query with fragments" do let(:query_string) {%| { # depth of 2 cheese1: cheese(id: 1) { id flavor } # depth of 4 cheese2: cheese(id: 2) { ... cheeseFields1 } } fragment cheeseFields1 on Cheese { similarCheese(source: COW) { id ... cheeseFields2 } } fragment cheeseFields2 on Cheese { similarCheese(source: SHEEP) { id } } |} it "finds the max depth" do assert_equal 4, result.first end end end graphql-ruby-1.11.10/spec/graphql/analysis/ast_spec.rb000066400000000000000000000173521414121453000226340ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Analysis::AST do class AstTypeCollector < GraphQL::Analysis::AST::Analyzer def initialize(query) super @types = [] end def on_enter_operation_definition(node, parent, visitor) @types << visitor.type_definition end def on_enter_field(memo, node, visitor) @types << visitor.field_definition.type.unwrap end def result @types end end class AstNodeCounter < GraphQL::Analysis::AST::Analyzer def initialize(query) super @nodes = Hash.new { |h,k| h[k] = 0 } end def on_enter_abstract_node(node, parent, _visitor) @nodes[node.class] += 1 end def result @nodes end end class AstConditionalAnalyzer < GraphQL::Analysis::AST::Analyzer def initialize(query) super @i_have_been_called = false end def analyze? !!query.context[:analyze] end def on_operation_definition(node, parent, visitor) @i_have_been_called = true end def result @i_have_been_called end end class AstErrorAnalyzer < GraphQL::Analysis::AST::Analyzer def result GraphQL::AnalysisError.new("An Error!") end end class AstPreviousField < GraphQL::Analysis::AST::Analyzer def on_enter_field(node, parent, visitor) @previous_field = visitor.previous_field_definition end def result @previous_field end end class AstArguments < GraphQL::Analysis::AST::Analyzer def on_enter_argument(node, parent, visitor) @argument = visitor.argument_definition @previous_argument = visitor.previous_argument_definition end def result [@argument, @previous_argument] end end describe "using the AST analysis engine" do let(:schema) do query_type = Class.new(GraphQL::Schema::Object) do graphql_name 'Query' field :foobar, Integer, null: false def foobar 1337 end end Class.new(GraphQL::Schema) do query query_type use GraphQL::Analysis::AST query_analyzer AstErrorAnalyzer use GraphQL::Execution::Interpreter end end let(:query_string) {%| query { foobar } |} let(:query) { GraphQL::Query.new(schema, query_string, variables: {}) } it "runs the AST analyzers correctly" do res = query.result refute res.key?("data") assert_equal ["An Error!"], res["errors"].map { |e| e["message"] } end it "skips rewrite" do # Try running the query: query.result # But the validation step doesn't build an irep_node tree assert_nil query.irep_selection end describe "when validate: false" do let(:query) { GraphQL::Query.new(schema, query_string, validate: false) } it "Skips rewrite" do # Try running the query: query.result # But the validation step doesn't build an irep_node tree assert_nil query.irep_selection end end end describe ".analyze_query" do let(:analyzers) { [AstTypeCollector, AstNodeCounter] } let(:reduce_result) { GraphQL::Analysis::AST.analyze_query(query, analyzers) } let(:variables) { {} } let(:query) { GraphQL::Query.new(Dummy::Schema, query_string, variables: variables) } let(:query_string) {%| { cheese(id: 1) { id flavor } } |} describe "without a valid operation" do let(:query_string) {%| # A comment # is an invalid operation # Should break |} it "bails early when there is no selected operation to be executed" do assert_equal 2, reduce_result.size end end describe "conditional analysis" do let(:analyzers) { [AstTypeCollector, AstConditionalAnalyzer] } describe "when analyze? returns false" do let(:query) { GraphQL::Query.new(Dummy::Schema, query_string, variables: variables, context: { analyze: false }) } it "does not run the analyzer" do # Only type_collector ran assert_equal 1, reduce_result.size end end describe "when analyze? returns true" do let(:query) { GraphQL::Query.new(Dummy::Schema, query_string, variables: variables, context: { analyze: true }) } it "it runs the analyzer" do # Both analyzers ran assert_equal 2, reduce_result.size end end describe "Visitor#previous_field_definition" do let(:analyzers) { [AstPreviousField] } let(:query) { GraphQL::Query.new(Dummy::Schema, "{ __schema { types { name } } }") } it "it runs the analyzer" do prev_field = reduce_result.first assert_equal "__Schema.types", prev_field.path end end describe "Visitor#argument_definition" do let(:analyzers) { [AstArguments] } let(:query) do GraphQL::Query.new( Dummy::Schema, '{ searchDairy(product: [{ source: "SHEEP" }]) { ... on Cheese { id } } }' ) end it "it runs the analyzer" do argument, prev_argument = reduce_result.first assert_equal "DairyProductInput.source", argument.path assert_equal "Query.searchDairy.product", prev_argument.path end end end it "calls the defined analyzers" do collected_types, node_counts = reduce_result expected_visited_types = [ Dummy::DairyAppQuery, Dummy::Cheese, GraphQL::Types::Int, GraphQL::Types::String ] assert_equal expected_visited_types, collected_types expected_node_counts = { GraphQL::Language::Nodes::OperationDefinition => 1, GraphQL::Language::Nodes::Field => 3, GraphQL::Language::Nodes::Argument => 1 } assert_equal expected_node_counts, node_counts end describe "tracing" do let(:query_string) { "{ t: __typename }"} it "emits traces" do traces = TestTracing.with_trace do ctx = { tracers: [TestTracing] } Dummy::Schema.execute(query_string, context: ctx) end # The query_trace is on the list _first_ because it finished first _lex, _parse, _validate, query_trace, multiplex_trace, *_rest = traces assert_equal "analyze_multiplex", multiplex_trace[:key] assert_instance_of GraphQL::Execution::Multiplex, multiplex_trace[:multiplex] assert_equal "analyze_query", query_trace[:key] assert_instance_of GraphQL::Query, query_trace[:query] end end class AstConnectionCounter < GraphQL::Analysis::AST::Analyzer def initialize(query) super @fields = 0 @connections = 0 end def on_enter_field(node, parent, visitor) if visitor.field_definition.connection? @connections += 1 else @fields += 1 end end def result { fields: @fields, connections: @connections } end end describe "when processing fields" do let(:analyzers) { [AstConnectionCounter] } let(:reduce_result) { GraphQL::Analysis::AST.analyze_query(query, analyzers) } let(:query) { GraphQL::Query.new(StarWars::Schema, query_string, variables: variables) } let(:query_string) {%| query getBases { empire { basesByName(first: 30) { edges { cursor } } bases(first: 30) { edges { cursor } } } } |} it "knows which fields are connections" do connection_counts = reduce_result.first expected_connection_counts = { :fields => 5, :connections => 2 } assert_equal expected_connection_counts, connection_counts end end end end graphql-ruby-1.11.10/spec/graphql/analysis/field_usage_spec.rb000066400000000000000000000031621414121453000243060ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Analysis::FieldUsage do let(:result) { [] } let(:field_usage_analyzer) { GraphQL::Analysis::FieldUsage.new { |query, used_fields, used_deprecated_fields| result << query << used_fields << used_deprecated_fields } } let(:reduce_result) { GraphQL::Analysis.analyze_query(query, [field_usage_analyzer]) } let(:query) { GraphQL::Query.new(Dummy::Schema.graphql_definition, query_string, variables: variables) } let(:variables) { {} } describe "query with deprecated fields" do let(:query_string) {%| query { cheese(id: 1) { id fatContent } } |} it "returns query in reduced result" do reduce_result assert_equal query, result[0] end it "keeps track of used fields" do reduce_result assert_equal ['Cheese.id', 'Cheese.fatContent', 'Query.cheese'], result[1] end it "keeps track of deprecated fields" do reduce_result assert_equal ['Cheese.fatContent'], result[2] end end describe "query with deprecated fields used more than once" do let(:query_string) {%| query { cheese1: cheese(id: 1) { id fatContent } cheese2: cheese(id: 2) { id fatContent } } |} it "omits duplicate usage of a field" do reduce_result assert_equal ['Cheese.id', 'Cheese.fatContent', 'Query.cheese'], result[1] end it "omits duplicate usage of a deprecated field" do reduce_result assert_equal ['Cheese.fatContent'], result[2] end end end graphql-ruby-1.11.10/spec/graphql/analysis/max_query_complexity_spec.rb000066400000000000000000000051251414121453000263270ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Analysis::MaxQueryComplexity do let(:schema) { Class.new(Dummy::Schema) } let(:result) { schema.execute(query_string) } let(:query_string) {%| { a: cheese(id: 1) { id } b: cheese(id: 1) { id } c: cheese(id: 1) { id } d: cheese(id: 1) { id } e: cheese(id: 1) { id } } |} describe "when a query goes over max complexity" do before do schema.max_complexity(9) end it "returns an error" do assert_equal "Query has complexity of 10, which exceeds max complexity of 9", result["errors"][0]["message"] end end describe "when there is no max complexity" do it "doesn't error" do assert_nil result["errors"] end end describe "when the query is less than the max complexity" do before do schema.max_complexity(99) end it "doesn't error" do assert_nil result["errors"] end end describe "when max_complexity is decreased at query-level" do before do schema.max_complexity(100) end let(:result) {schema.execute(query_string, max_complexity: 7) } it "is applied" do assert_equal "Query has complexity of 10, which exceeds max complexity of 7", result["errors"][0]["message"] end end describe "when max_complexity is increased at query-level" do before do schema.max_complexity(1) end let(:result) {schema.execute(query_string, max_complexity: 10) } it "doesn't error" do assert_nil result["errors"] end end describe "when max_complexity is nil query-level" do before do schema.max_complexity(1) end let(:result) {schema.execute(query_string, max_complexity: nil) } it "is applied" do assert_nil result["errors"] end end describe "across a multiplex" do before do schema.max_complexity(9) end let(:queries) { 5.times.map { |n| { query: "{ cheese(id: #{n}) { id } }" } } } it "returns errors for all queries" do results = schema.multiplex(queries) assert_equal 5, results.length err_msg = "Query has complexity of 10, which exceeds max complexity of 9" results.each do |res| assert_equal err_msg, res["errors"][0]["message"] end end describe "with a local override" do it "uses the override" do results = schema.multiplex(queries, max_complexity: 10) assert_equal 5, results.length results.each do |res| assert_equal true, res.key?("data") assert_equal false, res.key?("errors") end end end end end graphql-ruby-1.11.10/spec/graphql/analysis/max_query_depth_spec.rb000066400000000000000000000046331414121453000252410ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Analysis::MaxQueryDepth do let(:schema) { Class.new(Dummy::Schema) } let(:result) { schema.execute(query_string) } let(:query_string) { " { cheese(id: 1) { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { id } } } } } } } "} describe "when the query is deeper than max depth" do it "adds an error message for a too-deep query" do assert_equal "Query has depth of 7, which exceeds max depth of 5", result["errors"][0]["message"] end end describe "when the query specifies a different max_depth" do let(:result) { schema.execute(query_string, max_depth: 100) } it "obeys that max_depth" do assert_nil result["errors"] end end describe "when the query specifies a nil max_depth" do let(:result) { schema.execute(query_string, max_depth: nil) } it "obeys that max_depth" do assert_nil result["errors"] end end describe "When the query is not deeper than max_depth" do before do schema.max_depth(100) end it "doesn't add an error" do assert_nil result["errors"] end end describe "when the max depth isn't set" do before do # Yuck - Can't override GraphQL::Schema.max_depth to return nil if it has already been set schema.define_singleton_method(:max_depth) { |*| nil } end it "doesn't add an error message" do assert_nil result["errors"] end end describe "when a fragment exceeds max depth" do before do schema.max_depth(4) end let(:query_string) { " { cheese(id: 1) { ...moreFields } } fragment moreFields on Cheese { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { ...evenMoreFields } } } } fragment evenMoreFields on Cheese { similarCheese(source: SHEEP) { similarCheese(source: SHEEP) { id } } } "} it "adds an error message for a too-deep query" do assert_equal 1, result["errors"].length end end end graphql-ruby-1.11.10/spec/graphql/analysis/query_complexity_spec.rb000066400000000000000000000160071414121453000254630ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Analysis::QueryComplexity do let(:complexities) { [] } let(:query_complexity) { GraphQL::Analysis::QueryComplexity.new { |this_query, complexity| complexities << this_query << complexity } } let(:reduce_result) { GraphQL::Analysis.analyze_query(query, [query_complexity]) } let(:variables) { {} } let(:query) { GraphQL::Query.new(Dummy::Schema.graphql_definition, query_string, variables: variables) } describe "simple queries" do let(:query_string) {%| query cheeses($isSkipped: Boolean = false){ # complexity of 3 cheese1: cheese(id: 1) { id flavor } # complexity of 4 cheese2: cheese(id: 2) @skip(if: $isSkipped) { similarCheese(source: SHEEP) { ... on Cheese { similarCheese(source: SHEEP) { id } } } } } |} it "sums the complexity" do reduce_result assert_equal complexities, [query, 7] end describe "when skipped by directives" do let(:variables) { { "isSkipped" => true } } it "doesn't include skipped fields" do reduce_result assert_equal complexities, [query, 3] end end end describe "query with fragments" do let(:query_string) {%| { # complexity of 3 cheese1: cheese(id: 1) { id flavor } # complexity of 7 cheese2: cheese(id: 2) { ... cheeseFields1 ... cheeseFields2 } } fragment cheeseFields1 on Cheese { similarCow: similarCheese(source: COW) { id ... cheeseFields2 } } fragment cheeseFields2 on Cheese { similarSheep: similarCheese(source: SHEEP) { id } } |} it "counts all fragment usages, not the definitions" do reduce_result assert_equal complexities, [query, 10] end describe "mutually exclusive types" do let(:query_string) {%| { favoriteEdible { # 1 for everybody fatContent # 1 for everybody ... on Edible { origin } # 1 for honey ... on Sweetener { sweetness } # 2 for milk ... milkFields # 1 for cheese ... cheeseFields # 1 for honey ... honeyFields # 1 for milk + cheese ... dairyProductFields } } fragment milkFields on Milk { id source } fragment cheeseFields on Cheese { source } fragment honeyFields on Honey { flowerType } fragment dairyProductFields on DairyProduct { ... on Cheese { flavor } ... on Milk { flavors } } |} it "gets the max among options" do reduce_result assert_equal 6, complexities.last end end describe "when there are no selections on any object types" do let(:query_string) {%| { # 1 for everybody favoriteEdible { # 1 for everybody fatContent # 1 for everybody ... on Edible { origin } # 1 for honey ... on Sweetener { sweetness } } } |} it "gets the max among interface types" do reduce_result assert_equal 4, complexities.last end end describe "redundant fields" do let(:query_string) {%| { favoriteEdible { fatContent # this is executed separately and counts separately: aliasedFatContent: fatContent ... on Edible { fatContent } ... edibleFields } } fragment edibleFields on Edible { fatContent } |} it "only counts them once" do reduce_result assert_equal 3, complexities.last end end end describe "relay types" do let(:query) { GraphQL::Query.new(StarWars::Schema.graphql_definition, query_string) } let(:query_string) {%| { rebels { ships { edges { node { id } } pageInfo { hasNextPage } } } } |} it "gets the complexity" do reduce_result assert_equal 7, complexities.last end end describe "custom complexities" do let(:query) { GraphQL::Query.new(complexity_schema, query_string) } let(:complexity_schema) { complexity_interface = GraphQL::InterfaceType.define do name "ComplexityInterface" field :value, types.Int end single_complexity_type = GraphQL::ObjectType.define do name "SingleComplexity" field :value, types.Int, complexity: 0.1 do resolve ->(obj, args, ctx) { obj } end field :complexity, single_complexity_type do argument :value, types.Int complexity ->(ctx, args, child_complexity) { args[:value] + child_complexity } resolve ->(obj, args, ctx) { args[:value] } end interfaces [complexity_interface] end double_complexity_type = GraphQL::ObjectType.define do name "DoubleComplexity" field :value, types.Int, complexity: 4 do resolve ->(obj, args, ctx) { obj } end interfaces [complexity_interface] end query_type = GraphQL::ObjectType.define do name "Query" field :complexity, single_complexity_type do argument :value, types.Int complexity ->(ctx, args, child_complexity) { args[:value] + child_complexity } resolve ->(obj, args, ctx) { args[:value] } end field :innerComplexity, complexity_interface do argument :value, types.Int resolve ->(obj, args, ctx) { args[:value] } end end GraphQL::Schema.define( query: query_type, orphan_types: [double_complexity_type], resolve_type: ->(a,b,c) { :pass } ) } let(:query_string) {%| { a: complexity(value: 3) { value } b: complexity(value: 6) { value complexity(value: 1) { value } } } |} it "sums the complexity" do reduce_result # 10 from `complexity`, `0.3` from `value` assert_equal complexities, [query, 10.3] end describe "same field on multiple types" do let(:query_string) {%| { innerComplexity(value: 2) { ... on SingleComplexity { value } ... on DoubleComplexity { value } } } |} it "picks them max for those fields" do reduce_result # 1 for innerComplexity + 4 for DoubleComplexity.value assert_equal complexities, [query, 5] end end end end graphql-ruby-1.11.10/spec/graphql/analysis/query_depth_spec.rb000066400000000000000000000035151414121453000243720ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Analysis::QueryDepth do let(:depths) { [] } let(:query_depth) { GraphQL::Analysis::QueryDepth.new { |query, max_depth| depths << query << max_depth } } let(:reduce_result) { GraphQL::Analysis.analyze_query(query, [query_depth]) } let(:query) { GraphQL::Query.new(Dummy::Schema.graphql_definition, query_string, variables: variables) } let(:variables) { {} } describe "simple queries" do let(:query_string) {%| query cheeses($isIncluded: Boolean = true){ # depth of 2 cheese1: cheese(id: 1) { id flavor } # depth of 4 cheese2: cheese(id: 2) @include(if: $isIncluded) { similarCheese(source: SHEEP) { ... on Cheese { similarCheese(source: SHEEP) { id } } } } } |} it "finds the max depth" do reduce_result assert_equal depths, [query, 4] end describe "with directives" do let(:variables) { { "isIncluded" => false } } it "doesn't count skipped fields" do reduce_result assert_equal depths.last, 2 end end end describe "query with fragments" do let(:query_string) {%| { # depth of 2 cheese1: cheese(id: 1) { id flavor } # depth of 4 cheese2: cheese(id: 2) { ... cheeseFields1 } } fragment cheeseFields1 on Cheese { similarCheese(source: COW) { id ... cheeseFields2 } } fragment cheeseFields2 on Cheese { similarCheese(source: SHEEP) { id } } |} it "finds the max depth" do reduce_result assert_equal depths, [query, 4] end end end graphql-ruby-1.11.10/spec/graphql/argument_spec.rb000066400000000000000000000121111414121453000220300ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Argument do it "is validated at schema build-time" do query_type = GraphQL::ObjectType.define do name "Query" field :invalid, types.Boolean do argument :invalid, types.Float, default_value: ["123"] end end err = assert_raises(GraphQL::Schema::InvalidTypeError) { schema = GraphQL::Schema.define(query: query_type) schema.types } expected_error = %|Query is invalid: field "invalid" argument "invalid" default value ["123"] is not valid for type Float| assert_includes err.message, expected_error end describe ".from_dsl" do it "accepts an existing argument" do existing = GraphQL::Argument.define do name "bar" type GraphQL::STRING_TYPE end arg = GraphQL::Argument.from_dsl(:foo, existing) assert_equal "foo", arg.name assert_equal GraphQL::STRING_TYPE, arg.type end it "accepts a definition block after defining kwargs" do arg = GraphQL::Argument.from_dsl(:foo, GraphQL::STRING_TYPE) do description "my type is #{target.type}" end assert_equal "my type is String", arg.description end it "accepts a definition block and yields the argument if the block has an arity of one" do arg = GraphQL::Argument.from_dsl(:foo, GraphQL::STRING_TYPE) do |argument| argument.description "my type is #{target.type}" end assert_equal "my type is String", arg.description end it "accepts a definition block with existing arg" do existing = GraphQL::Argument.define do name "bar" type GraphQL::STRING_TYPE end arg = GraphQL::Argument.from_dsl(:foo, existing) do description "Description for an existing field." end assert_equal "Description for an existing field.", arg.description end it "creates an argument from dsl arguments" do arg = GraphQL::Argument.from_dsl( :foo, GraphQL::STRING_TYPE, "A Description", default_value: "Bar" ) assert_equal "foo", arg.name assert_equal GraphQL::STRING_TYPE, arg.type assert_equal "A Description", arg.description assert_equal "Bar", arg.default_value end end it "accepts custom keywords" do type = GraphQL::ObjectType.define do name "Something" field :something, types.String do argument "flagged", types.Int, metadata_flag: :flag_1 end end arg = type.fields["something"].arguments["flagged"] assert_equal true, arg.metadata[:flag_1] end it "accepts proc type" do argument = GraphQL::Argument.define(name: :favoriteFood, type: -> { GraphQL::STRING_TYPE }) assert_equal GraphQL::STRING_TYPE, argument.type end it "accepts a default_value" do argument = GraphQL::Argument.define(name: :favoriteFood, type: GraphQL::STRING_TYPE, default_value: 'Default') assert_equal 'Default', argument.default_value assert argument.default_value? end it "accepts a default_value of nil" do argument = GraphQL::Argument.define(name: :favoriteFood, type: GraphQL::STRING_TYPE, default_value: nil) assert argument.default_value.nil? assert argument.default_value? end it "default_value is optional" do argument = GraphQL::Argument.define(name: :favoriteFood, type: GraphQL::STRING_TYPE) assert argument.default_value.nil? assert !argument.default_value? end describe "#as, #exposed_as" do it "accepts a `as` property to define the arg name at resolve time" do argument = GraphQL::Argument.define(name: :favoriteFood, type: GraphQL::STRING_TYPE, as: :favFood) assert_equal argument.as, :favFood end it "uses `name` or `as` for `expose_as`" do arg_1 = GraphQL::Argument.define(name: :favoriteFood, type: GraphQL::STRING_TYPE, as: :favFood) assert_equal arg_1.expose_as, "favFood" arg_2 = GraphQL::Argument.define(name: :favoriteFood, type: GraphQL::STRING_TYPE) assert_equal arg_2.expose_as, "favoriteFood" arg_3 = arg_2.redefine { as :ff } assert_equal arg_3.expose_as, "ff" end it "can be set in the passed block" do argument = GraphQL::Argument.define do name "arg" as "arg_name" end assert_equal "arg_name", argument.as end end describe "prepare" do it "accepts a prepare proc and calls it to generate the prepared value" do prepare_proc = Proc.new { |arg, ctx| arg + ctx[:val] } argument = GraphQL::Argument.define(name: :plusOne, type: GraphQL::INT_TYPE, prepare: prepare_proc) assert_equal argument.prepare(1, {val: 1}), 2 end it "returns the value itself if no prepare proc is provided" do argument = GraphQL::Argument.define(name: :someNumber, type: GraphQL::INT_TYPE) assert_equal argument.prepare(1, nil), 1 end it "can be set in the passed block" do prepare_proc = Proc.new { |arg, ctx| arg + ctx[:val] } argument = GraphQL::Argument.define do name "arg" prepare prepare_proc end assert_equal argument.prepare(1, {val: 1}), 2 end end end graphql-ruby-1.11.10/spec/graphql/authorization_spec.rb000066400000000000000000001050361414121453000231170ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Authorization do module AuthTest class Box attr_reader :value def initialize(value:) @value = value end end class BaseArgument < GraphQL::Schema::Argument def visible?(context) super && (context[:hide] ? @name != "hidden" : true) end def accessible?(context) super && (context[:hide] ? @name != "inaccessible" : true) end def authorized?(parent_object, value, context) super && parent_object != :hide2 end end class BaseInputObjectArgument < BaseArgument def authorized?(parent_object, value, context) super && parent_object != :hide3 end end class BaseInputObject < GraphQL::Schema::InputObject argument_class BaseInputObjectArgument end class BaseField < GraphQL::Schema::Field def initialize(*args, edge_class: nil, **kwargs, &block) @edge_class = edge_class super(*args, **kwargs, &block) end def to_graphql field_defn = super if @edge_class field_defn.edge_class = @edge_class end field_defn end argument_class BaseArgument def visible?(context) super && (context[:hide] ? @name != "hidden" : true) end def accessible?(context) super && (context[:hide] ? @name != "inaccessible" : true) end def authorized?(object, args, context) if object == :raise raise GraphQL::UnauthorizedFieldError.new("raised authorized field error", object: object) end return Box.new(value: context[:lazy_field_authorized]) if context.key?(:lazy_field_authorized) super && object != :hide && object != :replace end end class BaseObject < GraphQL::Schema::Object field_class BaseField end module BaseInterface include GraphQL::Schema::Interface end class BaseEnumValue < GraphQL::Schema::EnumValue def initialize(*args, role: nil, **kwargs) @role = role super(*args, **kwargs) end def visible?(context) super && (context[:hide] ? @role != :hidden : true) end end class BaseEnum < GraphQL::Schema::Enum enum_value_class(BaseEnumValue) end module HiddenInterface include BaseInterface def self.visible?(ctx) super && !ctx[:hide] end def self.resolve_type(obj, ctx) HiddenObject end end module HiddenDefaultInterface include BaseInterface # visible? will call the super method def self.resolve_type(obj, ctx) HiddenObject end end class HiddenObject < BaseObject implements HiddenInterface implements HiddenDefaultInterface def self.visible?(ctx) super && !ctx[:hide] end field :some_field, String, null: true end class RelayObject < BaseObject def self.visible?(ctx) super && !ctx[:hidden_relay] end def self.accessible?(ctx) super && !ctx[:inaccessible_relay] end def self.authorized?(_val, ctx) super && !ctx[:unauthorized_relay] end field :some_field, String, null: true end # TODO test default behavior for abstract types, # that they check their concrete types module InaccessibleInterface include BaseInterface def self.accessible?(ctx) super && !ctx[:hide] end def self.resolve_type(obj, ctx) InaccessibleObject end end module InaccessibleDefaultInterface include BaseInterface # accessible? will call the super method def self.resolve_type(obj, ctx) InaccessibleObject end field :some_field, String, null: true end class InaccessibleObject < BaseObject implements InaccessibleInterface implements InaccessibleDefaultInterface def self.accessible?(ctx) super && !ctx[:hide] end field :some_field, String, null: true end class UnauthorizedObject < BaseObject def self.authorized?(value, context) if context[:raise] raise GraphQL::UnauthorizedError.new("raised authorized object error", object: value.object) end super && !context[:hide] end field :value, String, null: false, method: :itself end class UnauthorizedBox < BaseObject # Hide `"a"` def self.authorized?(value, context) super && value != "a" end field :value, String, null: false, method: :itself end module UnauthorizedInterface include BaseInterface def self.resolve_type(obj, ctx) if obj.is_a?(String) UnauthorizedCheckBox else raise "Unexpected value: #{obj.inspect}" end end end class UnauthorizedCheckBox < BaseObject implements UnauthorizedInterface # This authorized check returns a lazy object, it should be synced by the runtime. def self.authorized?(value, context) if !value.is_a?(String) raise "Unexpected box value: #{value.inspect}" end is_authed = super && value != "a" # Make it many levels nested just to make sure we support nested lazy objects Box.new(value: Box.new(value: Box.new(value: Box.new(value: is_authed)))) end field :value, String, null: false, method: :itself end class IntegerObject < BaseObject def self.authorized?(obj, ctx) if !obj.is_a?(Integer) raise "Unexpected IntegerObject: #{obj}" end is_allowed = !(ctx[:unauthorized_relay] || obj == ctx[:exclude_integer]) Box.new(value: Box.new(value: is_allowed)) end field :value, Integer, null: false, method: :itself end class IntegerObjectEdge < GraphQL::Types::Relay::BaseEdge node_type(IntegerObject) end class IntegerObjectConnection < GraphQL::Types::Relay::BaseConnection edge_type(IntegerObjectEdge) end # This object responds with `replaced => false`, # but if its replacement value is used, it gives `replaced => true` class Replaceable def replacement { replaced: true } end def replaced false end end class ReplacedObject < BaseObject def self.authorized?(obj, ctx) super && !ctx[:replace_me] end field :replaced, Boolean, null: false end class LandscapeFeature < BaseEnum value "MOUNTAIN" value "STREAM", role: :unauthorized value "FIELD", role: :inaccessible value "TAR_PIT", role: :hidden end class Query < BaseObject def self.authorized?(obj, ctx) !ctx[:query_unauthorized] end field :hidden, Integer, null: false field :unauthorized, Integer, null: true, method: :itself field :int2, Integer, null: true do argument :int, Integer, required: false argument :hidden, Integer, required: false argument :inaccessible, Integer, required: false argument :unauthorized, Integer, required: false end def int2(**args) args[:unauthorized] || 1 end field :landscape_feature, LandscapeFeature, null: false do argument :string, String, required: false argument :enum, LandscapeFeature, required: false end def landscape_feature(string: nil, enum: nil) string || enum end field :landscape_features, [LandscapeFeature], null: false do argument :strings, [String], required: false argument :enums, [LandscapeFeature], required: false end def landscape_features(strings: [], enums: []) strings + enums end def empty_array; []; end field :hidden_object, HiddenObject, null: false, resolver_method: :itself field :hidden_interface, HiddenInterface, null: false, resolver_method: :itself field :hidden_default_interface, HiddenDefaultInterface, null: false, resolver_method: :itself field :hidden_connection, RelayObject.connection_type, null: :false, resolver_method: :empty_array field :hidden_edge, RelayObject.edge_type, null: :false, resolver_method: :edge_object field :inaccessible, Integer, null: false, method: :object_id field :inaccessible_object, InaccessibleObject, null: false, resolver_method: :itself field :inaccessible_interface, InaccessibleInterface, null: false, resolver_method: :itself field :inaccessible_default_interface, InaccessibleDefaultInterface, null: false, resolver_method: :itself field :inaccessible_connection, RelayObject.connection_type, null: :false, resolver_method: :empty_array field :inaccessible_edge, RelayObject.edge_type, null: :false, resolver_method: :edge_object field :unauthorized_object, UnauthorizedObject, null: true, resolver_method: :itself field :unauthorized_connection, RelayObject.connection_type, null: false, resolver_method: :array_with_item field :unauthorized_edge, RelayObject.edge_type, null: false, resolver_method: :edge_object def edge_object OpenStruct.new(node: 100) end def array_with_item [1] end field :unauthorized_lazy_box, UnauthorizedBox, null: true do argument :value, String, required: true end def unauthorized_lazy_box(value:) # Make it extra nested, just for good measure. Box.new(value: Box.new(value: value)) end field :unauthorized_list_items, [UnauthorizedObject], null: true def unauthorized_list_items [self, self] end field :unauthorized_lazy_check_box, UnauthorizedCheckBox, null: true, resolver_method: :unauthorized_lazy_box do argument :value, String, required: true end field :unauthorized_interface, UnauthorizedInterface, null: true, resolver_method: :unauthorized_lazy_box do argument :value, String, required: true end field :unauthorized_lazy_list_interface, [UnauthorizedInterface, null: true], null: true def unauthorized_lazy_list_interface ["z", Box.new(value: Box.new(value: "z2")), "a", Box.new(value: "a")] end field :integers, IntegerObjectConnection, null: false def integers [1,2,3] end field :lazy_integers, IntegerObjectConnection, null: false def lazy_integers Box.new(value: Box.new(value: [1,2,3])) end field :replaced_object, ReplacedObject, null: false def replaced_object Replaceable.new end end class DoHiddenStuff < GraphQL::Schema::RelayClassicMutation def self.visible?(ctx) super && (ctx[:hidden_mutation] ? false : true) end end class DoHiddenStuff2 < GraphQL::Schema::Mutation def self.visible?(ctx) super && !ctx[:hidden_mutation] end field :some_return_field, String, null: true end class DoInaccessibleStuff < GraphQL::Schema::RelayClassicMutation def self.accessible?(ctx) super && (ctx[:inaccessible_mutation] ? false : true) end end class DoUnauthorizedStuff < GraphQL::Schema::RelayClassicMutation def self.authorized?(obj, ctx) super && (ctx[:unauthorized_mutation] ? false : true) end end class Mutation < BaseObject field :do_hidden_stuff, mutation: DoHiddenStuff field :do_hidden_stuff2, mutation: DoHiddenStuff2 field :do_inaccessible_stuff, mutation: DoInaccessibleStuff field :do_unauthorized_stuff, mutation: DoUnauthorizedStuff end class Schema < GraphQL::Schema if TESTING_INTERPRETER use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST else # Opt in to accessible? checks query_analyzer GraphQL::Authorization::Analyzer end query(Query) mutation(Mutation) lazy_resolve(Box, :value) def self.unauthorized_object(err) if err.object.respond_to?(:replacement) err.object.replacement elsif err.object == :replace 33 elsif err.object == :raise_from_object raise GraphQL::ExecutionError, err.message else raise GraphQL::ExecutionError, "Unauthorized #{err.type.graphql_name}: #{err.object.inspect}" end end # use GraphQL::Backtrace end class SchemaWithFieldHook < GraphQL::Schema if TESTING_INTERPRETER use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST end query(Query) lazy_resolve(Box, :value) def self.unauthorized_field(err) if err.object == :replace 42 elsif err.object == :raise raise GraphQL::ExecutionError, "#{err.message} in field #{err.field.graphql_name}" else raise GraphQL::ExecutionError, "Unauthorized field #{err.field.graphql_name} on #{err.type.graphql_name}: #{err.object}" end end end end def auth_execute(*args, **kwargs) AuthTest::Schema.execute(*args, **kwargs) end describe "applying the visible? method" do it "works in queries" do res = auth_execute(" { int int2 } ", context: { hide: true }) assert_equal 1, res["errors"].size end it "applies return type visibility to fields" do error_queries = { "hiddenObject" => "{ hiddenObject { __typename } }", "hiddenInterface" => "{ hiddenInterface { __typename } }", "hiddenDefaultInterface" => "{ hiddenDefaultInterface { __typename } }", } error_queries.each do |name, q| hidden_res = auth_execute(q, context: { hide: true}) assert_equal ["Field '#{name}' doesn't exist on type 'Query'"], hidden_res["errors"].map { |e| e["message"] } visible_res = auth_execute(q) # Both fields exist; the interface resolves to the object type, though assert_equal "HiddenObject", visible_res["data"][name]["__typename"] end end it "uses the mutation for derived fields, inputs and outputs" do query = "mutation { doHiddenStuff(input: {}) { __typename } }" res = auth_execute(query, context: { hidden_mutation: true }) assert_equal ["Field 'doHiddenStuff' doesn't exist on type 'Mutation'"], res["errors"].map { |e| e["message"] } # `#resolve` isn't implemented, so this errors out: assert_raises GraphQL::RequiredImplementationMissingError do auth_execute(query) end introspection_q = <<-GRAPHQL { t1: __type(name: "DoHiddenStuffInput") { name } t2: __type(name: "DoHiddenStuffPayload") { name } } GRAPHQL hidden_introspection_res = auth_execute(introspection_q, context: { hidden_mutation: true }) assert_nil hidden_introspection_res["data"]["t1"] assert_nil hidden_introspection_res["data"]["t2"] visible_introspection_res = auth_execute(introspection_q) assert_equal "DoHiddenStuffInput", visible_introspection_res["data"]["t1"]["name"] assert_equal "DoHiddenStuffPayload", visible_introspection_res["data"]["t2"]["name"] end it "works with Schema::Mutation" do query = "mutation { doHiddenStuff2 { __typename } }" res = auth_execute(query, context: { hidden_mutation: true }) assert_equal ["Field 'doHiddenStuff2' doesn't exist on type 'Mutation'"], res["errors"].map { |e| e["message"] } # `#resolve` isn't implemented, so this errors out: assert_raises GraphQL::RequiredImplementationMissingError do auth_execute(query) end end it "uses the base type for edges and connections" do query = <<-GRAPHQL { hiddenConnection { __typename } hiddenEdge { __typename } } GRAPHQL hidden_res = auth_execute(query, context: { hidden_relay: true }) assert_equal 2, hidden_res["errors"].size visible_res = auth_execute(query) assert_equal "RelayObjectConnection", visible_res["data"]["hiddenConnection"]["__typename"] assert_equal "RelayObjectEdge", visible_res["data"]["hiddenEdge"]["__typename"] end it "treats hidden enum values as non-existant, even in lists" do hidden_res_1 = auth_execute <<-GRAPHQL, context: { hide: true } { landscapeFeature(enum: TAR_PIT) } GRAPHQL assert_equal ["Argument 'enum' on Field 'landscapeFeature' has an invalid value (TAR_PIT). Expected type 'LandscapeFeature'."], hidden_res_1["errors"].map { |e| e["message"] } hidden_res_2 = auth_execute <<-GRAPHQL, context: { hide: true } { landscapeFeatures(enums: [STREAM, TAR_PIT]) } GRAPHQL assert_equal ["Argument 'enums' on Field 'landscapeFeatures' has an invalid value ([STREAM, TAR_PIT]). Expected type '[LandscapeFeature!]'."], hidden_res_2["errors"].map { |e| e["message"] } success_res = auth_execute <<-GRAPHQL, context: { hide: false } { landscapeFeature(enum: TAR_PIT) landscapeFeatures(enums: [STREAM, TAR_PIT]) } GRAPHQL assert_equal "TAR_PIT", success_res["data"]["landscapeFeature"] assert_equal ["STREAM", "TAR_PIT"], success_res["data"]["landscapeFeatures"] end it "refuses to resolve to hidden enum values" do expected_class = TESTING_INTERPRETER ? AuthTest::LandscapeFeature::UnresolvedValueError : GraphQL::EnumType::UnresolvedValueError assert_raises(expected_class) do auth_execute <<-GRAPHQL, context: { hide: true } { landscapeFeature(string: "TAR_PIT") } GRAPHQL end assert_raises(expected_class) do auth_execute <<-GRAPHQL, context: { hide: true } { landscapeFeatures(strings: ["STREAM", "TAR_PIT"]) } GRAPHQL end end it "works in introspection" do res = auth_execute <<-GRAPHQL, context: { hide: true, hidden_mutation: true } { query: __type(name: "Query") { fields { name args { name } } } hiddenObject: __type(name: "HiddenObject") { name } hiddenInterface: __type(name: "HiddenInterface") { name } landscapeFeatures: __type(name: "LandscapeFeature") { enumValues { name } } } GRAPHQL query_field_names = res["data"]["query"]["fields"].map { |f| f["name"] } refute_includes query_field_names, "int" int2_arg_names = res["data"]["query"]["fields"].find { |f| f["name"] == "int2" }["args"].map { |a| a["name"] } assert_equal ["int", "inaccessible", "unauthorized"], int2_arg_names assert_nil res["data"]["hiddenObject"] assert_nil res["data"]["hiddenInterface"] visible_landscape_features = res["data"]["landscapeFeatures"]["enumValues"].map { |v| v["name"] } assert_equal ["MOUNTAIN", "STREAM", "FIELD"], visible_landscape_features end it "works when printing the SDL" do full_sdl = AuthTest::Schema.to_definition restricted_sdl = AuthTest::Schema.to_definition(context: { hide: true, hidden_mutation: true, hidden_relay: true }) assert_includes full_sdl, 'Hidden' assert_includes full_sdl, 'hidden' refute_includes restricted_sdl, 'Hidden' refute_includes restricted_sdl, 'hidden' end end if !TESTING_INTERPRETER # This isn't supported when running the interpreter describe "applying the accessible? method" do it "works with fields and arguments" do queries = { "{ inaccessible }" => ["Some fields in this query are not accessible: inaccessible"], "{ int2(inaccessible: 1) }" => ["Some fields in this query are not accessible: int2"], } queries.each do |query_str, errors| res = auth_execute(query_str, context: { hide: true }) assert_equal errors, res.fetch("errors").map { |e| e["message"] } res = auth_execute(query_str, context: { hide: false }) refute res.key?("errors") end end it "works with return types" do queries = { "{ inaccessibleObject { __typename } }" => ["Some fields in this query are not accessible: inaccessibleObject"], "{ inaccessibleInterface { __typename } }" => ["Some fields in this query are not accessible: inaccessibleInterface"], "{ inaccessibleDefaultInterface { __typename } }" => ["Some fields in this query are not accessible: inaccessibleDefaultInterface"], } queries.each do |query_str, errors| res = auth_execute(query_str, context: { hide: true }) assert_equal errors, res["errors"].map { |e| e["message"] } res = auth_execute(query_str, context: { hide: false }) refute res.key?("errors") end end it "works with mutations" do query = "mutation { doInaccessibleStuff(input: {}) { __typename } }" res = auth_execute(query, context: { inaccessible_mutation: true }) assert_equal ["Some fields in this query are not accessible: doInaccessibleStuff"], res["errors"].map { |e| e["message"] } assert_raises GraphQL::RequiredImplementationMissingError do auth_execute(query) end end it "works with edges and connections" do query = <<-GRAPHQL { inaccessibleConnection { __typename } inaccessibleEdge { __typename } } GRAPHQL inaccessible_res = auth_execute(query, context: { inaccessible_relay: true }) assert_equal ["Some fields in this query are not accessible: inaccessibleConnection, inaccessibleEdge"], inaccessible_res["errors"].map { |e| e["message"] } accessible_res = auth_execute(query) refute accessible_res.key?("errors") end end end describe "applying the authorized? method" do it "halts on unauthorized objects, replacing the object with nil" do query = "{ unauthorizedObject { __typename } }" hidden_response = auth_execute(query, context: { hide: true }) assert_nil hidden_response["data"].fetch("unauthorizedObject") visible_response = auth_execute(query, context: {}) assert_equal({ "__typename" => "UnauthorizedObject" }, visible_response["data"]["unauthorizedObject"]) end it "halts on unauthorized mutations" do query = "mutation { doUnauthorizedStuff(input: {}) { __typename } }" res = auth_execute(query, context: { unauthorized_mutation: true }) assert_nil res["data"].fetch("doUnauthorizedStuff") assert_raises GraphQL::RequiredImplementationMissingError do auth_execute(query) end end describe "field level authorization" do describe "unauthorized field" do describe "with an unauthorized field hook configured" do describe "when the hook returns a value" do it "replaces the response with the return value of the unauthorized field hook" do query = "{ unauthorized }" response = AuthTest::SchemaWithFieldHook.execute(query, root_value: :replace) assert_equal 42, response["data"].fetch("unauthorized") end end describe "when the field hook raises an error" do it "returns nil" do query = "{ unauthorized }" response = AuthTest::SchemaWithFieldHook.execute(query, root_value: :hide) assert_nil response["data"].fetch("unauthorized") end it "adds the error to the errors key" do query = "{ unauthorized }" response = AuthTest::SchemaWithFieldHook.execute(query, root_value: :hide) assert_equal ["Unauthorized field unauthorized on Query: hide"], response["errors"].map { |e| e["message"] } end end if TESTING_INTERPRETER describe "when the field authorization resolves lazily" do it "returns value if authorized" do query = "{ unauthorized }" response = AuthTest::SchemaWithFieldHook.execute(query, root_value: 34, context: { lazy_field_authorized: true }) assert_equal 34, response["data"].fetch("unauthorized") end it "returns nil if not authorized" do query = "{ unauthorized }" response = AuthTest::SchemaWithFieldHook.execute(query, root_value: 34, context: { lazy_field_authorized: false }) assert_nil response["data"].fetch("unauthorized") end end end describe "when the field authorization raises an UnauthorizedFieldError" do it "receives the raised error" do query = "{ unauthorized }" response = AuthTest::SchemaWithFieldHook.execute(query, root_value: :raise) assert_equal ["raised authorized field error in field unauthorized"], response["errors"].map { |e| e["message"] } end end end describe "with an unauthorized field hook not configured" do describe "When the object hook replaces the field" do it "delegates to the unauthorized object hook, which replaces the object" do query = "{ unauthorized }" response = AuthTest::Schema.execute(query, root_value: :replace) assert_equal 33, response["data"].fetch("unauthorized") end end describe "When the object hook raises an error" do it "returns nil" do query = "{ unauthorized }" response = AuthTest::Schema.execute(query, root_value: :hide) assert_nil response["data"].fetch("unauthorized") end it "adds the error to the errors key" do query = "{ unauthorized }" response = AuthTest::Schema.execute(query, root_value: :hide) assert_equal ["Unauthorized Query: :hide"], response["errors"].map { |e| e["message"] } end end end end describe "authorized field" do it "returns the field data" do query = "{ unauthorized }" response = AuthTest::SchemaWithFieldHook.execute(query, root_value: 1) assert_equal 1, response["data"].fetch("unauthorized") end end end it "halts on unauthorized fields, using the parent object" do query = "{ unauthorized }" hidden_response = auth_execute(query, root_value: :hide) assert_nil hidden_response["data"].fetch("unauthorized") visible_response = auth_execute(query, root_value: 1) assert_equal 1, visible_response["data"]["unauthorized"] end it "halts on unauthorized arguments, using the parent object" do query = "{ int2(unauthorized: 5) }" hidden_response = auth_execute(query, root_value: :hide2) assert_nil hidden_response["data"].fetch("int2") visible_response = auth_execute(query) assert_equal 5, visible_response["data"]["int2"] end it "works with edges and connections" do query = <<-GRAPHQL { unauthorizedConnection { __typename edges { __typename node { __typename } } nodes { __typename } } unauthorizedEdge { __typename node { __typename } } } GRAPHQL unauthorized_res = auth_execute(query, context: { unauthorized_relay: true }) conn = unauthorized_res["data"].fetch("unauthorizedConnection") assert_equal "RelayObjectConnection", conn.fetch("__typename") # This is tricky: the previous behavior was to replace the _whole_ # list with `nil`. This was due to an implementation detail: # The list field's return value (an array of integers) was wrapped # _before_ returning, and during this wrapping, a cascading error # caused the entire field to be nilled out. # # In the interpreter, each list item is contained and the error doesn't propagate # up to the whole list. # # Originally, I thought that this was a _feature_ that obscured list entries. # But really, look at the test below: you don't get this "feature" if # you use `edges { node }`, so it can't be relied on in any way. # # All that to say, in the interpreter, `nodes` and `edges { node }` behave # the same. # # TODO revisit the docs for this. failed_nodes_value = TESTING_INTERPRETER ? [nil] : nil assert_equal failed_nodes_value, conn.fetch("nodes") assert_equal [{"node" => nil, "__typename" => "RelayObjectEdge"}], conn.fetch("edges") edge = unauthorized_res["data"].fetch("unauthorizedEdge") assert_nil edge.fetch("node") assert_equal "RelayObjectEdge", edge["__typename"] unauthorized_object_paths = [ ["unauthorizedConnection", "edges", 0, "node"], TESTING_INTERPRETER ? ["unauthorizedConnection", "nodes", 0] : ["unauthorizedConnection", "nodes"], ["unauthorizedEdge", "node"] ] assert_equal unauthorized_object_paths, unauthorized_res["errors"].map { |e| e["path"] } authorized_res = auth_execute(query) conn = authorized_res["data"].fetch("unauthorizedConnection") assert_equal "RelayObjectConnection", conn.fetch("__typename") assert_equal [{"__typename"=>"RelayObject"}], conn.fetch("nodes") assert_equal [{"node" => {"__typename" => "RelayObject"}, "__typename" => "RelayObjectEdge"}], conn.fetch("edges") edge = authorized_res["data"].fetch("unauthorizedEdge") assert_equal "RelayObject", edge.fetch("node").fetch("__typename") assert_equal "RelayObjectEdge", edge["__typename"] end it "authorizes _after_ resolving lazy objects" do query = <<-GRAPHQL { a: unauthorizedLazyBox(value: "a") { value } b: unauthorizedLazyBox(value: "b") { value } } GRAPHQL unauthorized_res = auth_execute(query) assert_nil unauthorized_res["data"].fetch("a") assert_equal "b", unauthorized_res["data"]["b"]["value"] end it "authorizes items in a list" do query = <<-GRAPHQL { unauthorizedListItems { __typename } } GRAPHQL unauthorized_res = auth_execute(query, context: { hide: true }) assert_nil unauthorized_res["data"]["unauthorizedListItems"] authorized_res = auth_execute(query, context: { hide: false }) assert_equal 2, authorized_res["data"]["unauthorizedListItems"].size end it "syncs lazy objects from authorized? checks" do query = <<-GRAPHQL { a: unauthorizedLazyCheckBox(value: "a") { value } b: unauthorizedLazyCheckBox(value: "b") { value } } GRAPHQL unauthorized_res = auth_execute(query) assert_nil unauthorized_res["data"].fetch("a") assert_equal "b", unauthorized_res["data"]["b"]["value"] # Also, the custom handler was called: assert_equal ["Unauthorized UnauthorizedCheckBox: \"a\""], unauthorized_res["errors"].map { |e| e["message"] } end it "Works for lazy connections" do query = <<-GRAPHQL { lazyIntegers { edges { node { value } } } } GRAPHQL res = auth_execute(query) assert_equal [1,2,3], res["data"]["lazyIntegers"]["edges"].map { |e| e["node"]["value"] } end it "Works for eager connections" do query = <<-GRAPHQL { integers { edges { node { value } } } } GRAPHQL res = auth_execute(query) assert_equal [1,2,3], res["data"]["integers"]["edges"].map { |e| e["node"]["value"] } end it "filters out individual nodes by value" do query = <<-GRAPHQL { integers { edges { node { value } } } } GRAPHQL res = auth_execute(query, context: { exclude_integer: 1 }) assert_equal [nil,2,3], res["data"]["integers"]["edges"].map { |e| e["node"] && e["node"]["value"] } assert_equal ["Unauthorized IntegerObject: 1"], res["errors"].map { |e| e["message"] } end it "works with lazy values / interfaces" do query = <<-GRAPHQL query($value: String!){ unauthorizedInterface(value: $value) { ... on UnauthorizedCheckBox { value } } } GRAPHQL res = auth_execute(query, variables: { value: "a"}) assert_nil res["data"]["unauthorizedInterface"] res2 = auth_execute(query, variables: { value: "b"}) assert_equal "b", res2["data"]["unauthorizedInterface"]["value"] end it "works with lazy values / lists of interfaces" do query = <<-GRAPHQL { unauthorizedLazyListInterface { ... on UnauthorizedCheckBox { value } } } GRAPHQL res = auth_execute(query) # An error from two, values from the others assert_equal ["Unauthorized UnauthorizedCheckBox: \"a\"", "Unauthorized UnauthorizedCheckBox: \"a\""], res["errors"].map { |e| e["message"] } assert_equal [{"value" => "z"}, {"value" => "z2"}, nil, nil], res["data"]["unauthorizedLazyListInterface"] end describe "with an unauthorized field hook configured" do it "replaces objects from the unauthorized_object hook" do query = "{ replacedObject { replaced } }" res = auth_execute(query, context: { replace_me: true }) assert_equal true, res["data"]["replacedObject"]["replaced"] res = auth_execute(query, context: { replace_me: false }) assert_equal false, res["data"]["replacedObject"]["replaced"] end it "works when the query hook returns false and there's no root object" do query = "{ __typename }" res = auth_execute(query) assert_equal "Query", res["data"]["__typename"] unauth_res = auth_execute(query, context: { query_unauthorized: true }) assert_nil unauth_res["data"] assert_equal [{"message"=>"Unauthorized Query: nil"}], unauth_res["errors"] end describe "when the object authorization raises an UnauthorizedFieldError" do it "receives the raised error" do query = "{ unauthorizedObject { value } }" response = auth_execute(query, context: { raise: true }, root_value: :raise_from_object) assert_equal ["raised authorized object error"], response["errors"].map { |e| e["message"] } end end end end describe "returning false" do class FalseSchema < GraphQL::Schema class Query < GraphQL::Schema::Object def self.authorized?(obj, ctx) false end field :int, Integer, null: false def int 1 end end query(Query) if TESTING_INTERPRETER use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST end end it "works out-of-the-box" do res = FalseSchema.execute("{ int }") assert_nil res.fetch("data") refute res.key?("errors") end end end graphql-ruby-1.11.10/spec/graphql/backtrace_spec.rb000066400000000000000000000162351414121453000221400ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Backtrace do class LazyError def raise_err raise "Lazy Boom" end end class ErrorAnalyzer def call(_memo, visit_type, irep_node) if irep_node.name == "raiseError" raise GraphQL::AnalysisError, "this should not be wrapped by a backtrace, but instead, returned to the client" end end end class NilInspectObject # Oops, this is evil, but it happens and we should handle it. def inspect; nil; end end class ErrorInstrumentation def self.before_query(_query) end def self.after_query(query) raise "Instrumentation Boom" end end let(:resolvers) { { "Query" => { "field1" => Proc.new { :something }, "field2" => Proc.new { :something }, "nilInspect" => Proc.new { NilInspectObject.new }, }, "Thing" => { "listField" => Proc.new { :not_a_list }, "raiseField" => Proc.new { |o, a| raise("This is broken: #{a[:message]}") }, }, "OtherThing" => { "strField" => Proc.new { LazyError.new }, }, } } let(:schema) { defn = <<-GRAPHQL type Query { field1: Thing field2: OtherThing nilInspect: Thing } type Thing { listField: [OtherThing] raiseField(message: String!): Int } type OtherThing { strField: String } GRAPHQL schema_class = GraphQL::Schema.from_definition(defn, default_resolve: resolvers, interpreter: false) schema_class.class_exec { lazy_resolve(LazyError, :raise_err) query_analyzer(ErrorAnalyzer.new) } schema_class } let(:backtrace_schema) { Class.new(schema) do use GraphQL::Backtrace end } describe "GraphQL backtrace helpers" do it "raises a TracedError when enabled" do assert_raises(GraphQL::Backtrace::TracedError) { backtrace_schema.execute("query BrokenList { field1 { listField { strField } } }") } assert_raises(NoMethodError) { schema.execute("query BrokenList { field1 { listField { strField } } }") } end it "annotates crashes from user code" do err = assert_raises(GraphQL::Backtrace::TracedError) { backtrace_schema.execute <<-GRAPHQL, root_value: "Root" query($msg: String = \"Boom\") { field1 { boomError: raiseField(message: $msg) } } GRAPHQL } # The original error info is present assert_instance_of RuntimeError, err.cause b = err.cause.backtrace assert_backtrace_includes(b, file: "backtrace_spec.rb", method: "block") assert_backtrace_includes(b, file: "execute.rb", method: "resolve_field") assert_backtrace_includes(b, file: "execute.rb", method: "resolve_field") assert_backtrace_includes(b, file: "execute.rb", method: "resolve_root_selection") # GraphQL backtrace is present expected_graphql_backtrace = [ "3:13: Thing.raiseField as boomError", "2:11: Query.field1", "1:9: query", ] assert_equal expected_graphql_backtrace, err.graphql_backtrace # The message includes the GraphQL context rendered_table = [ 'Loc | Field | Object | Arguments | Result', '3:13 | Thing.raiseField as boomError | :something | {"message"=>"Boom"} | #', '2:11 | Query.field1 | "Root" | {} | {}', '1:9 | query | "Root" | {"msg"=>"Boom"} | ', ].join("\n") assert_includes err.message, rendered_table # The message includes the original error message assert_includes err.message, "This is broken: Boom" assert_includes err.message, "spec/graphql/backtrace_spec.rb:42", "It includes the original backtrace" assert_includes err.message, "more lines" end it "annotates errors from Query#result" do query_str = "query StrField { field2 { strField } __typename }" context = { backtrace: true } query = GraphQL::Query.new(schema, query_str, context: context) err = assert_raises(GraphQL::Backtrace::TracedError) { query.result } assert_instance_of RuntimeError, err.cause end it "annotates errors inside lazy resolution" do # Test context-based flag err = assert_raises(GraphQL::Backtrace::TracedError) { schema.execute("query StrField { field2 { strField } __typename }", context: { backtrace: true }) } assert_instance_of RuntimeError, err.cause b = err.cause.backtrace assert_backtrace_includes(b, file: "backtrace_spec.rb", method: "raise_err") assert_backtrace_includes(b, file: "field.rb", method: "lazy_resolve") assert_backtrace_includes(b, file: "lazy/resolve.rb", method: "block") expected_graphql_backtrace = [ "1:27: OtherThing.strField", "1:18: Query.field2", "1:1: query StrField", ] assert_equal(expected_graphql_backtrace, err.graphql_backtrace) rendered_table = [ 'Loc | Field | Object | Arguments | Result', '1:27 | OtherThing.strField | :something | {} | #', '1:18 | Query.field2 | nil | {} | {strField: (unresolved)}', '1:1 | query StrField | nil | {} | {field2: {...}, __typename: "Query"}', ].join("\n") assert_includes err.message, rendered_table end it "returns analysis errors to the client" do res = backtrace_schema.execute("query raiseError { __typename }") assert_equal "this should not be wrapped by a backtrace, but instead, returned to the client", res["errors"].first["message"] end it "always stringifies the #inspect response" do # test the schema plugin err = assert_raises(GraphQL::Backtrace::TracedError) { backtrace_schema.execute("query { nilInspect { raiseField(message: \"pop!\") } }") } rendered_table = [ 'Loc | Field | Object | Arguments | Result', '1:22 | Thing.raiseField | | {"message"=>"pop!"} | #', '1:9 | Query.nilInspect | nil | {} | {}', '1:1 | query | nil | {} | {}', ].join("\n") assert_includes(err.message, rendered_table) end it "raises original exception instead of a TracedError when error does not occur during resolving" do instrumentation_schema = schema.redefine do instrument(:query, ErrorInstrumentation) end assert_raises(RuntimeError) { instrumentation_schema.execute(GraphQL::Introspection::INTROSPECTION_QUERY, context: { backtrace: true }) } end end # This will get brittle when execution code moves between files # but I'm not sure how to be sure that the backtrace contains the right stuff! def assert_backtrace_includes(backtrace, file:, method:) includes_tag = backtrace.any? { |s| s.include?(file) && s.include?("`" + method) } assert includes_tag, "Backtrace should include #{file} inside method #{method}" end end graphql-ruby-1.11.10/spec/graphql/base_type_spec.rb000066400000000000000000000102721414121453000221670ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::BaseType do it "becomes non-null with !" do type = GraphQL::EnumType.new non_null_type = !type assert_equal(GraphQL::TypeKinds::NON_NULL, non_null_type.kind) assert_equal(type, non_null_type.of_type) assert_equal(GraphQL::TypeKinds::NON_NULL, (!GraphQL::STRING_TYPE).kind) end it "can be compared" do obj_type = Dummy::Milk.graphql_definition assert_equal(!GraphQL::INT_TYPE, !GraphQL::INT_TYPE) refute_equal(!GraphQL::FLOAT_TYPE, GraphQL::FLOAT_TYPE) assert_equal( GraphQL::ListType.new(of_type: obj_type), GraphQL::ListType.new(of_type: obj_type) ) refute_equal( GraphQL::ListType.new(of_type: obj_type), GraphQL::ListType.new(of_type: !obj_type) ) end it "Accepts arbitrary metadata" do assert_equal ["Cheese"], Dummy::Cheese.graphql_definition.metadata[:class_names] end describe "#name" do describe "when containing spaces" do BaseNameSpaceTest = GraphQL::BaseType.define do name "Some Invalid Name" end it "is invalid" do assert_raises(GraphQL::InvalidNameError) { BaseNameSpaceTest.name } end end describe "when containing colons" do BaseNameColonsTest = GraphQL::BaseType.define do name "Some::Invalid::Name" end it 'is invalid' do assert_raises(GraphQL::InvalidNameError) { BaseNameColonsTest.name } end end end describe "name" do it "fails with a helpful message" do error = assert_raises RuntimeError do class BaseType < GraphQL::Schema::Object name "KerkShine" end end assert_equal error.message, "The new name override method is `graphql_name`, not `name`. Usage: graphql_name \"KerkShine\"" end end describe "forwards-compat with new api" do let(:type_defn) { Dummy::Cheese.graphql_definition } it "responds to new methods" do assert_equal "Cheese", type_defn.graphql_name assert_equal type_defn, type_defn.graphql_definition end end describe "#dup" do let(:obj_type) { GraphQL::ObjectType.define do name "SomeObject" field :id, types.Int end } it "resets connection types" do # Make sure the defaults have been calculated obj_edge = obj_type.edge_type obj_conn = obj_type.connection_type obj_2 = obj_type.dup obj_2.name = "Cheese2" refute_equal obj_edge, obj_2.edge_type refute_equal obj_conn, obj_2.connection_type end end describe "#to_definition" do post_type = GraphQL::ObjectType.define do name "Post" description "A blog post" field :id, !types.ID field :title, !types.String field :body, !types.String end query_root = GraphQL::ObjectType.define do name "Query" description "The query root of this schema" field :post do type post_type resolve ->(obj, args, ctx) { Post.find(args["id"]) } end end schema = GraphQL::Schema.define(query: query_root) expected = < true, "f" => false} } let(:result) { Dummy::Schema.execute(query_string, variables: variables) } describe "on fields" do let(:query_string) { %|query directives($t: Boolean!, $f: Boolean!) { cheese(id: 1) { # plain fields: skipFlavor: flavor @skip(if: true) dontSkipFlavor: flavor @skip(if: false) dontSkipDontIncludeFlavor: flavor @skip(if: false), @include(if: false) skipAndInclude: flavor @skip(if: true), @include(if: true) includeFlavor: flavor @include(if: $t) dontIncludeFlavor: flavor @include(if: $f) # fields in fragments ... includeIdField ... dontIncludeIdField ... skipIdField ... dontSkipIdField } } fragment includeIdField on Cheese { includeId: id @include(if: true) } fragment dontIncludeIdField on Cheese { dontIncludeId: id @include(if: false) } fragment skipIdField on Cheese { skipId: id @skip(if: true) } fragment dontSkipIdField on Cheese { dontSkipId: id @skip(if: false) } | } describe "child fields" do let(:query_string) { <<-GRAPHQL { __type(name: """ Cheese """) { fields { name } fields @skip(if: true) { isDeprecated } } } GRAPHQL } it "skips child fields too" do first_field = result["data"]["__type"]["fields"].first assert first_field.key?("name") assert !first_field.key?("isDeprecated") end end describe "when directive uses argument with default value" do describe "with false" do let(:query_string) { <<-GRAPHQL query($f: Boolean = false) { cheese(id: 1) { dontIncludeFlavor: flavor @include(if: $f) dontSkipFlavor: flavor @skip(if: $f) } } GRAPHQL } it "is not included" do assert !result["data"]["cheese"].key?("dontIncludeFlavor") end it "is not skipped" do assert result["data"]["cheese"].key?("dontSkipFlavor") end end describe "with true" do let(:query_string) { <<-GRAPHQL query($t: Boolean = true) { cheese(id: 1) { includeFlavor: flavor @include(if: $t) skipFlavor: flavor @skip(if: $t) } } GRAPHQL } it "is included" do assert result["data"]["cheese"].key?("includeFlavor") end it "is skipped" do assert !result["data"]["cheese"].key?("skipFlavor") end end end it "intercepts fields" do expected = { "data" =>{ "cheese" => { "dontSkipFlavor" => "Brie", "includeFlavor" => "Brie", "includeId" => 1, "dontSkipId" => 1, }, }} assert_equal(expected, result) end end describe "on fragments spreads and inline fragments" do let(:query_string) { %|query directives { cheese(id: 1) { ... skipFlavorField @skip(if: true) ... dontSkipFlavorField @skip(if: false) ... includeFlavorField @include(if: true) ... dontIncludeFlavorField @include(if: false) ... on Cheese @skip(if: true) { skipInlineId: id } ... on Cheese @skip(if: false) { dontSkipInlineId: id } ... on Cheese @include(if: true) { includeInlineId: id } ... on Cheese @include(if: false) { dontIncludeInlineId: id } ... @skip(if: true) { skipNoType: id } ... @skip(if: false) { dontSkipNoType: id } } } fragment includeFlavorField on Cheese { includeFlavor: flavor } fragment dontIncludeFlavorField on Cheese { dontIncludeFlavor: flavor } fragment skipFlavorField on Cheese { skipFlavor: flavor } fragment dontSkipFlavorField on Cheese { dontSkipFlavor: flavor } |} it "intercepts fragment spreads" do expected = { "data" => { "cheese" => { "dontSkipFlavor" => "Brie", "includeFlavor" => "Brie", "dontSkipInlineId" => 1, "includeInlineId" => 1, "dontSkipNoType" => 1, }, }} assert_equal(expected, result) end end describe "merging @skip and @include" do let(:field_included?) { r = result["data"]["cheese"]; r.has_key?('flavor') && r.has_key?('withVariables') } let(:skip?) { false } let(:include?) { true } let(:variables) { {"skip" => skip?, "include" => include?} } let(:query_string) {" query getCheese ($include: Boolean!, $skip: Boolean!) { cheese(id: 1) { flavor @include(if: #{include?}) @skip(if: #{skip?}), withVariables: flavor @include(if: $include) @skip(if: $skip) } } "} # behavior as defined in # https://github.com/facebook/graphql/blob/master/spec/Section%203%20--%20Type%20System.md#include describe "when @skip=false and @include=true" do let(:skip?) { false } let(:include?) { true } it "is included" do assert field_included? end end describe "when @skip=false and @include=false" do let(:skip?) { false } let(:include?) { false } it "is not included" do assert !field_included? end end describe "when @skip=true and @include=true" do let(:skip?) { true } let(:include?) { true } it "is not included" do assert !field_included? end end describe "when @skip=true and @include=false" do let(:skip?) { true } let(:include?) { false } it "is not included" do assert !field_included? end end describe "when evaluating skip on query selection and fragment" do describe "with @skip" do let(:query_string) {" query getCheese ($skip: Boolean!) { cheese(id: 1) { flavor, withVariables: flavor, ...F0 } } fragment F0 on Cheese { flavor @skip(if: #{skip?}) withVariables: flavor @skip(if: $skip) } "} describe "and @skip=false" do let(:skip?) { false } it "is included" do assert field_included? end end describe "and @skip=true" do let(:skip?) { true } it "is included" do assert field_included? end end end end describe "when evaluating conflicting @skip and @include on query selection and fragment" do let(:query_string) {" query getCheese ($include: Boolean!, $skip: Boolean!) { cheese(id: 1) { ... on Cheese @include(if: #{include?}) { flavor } withVariables: flavor @include(if: $include), ...F0 } } fragment F0 on Cheese { flavor @skip(if: #{skip?}), withVariables: flavor @skip(if: $skip) } "} describe "when @skip=false and @include=true" do let(:skip?) { false } let(:include?) { true } it "is included" do assert field_included? end end describe "when @skip=false and @include=false" do let(:skip?) { false } let(:include?) { false } it "is included" do assert field_included? end end describe "when @skip=true and @include=true" do let(:skip?) { true } let(:include?) { true } it "is included" do assert field_included? end end describe "when @skip=true and @include=false" do let(:skip?) { true } let(:include?) { false } it "is not included" do assert !field_included? end end end describe "when handling multiple fields at the same level" do describe "when at least one occurrence would be included" do let(:query_string) {" query getCheese ($include: Boolean!, $skip: Boolean!) { cheese(id: 1) { ... on Cheese { flavor } flavor @include(if: #{include?}), flavor @skip(if: #{skip?}), withVariables: flavor, withVariables: flavor @include(if: $include), withVariables: flavor @skip(if: $skip) } } "} let(:skip?) { true } let(:include?) { false } it "is included" do assert field_included? end end describe "when no occurrence would be included" do let(:query_string) {" query getCheese ($include: Boolean!, $skip: Boolean!) { cheese(id: 1) { flavor @include(if: #{include?}), flavor @skip(if: #{skip?}), withVariables: flavor @include(if: $include), withVariables: flavor @skip(if: $skip) } } "} let(:skip?) { true } let(:include?) { false } it "is not included" do assert !field_included? end end end end describe "defining a directive" do let(:directive) { GraphQL::Directive.define do arguments [GraphQL::Argument.define(name: 'skip')] end } it "can accept an array of arguments" do assert_equal 1, directive.arguments.length assert_equal 'skip', directive.arguments.first.name end it "is not default" do assert_equal false, directive.default_directive? end end end graphql-ruby-1.11.10/spec/graphql/enum_type_spec.rb000066400000000000000000000110631414121453000222200ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::EnumType do let(:enum) { Dummy::DairyAnimal.graphql_definition } it "coerces names to underlying values" do assert_equal("YAK", enum.coerce_isolated_input("YAK")) assert_equal(1, enum.coerce_isolated_input("COW")) end it "coerces invalid names to nil" do assert_equal(nil, enum.coerce_isolated_input("YAKKITY")) end it "coerces result values to value's value" do assert_equal("YAK", enum.coerce_isolated_result("YAK")) assert_equal("COW", enum.coerce_isolated_result(1)) assert_equal("REINDEER", enum.coerce_isolated_result('reindeer')) assert_equal("DONKEY", enum.coerce_isolated_result(:donkey)) end it "raises when a result value can't be coerced" do assert_raises(GraphQL::EnumType::UnresolvedValueError) { enum.coerce_isolated_result(:nonsense) } end describe "resolving with a warden" do it "gets values from the warden" do # OK assert_equal("YAK", enum.coerce_isolated_result("YAK")) # NOT OK assert_raises(GraphQL::EnumType::UnresolvedValueError) { enum.coerce_result("YAK", OpenStruct.new(warden: NothingWarden)) } end end describe "invalid values" do it "rejects value names with a space" do assert_raises(GraphQL::InvalidNameError) { InvalidEnumValueTest = GraphQL::EnumType.define do name "InvalidEnumValueTest" value("SPACE IN VALUE", "Invalid enum because it contains spaces", value: 1) end # Force evaluation InvalidEnumValueTest.name } end end describe "invalid name" do it "reject names with invalid format" do assert_raises(GraphQL::InvalidNameError) do InvalidEnumNameTest = GraphQL::EnumType.define do name "Some::Invalid::Name" end # Force evaluation InvalidEnumNameTest.name end end end describe "values that are Arrays" do let(:schema) { enum = GraphQL::EnumType.define do name "PluralEnum" value 'PETS', value: ["dogs", "cats"] value 'FRUITS', value: ["apples", "oranges"] value 'PLANETS', value: ["Earth"] end query_type = GraphQL::ObjectType.define do name "Query" field :names, types[types.String] do argument :things, types[enum] resolve ->(o, a, c) { a[:things].reduce(&:+) } end end GraphQL::Schema.define do query(query_type) end } it "accepts them as inputs" do res = schema.execute("{ names(things: [PETS, PLANETS]) }") assert_equal ["dogs", "cats", "Earth"], res["data"]["names"] end end it "accepts a symbol as a variant and Ruby-land value" do enum = GraphQL::EnumType.define do name 'MessageFormat' value :markdown end variant = enum.values['markdown'] assert_equal(variant.name, 'markdown') assert_equal(variant.value, :markdown) end it "has value description" do assert_equal("Animal with horns", enum.values["GOAT"].description) end describe "validate_input with bad input" do let(:result) { enum.validate_isolated_input("bad enum") } it "returns an invalid result" do assert(!result.valid?) assert_equal( result.problems.first['explanation'], "Expected \"bad enum\" to be one of: COW, DONKEY, GOAT, REINDEER, SHEEP, YAK" ) end end it "accepts values array" do cow = GraphQL::EnumType::EnumValue.define(name: "COW") goat = GraphQL::EnumType::EnumValue.define(name: "GOAT") enum = GraphQL::EnumType.define(name: "DairyAnimal", values: [cow, goat]) assert_equal({ "COW" => cow, "GOAT" => goat }, enum.values) end it "values respond to graphql_name" do cow = GraphQL::EnumType::EnumValue.define(name: "COW") assert_equal("COW", cow.graphql_name) end describe "#dup" do it "copies the values map without altering the original" do enum_2 = enum.dup enum_2.add_value(GraphQL::EnumType::EnumValue.define(name: "MUSKRAT")) assert_equal(6, enum.values.size) assert_equal(7, enum_2.values.size) end end describe "validates enum value name uniqueness" do it "raises an exception when adding a duplicate enum value name" do expected_message = "Enum value names must be unique. Value `COW` already exists on Enum `DairyAnimal`." exception = assert_raises(RuntimeError) do enum.add_value(GraphQL::EnumType::EnumValue.define(name: "COW")) end assert_equal(expected_message, exception.message) end end end graphql-ruby-1.11.10/spec/graphql/execution/000077500000000000000000000000001414121453000206565ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/graphql/execution/errors_spec.rb000066400000000000000000000150171414121453000235350ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe "GraphQL::Execution::Errors" do class ErrorsTestSchema < GraphQL::Schema class ErrorA < RuntimeError; end class ErrorB < RuntimeError; end class ErrorC < RuntimeError attr_reader :value def initialize(value:) @value = value super end end class ErrorD < RuntimeError; end class ErrorASubclass < ErrorA; end use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST use GraphQL::Execution::Errors rescue_from(ErrorA) do |err, obj, args, ctx, field| ctx[:errors] << "#{err.message} (#{field.owner.name}.#{field.graphql_name}, #{obj.inspect}, #{args.inspect})" nil end rescue_from(ErrorB) do |*| raise GraphQL::ExecutionError, "boom!" end rescue_from(ErrorC) do |err, *| err.value end rescue_from(ErrorD) do |err, obj, args, ctx, field| raise GraphQL::ExecutionError, "ErrorD on #{obj.inspect} at #{field ? "#{field.path}(#{args})" : "boot"}" end class Thing < GraphQL::Schema::Object def self.authorized?(obj, ctx) if ctx[:authorized] == false raise ErrorD end end field :string, String, null: false def string "a string" end end class ValuesInput < GraphQL::Schema::InputObject argument :value, Int, required: true, loads: Thing def self.object_from_id(type, value, ctx) if value == 1 :thing else raise ErrorD end end end class Query < GraphQL::Schema::Object field :f1, Int, null: true do argument :a1, Int, required: false end def f1(a1: nil) raise ErrorA, "f1 broke" end field :f2, Int, null: true def f2 -> { raise ErrorA, "f2 broke" } end field :f3, Int, null: true def f3 raise ErrorB end field :f4, Int, null: false def f4 raise ErrorC.new(value: 20) end field :f5, Int, null: true def f5 raise ErrorASubclass, "raised subclass" end field :f6, Int, null: true def f6 -> { raise ErrorB } end field :thing, Thing, null: true def thing :thing end field :input_field, Int, null: true do argument :values, ValuesInput, required: true, method_access: false end field :non_nullable_array, [String], null: false def non_nullable_array [nil] end end query(Query) lazy_resolve(Proc, :call) end class ErrorsTestSchemaWithoutInterpreter < GraphQL::Schema class Query < GraphQL::Schema::Object field :non_nullable_array, [String], null: false def non_nullable_array [nil] end end query(Query) end describe "rescue_from handling" do it "can replace values with `nil`" do ctx = { errors: [] } res = ErrorsTestSchema.execute "{ f1(a1: 1) }", context: ctx, root_value: :abc assert_equal({ "data" => { "f1" => nil } }, res) assert_equal ["f1 broke (ErrorsTestSchema::Query.f1, :abc, {:a1=>1})"], ctx[:errors] end it "rescues errors from lazy code" do ctx = { errors: [] } res = ErrorsTestSchema.execute("{ f2 }", context: ctx) assert_equal({ "data" => { "f2" => nil } }, res) assert_equal ["f2 broke (ErrorsTestSchema::Query.f2, nil, {})"], ctx[:errors] end it "rescues errors from lazy code with handlers that re-raise" do res = ErrorsTestSchema.execute("{ f6 }") expected_error = { "message"=>"boom!", "locations"=>[{"line"=>1, "column"=>3}], "path"=>["f6"] } assert_equal({ "data" => { "f6" => nil }, "errors" => [expected_error] }, res) end it "can raise new errors" do res = ErrorsTestSchema.execute("{ f3 }") expected_error = { "message"=>"boom!", "locations"=>[{"line"=>1, "column"=>3}], "path"=>["f3"] } assert_equal({ "data" => { "f3" => nil }, "errors" => [expected_error] }, res) end it "can replace values with non-nil" do res = ErrorsTestSchema.execute("{ f4 }") assert_equal({ "data" => { "f4" => 20 } }, res) end it "rescues subclasses" do context = { errors: [] } res = ErrorsTestSchema.execute("{ f5 }", context: context) assert_equal({ "data" => { "f5" => nil } }, res) assert_equal ["raised subclass (ErrorsTestSchema::Query.f5, nil, {})"], context[:errors] end describe "errors raised in authorized hooks" do it "rescues them" do context = { authorized: false } res = ErrorsTestSchema.execute(" { thing { string } } ", context: context) assert_equal ["ErrorD on nil at Query.thing({})"], res["errors"].map { |e| e["message"] } end end describe "errors raised in input_object loads" do it "rescues them from literal values" do context = { authorized: false } res = ErrorsTestSchema.execute(" { inputField(values: { value: 2 }) } ", root_value: :root, context: context) # It would be better to have the arguments here, but since this error was raised during _creation_ of keywords, # so the runtime arguments aren't available now. assert_equal ["ErrorD on :root at Query.inputField()"], res["errors"].map { |e| e["message"] } end it "rescues them from variable values" do context = { authorized: false } res = ErrorsTestSchema.execute( "query($values: ValuesInput!) { inputField(values: $values) } ", variables: { values: { "value" => 2 } }, context: context, ) assert_equal ["ErrorD on nil at Query.inputField()"], res["errors"].map { |e| e["message"] } end end describe "errors raised in non_nullable_array loads" do it "outputs the appropriate error message when using non-interpreter schema" do res = ErrorsTestSchemaWithoutInterpreter.execute("{ nonNullableArray }") expected_error = { "message" => "Cannot return null for non-nullable field Query.nonNullableArray" } assert_equal({ "data" => nil, "errors" => [expected_error] }, res) end it "outputs the appropriate error message when using interpreter schema" do res = ErrorsTestSchema.execute("{ nonNullableArray }") expected_error = { "message" => "Cannot return null for non-nullable field Query.nonNullableArray" } assert_equal({ "data" => nil, "errors" => [expected_error] }, res) end end end end graphql-ruby-1.11.10/spec/graphql/execution/execute_spec.rb000066400000000000000000000212701414121453000236610ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" ExecuteSuite = GraphQL::Compatibility::ExecutionSpecification.build_suite(GraphQL::Execution::Execute) LazyExecuteSuite = GraphQL::Compatibility::LazyExecutionSpecification.build_suite(GraphQL::Execution::Execute) describe GraphQL::Execution::Execute do describe "null propagation on mutation root" do module MutationNullTestRoot INTS = [] def self.push(args, ctx) if args[:int] == 13 nil else INTS << args[:int] args[:int] end end def ints INTS end end let(:root) { MutationNullTestRoot } let(:query_str) do <<-GRAPHQL mutation { one: push(int: 1) thirteen: push(int: 13) two: push(int: 2) } GRAPHQL end before do MutationNullTestRoot::INTS.clear end describe "when root fields are non-nullable" do let(:schema) { GraphQL::Schema.from_definition <<-GRAPHQL, interpreter: false type Mutation { push(int: Int!): Int! } type Query { ints: [Int!] } GRAPHQL } it "propagates null to the root mutation and halts mutation execution" do res = schema.execute(query_str, root_value: root) assert_equal [1], MutationNullTestRoot::INTS assert_equal(nil, res["data"]) end end describe 'mutation fields are nullable' do let(:schema) { GraphQL::Schema.from_definition <<-GRAPHQL type Mutation { push(int: Int!): Int } type Query { ints: [Int!] } GRAPHQL } it 'does not halt execution and returns data for the successful mutations' do res = schema.execute(query_str, root_value: root) assert_equal [1, 2], MutationNullTestRoot::INTS assert_equal({"one"=>1, "thirteen"=>nil, "two"=>2}, res["data"]) end end end describe "when a member of a list of non-null type returns nil" do let(:schema) { node_type = GraphQL::ObjectType.define do name "Node" field :id, types.ID, "" do resolve ->(obj, args, ctx) { obj[:id] } end end query_type = GraphQL::ObjectType.define do name "Query" field :nonNullListWithNullStrings, types[!types.String], "" do resolve ->(obj, args, ctx) { [nil] } end field :nonNullListWithNullStringsLazy, types[!types.String], "" do resolve ->(obj, args, ctx) { LazyHelpers::Wrapper.new([nil]) } end field :nonNullListWithNullTypes, types[!node_type], "" do resolve ->(obj, args, ctx) { [{ id: 1 }, nil, { id: 2 }] } end field :listWithNullStrings, types[types.String], "" do resolve ->(obj, args, ctx) { [nil, "hello"] } end field :listWithNullTypes, types[node_type], "" do resolve ->(obj, args, ctx) { [nil, { id: 1 }, nil] } end field :nonNullList, !types[!types.String], "" do resolve ->(obj, args, ctx) { [nil] } end end GraphQL::Schema.define do query query_type lazy_resolve(LazyHelpers::Wrapper, :item) end } it "propagates null for non-lazy resolvers" do query = <<-GRAPHQL { nonNullListWithNullStrings nonNullListWithNullTypes { id } listWithNullStrings listWithNullTypes { id } } GRAPHQL result = schema.execute(query).to_h assert_equal ["nonNullListWithNullStrings", "nonNullListWithNullTypes", "listWithNullStrings", "listWithNullTypes"], result["data"].keys assert_equal nil, result["data"]["nonNullListWithNullStrings"] assert_equal nil, result["data"]["nonNullListWithNullTypes"] assert_equal [nil, "hello"], result["data"]["listWithNullStrings"] assert_equal [nil, { "id" => "1" }, nil], result["data"]["listWithNullTypes"] end it "propagates null for lazy resolvers" do result = schema.execute("{ nonNullListWithNullStringsLazy }").to_h assert_equal ["nonNullListWithNullStringsLazy"], result["data"].keys assert_equal nil, result["data"]["nonNullListWithNullStringsLazy"] end it "propagates null for non-null lists of non-null types" do result = schema.execute("{ nonNullList }").to_h assert_equal nil, result["data"] end end describe "when a list member raises an error" do let(:schema) { thing_type = GraphQL::ObjectType.define do name "Thing" field :name, !types.String do resolve ->(o, a, c) { -> { raise GraphQL::ExecutionError.new("👻") } } end end query_type = GraphQL::ObjectType.define do name "Query" field :things, !types[!thing_type] do resolve ->(o, a, c) { [OpenStruct.new(name: "A")] } end field :nullableThings, !types[thing_type] do resolve ->(o, a, c) { [OpenStruct.new(name: "A")] } end end GraphQL::Schema.define do query query_type lazy_resolve(Proc, :call) end } it "handles the error & propagates the null" do res = schema.execute <<-GRAPHQL { things { name } } GRAPHQL assert_nil res["data"] assert_equal "👻", res["errors"].first["message"] end it "allows nulls" do res = schema.execute <<-GRAPHQL { nullableThings { name } } GRAPHQL assert_equal [nil], res["data"]["nullableThings"] assert_equal "👻", res["errors"].first["message"] end end describe "tracing" do if defined?(Rails) it "emits traces" do query_string = <<-GRAPHQL query Bases($id1: ID!, $id2: ID!){ b1: batchedBase(id: $id1) { name } b2: batchedBase(id: $id2) { name } } GRAPHQL first_id = StarWars::Base.first.id last_id = StarWars::Base.last.id traces = TestTracing.with_trace do star_wars_query(query_string, { "id1" => first_id, "id2" => last_id, }, context: {tracers: [TestTracing]}) end exec_traces = traces[5..-1] expected_traces = [ (TESTING_INTERPRETER ? "authorized" : nil), "execute_field", "execute_field", "execute_query", "lazy_loader", "execute_field_lazy", (TESTING_INTERPRETER ? "authorized" : nil), "execute_field", "execute_field_lazy", (TESTING_INTERPRETER ? "authorized" : nil), "execute_field", "execute_field_lazy", "execute_field_lazy", "execute_query_lazy", "execute_multiplex", ].compact assert_equal expected_traces, exec_traces.map { |t| t[:key] } if TESTING_INTERPRETER _authorized_1, field_1_eager, field_2_eager, query_eager, lazy_loader, # field 3 is eager-resolved _during_ field 1's lazy resolve field_1_lazy, _authorized_2, field_3_eager, field_2_lazy, _authorized_3, field_4_eager, # field 3 didn't finish above, it's resolved in the next round field_3_lazy, field_4_lazy, query_lazy, multiplex = exec_traces else field_1_eager, field_2_eager, query_eager, lazy_loader, # field 3 is eager-resolved _during_ field 1's lazy resolve field_1_lazy, field_3_eager, field_2_lazy, field_4_eager, # field 3 didn't finish above, it's resolved in the next round field_3_lazy, field_4_lazy, query_lazy, multiplex = exec_traces end assert_equal ["b1"], field_1_eager[:path] assert_equal ["b2"], field_2_eager[:path] assert_instance_of GraphQL::Query, query_eager[:query] assert_equal [first_id.to_s, last_id.to_s], lazy_loader[:ids] assert_equal StarWars::Base, lazy_loader[:model] assert_equal ["b1", "name"], field_3_eager[:path] assert_equal ["b1"], field_1_lazy[:path] assert_equal ["b2", "name"], field_4_eager[:path] assert_equal ["b2"], field_2_lazy[:path] assert_equal ["b1", "name"], field_3_lazy[:path] assert_equal ["b2", "name"], field_4_lazy[:path] assert_instance_of GraphQL::Query, query_lazy[:query] assert_instance_of GraphQL::Execution::Multiplex, multiplex[:multiplex] end end end end graphql-ruby-1.11.10/spec/graphql/execution/instrumentation_spec.rb000066400000000000000000000153341414121453000254660ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Schema do describe "instrumentation teardown bug" do # This instrumenter records that it ran, # or raises an error if instructed to do so class InstrumenterError < StandardError attr_reader :key def initialize(key) @key = key super() end end class LogInstrumenter def before_query(unit_of_work) run_hook(unit_of_work, "begin") end def after_query(unit_of_work) run_hook(unit_of_work, "end") end alias :before_multiplex :before_query alias :after_multiplex :after_query private def run_hook(unit_of_work, event_name) unit_of_work.context[log_key(event_name)] = true if unit_of_work.context[raise_key(event_name)] raise InstrumenterError.new(log_key(event_name)) end end def log_key(event_name) context_key("did_#{event_name}") end def raise_key(event_name) context_key("should_raise_#{event_name}") end def context_key(suffix) prefix = self.class.name.sub("Instrumenter", "").downcase :"#{prefix}_instrumenter_#{suffix}" end end class FirstInstrumenter < LogInstrumenter; end class SecondInstrumenter < LogInstrumenter; end class ExecutionErrorInstrumenter def before_query(query) if query.context[:raise_execution_error] raise GraphQL::ExecutionError, "Raised from instrumenter before_query" end end def after_query(query) end end # This is how you might add queries from a persisted query backend class QueryStringInstrumenter def before_query(query) if query.context[:extra_query_string] && query.query_string.nil? query.query_string = query.context[:extra_query_string] end end def after_query(query) end end let(:query_type) { Class.new(GraphQL::Schema::Object) do graphql_name "Query" field :int, Integer, null: true do argument :value, Integer, required: false end def int(value:) value end end } let(:schema) { spec = self Class.new(GraphQL::Schema) do query(spec.query_type) instrument(:query, FirstInstrumenter.new) instrument(:query, SecondInstrumenter.new) instrument(:query, ExecutionErrorInstrumenter.new) instrument(:query, QueryStringInstrumenter.new) if TESTING_INTERPRETER use GraphQL::Analysis::AST use GraphQL::Execution::Interpreter end end } describe "query instrumenters" do it "before_query of the 2nd instrumenter does not run but after_query does" do context = {second_instrumenter_should_raise_begin: true} assert_raises InstrumenterError do schema.execute(" { int(value: 2) } ", context: context) end assert context[:first_instrumenter_did_begin] assert context[:first_instrumenter_did_end] assert context[:second_instrumenter_did_begin] refute context[:second_instrumenter_did_end] end it "runs after_query even if a previous after_query raised an error" do context = {second_instrumenter_should_raise_end: true} err = assert_raises InstrumenterError do schema.execute(" { int(value: 2) } ", context: context) end # The error came from the second instrumenter: assert_equal :second_instrumenter_did_end, err.key # But the first instrumenter still got a chance to teardown assert context[:first_instrumenter_did_begin] assert context[:first_instrumenter_did_end] assert context[:second_instrumenter_did_begin] assert context[:second_instrumenter_did_end] end it "rescues execution errors from before_query" do context = {raise_execution_error: true} res = schema.execute(" { int(value: 2) } ", context: context) assert_equal "Raised from instrumenter before_query", res["errors"].first["message"] refute res.key?("data"), "The query doesn't run" end it "can assign a query string there" do context = { extra_query_string: "{ __typename }"} res = schema.execute(nil, context: context) assert_equal "Query", res["data"]["__typename"] end end describe "within a multiplex" do let(:multiplex_schema) { schema.redefine { instrument(:multiplex, FirstInstrumenter.new) instrument(:multiplex, SecondInstrumenter.new) } } it "only runs after_multiplex if before_multiplex finished" do multiplex_ctx = {second_instrumenter_should_raise_begin: true} query_1_ctx = {} query_2_ctx = {} assert_raises InstrumenterError do multiplex_schema.multiplex( [ {query: "{int(value: 1)}", context: query_1_ctx}, {query: "{int(value: 2)}", context: query_2_ctx}, ], context: multiplex_ctx ) end assert multiplex_ctx[:first_instrumenter_did_begin] assert multiplex_ctx[:first_instrumenter_did_end] assert multiplex_ctx[:second_instrumenter_did_begin] refute multiplex_ctx[:second_instrumenter_did_end] # No query instrumentation was run at all assert_equal 0, query_1_ctx.size assert_equal 0, query_2_ctx.size end it "does full and partial query runs" do multiplex_ctx = {} query_1_ctx = {} query_2_ctx = {second_instrumenter_should_raise_begin: true} assert_raises InstrumenterError do multiplex_schema.multiplex( [ { query: " { int(value: 2) } ", context: query_1_ctx }, { query: " { int(value: 2) } ", context: query_2_ctx }, ], context: multiplex_ctx ) end # multiplex got a full run assert multiplex_ctx[:first_instrumenter_did_begin] assert multiplex_ctx[:first_instrumenter_did_end] assert multiplex_ctx[:second_instrumenter_did_begin] assert multiplex_ctx[:second_instrumenter_did_end] # query 1 got a full run assert query_1_ctx[:first_instrumenter_did_begin] assert query_1_ctx[:first_instrumenter_did_end] assert query_1_ctx[:second_instrumenter_did_begin] assert query_1_ctx[:second_instrumenter_did_end] # query 2 got a partial run assert query_2_ctx[:first_instrumenter_did_begin] assert query_2_ctx[:first_instrumenter_did_end] assert query_2_ctx[:second_instrumenter_did_begin] refute query_2_ctx[:second_instrumenter_did_end] end end end end graphql-ruby-1.11.10/spec/graphql/execution/interpreter/000077500000000000000000000000001414121453000232215ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/graphql/execution/interpreter/arguments_spec.rb000066400000000000000000000023631414121453000265710ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe "GraphQL::Execution::Interpreter::Arguments" do class InterpreterArgsTestSchema < GraphQL::Schema class SearchParams < GraphQL::Schema::InputObject argument :query, String, required: false end class Query < GraphQL::Schema::Object field :search, [String], null: false do argument :params, SearchParams, required: false argument :limit, Int, required: true end end query(Query) use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST end def arguments(query_str) query = GraphQL::Query.new(InterpreterArgsTestSchema, query_str) ast_node = query.document.definitions.first.selections.first query_type = query.get_type("Query") field = query.get_field(query_type, "search") query.arguments_for(ast_node, field) end it "provides .dig" do query_str = <<-GRAPHQL { search(limit: 10, params: { query: "abcde" } ) } GRAPHQL args = arguments(query_str) assert_equal 10, args.dig(:limit) assert_equal "abcde", args.dig(:params, :query) assert_nil args.dig(:nothing) assert_nil args.dig(:params, :nothing) assert_nil args.dig(:nothing, :nothing, :nothing) end end graphql-ruby-1.11.10/spec/graphql/execution/interpreter_spec.rb000066400000000000000000000356411414121453000245710ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Execution::Interpreter do module InterpreterTest class Box def initialize(value: nil, &block) @value = value @block = block end def value if @block @value = @block.call @block = nil end @value end end class Expansion < GraphQL::Schema::Object field :sym, String, null: false field :lazy_sym, String, null: false field :name, String, null: false field :cards, ["InterpreterTest::Card"], null: false def self.authorized?(expansion, ctx) if expansion.sym == "NOPE" false else true end end def cards Query::CARDS.select { |c| c.expansion_sym == @object.sym } end def lazy_sym Box.new(value: object.sym) end field :null_union_field_test, Integer, null: false def null_union_field_test 1 end field :always_cached_value, Integer, null: false def always_cached_value raise "should never be called" end end class Card < GraphQL::Schema::Object field :name, String, null: false field :colors, "[InterpreterTest::Color]", null: false field :expansion, Expansion, null: false def expansion Query::EXPANSIONS.find { |e| e.sym == @object.expansion_sym } end field :null_union_field_test, Integer, null: true def null_union_field_test nil end end class Color < GraphQL::Schema::Enum value "WHITE" value "BLUE" value "BLACK" value "RED" value "GREEN" end class Entity < GraphQL::Schema::Union possible_types Card, Expansion def self.resolve_type(obj, ctx) obj.sym ? Expansion : Card end end class FieldCounter < GraphQL::Schema::Object implements GraphQL::Types::Relay::Node field :field_counter, FieldCounter, null: false def field_counter; self.class.generate_tag(context); end field :calls, Integer, null: false do argument :expected, Integer, required: true end def calls(expected:) c = context[:calls] += 1 if c != expected raise "Expected #{expected} calls but had #{c} so far" else c end end field :runtime_info, String, null: false do argument :a, Integer, required: false argument :b, Integer, required: false end def runtime_info(a: nil, b: nil) inspect_context end field :lazy_runtime_info, String, null: false do argument :a, Integer, required: false argument :b, Integer, required: false end def lazy_runtime_info(a: nil, b: nil) Box.new { inspect_context } end def self.generate_tag(context) context[:field_counters_count] ||= 0 current_count = context[:field_counters_count] += 1 "field_counter_#{current_count}" end private def inspect_context "<#{interpreter_context_for(:current_object).object.inspect}> #{interpreter_context_for(:current_path)} -> #{interpreter_context_for(:current_field).path}(#{interpreter_context_for(:current_arguments).size})" end def interpreter_context_for(key) # Make sure it's set in query context and interpreter namespace. base_ctx_value = context[key] interpreter_ctx_value = context.namespace(:interpreter)[key] if base_ctx_value != interpreter_ctx_value raise "Context mismatch for #{key} -> #{base_ctx_value} / intepreter: #{interpreter_ctx_value}" else base_ctx_value end end end class Query < GraphQL::Schema::Object # Try a root-level authorized hook that returns a lazy value def self.authorized?(obj, ctx) Box.new(value: true) end field :card, Card, null: true do argument :name, String, required: true end def card(name:) Box.new(value: CARDS.find { |c| c.name == name }) end field :expansion, Expansion, null: true do argument :sym, String, required: true end def expansion(sym:) EXPANSIONS.find { |e| e.sym == sym } end field :expansion_raw, Expansion, null: false def expansion_raw raw_value(sym: "RAW", name: "Raw expansion", always_cached_value: 42) end field :expansions, [Expansion], null: false def expansions EXPANSIONS end CARDS = [ OpenStruct.new(name: "Dark Confidant", colors: ["BLACK"], expansion_sym: "RAV"), ] EXPANSIONS = [ OpenStruct.new(name: "Ravnica, City of Guilds", sym: "RAV"), # This data has an error, for testing null propagation OpenStruct.new(name: nil, sym: "XYZ"), # This is not allowed by .authorized?, OpenStruct.new(name: nil, sym: "NOPE"), ] field :find, [Entity], null: false do argument :id, [ID], required: true end def find(id:) id.map do |ent_id| Query::EXPANSIONS.find { |e| e.sym == ent_id } || Query::CARDS.find { |c| c.name == ent_id } end end field :find_many, [Entity, null: true], null: false do argument :ids, [ID], required: true end def find_many(ids:) find(id: ids).map { |e| Box.new(value: e) } end field :field_counter, FieldCounter, null: false def field_counter; FieldCounter.generate_tag(context) ; end field :node, field: GraphQL::Relay::Node.field field :nodes, field: GraphQL::Relay::Node.plural_field end class Counter < GraphQL::Schema::Object field :value, Integer, null: false field :lazy_value, Integer, null: false def lazy_value Box.new { object.value } end field :increment, Counter, null: false def increment object.value += 1 object end end class Mutation < GraphQL::Schema::Object field :increment_counter, Counter, null: false def increment_counter counter = context[:counter] counter.value += 1 counter end end class Schema < GraphQL::Schema use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST query(Query) mutation(Mutation) lazy_resolve(Box, :value) def self.object_from_id(id, ctx) OpenStruct.new(id: id) end def self.resolve_type(type, obj, ctx) FieldCounter end end end it "runs a query" do query_string = <<-GRAPHQL query($expansion: String!, $id1: ID!, $id2: ID!){ card(name: "Dark Confidant") { colors expansion { ... { name } cards { name } } } expansion(sym: $expansion) { ... ExpansionFields } find(id: [$id1, $id2]) { __typename ... on Card { name } ... on Expansion { sym } } } fragment ExpansionFields on Expansion { cards { name } } GRAPHQL vars = {expansion: "RAV", id1: "Dark Confidant", id2: "RAV"} result = InterpreterTest::Schema.execute(query_string, variables: vars) assert_equal ["BLACK"], result["data"]["card"]["colors"] assert_equal "Ravnica, City of Guilds", result["data"]["card"]["expansion"]["name"] assert_equal [{"name" => "Dark Confidant"}], result["data"]["card"]["expansion"]["cards"] assert_equal [{"name" => "Dark Confidant"}], result["data"]["expansion"]["cards"] expected_abstract_list = [ {"__typename" => "Card", "name" => "Dark Confidant"}, {"__typename" => "Expansion", "sym" => "RAV"}, ] assert_equal expected_abstract_list, result["data"]["find"] end it "runs mutation roots atomically and sequentially" do query_str = <<-GRAPHQL mutation { i1: incrementCounter { value lazyValue i2: increment { value lazyValue } i3: increment { value lazyValue } } i4: incrementCounter { value lazyValue } i5: incrementCounter { value lazyValue } } GRAPHQL result = InterpreterTest::Schema.execute(query_str, context: { counter: OpenStruct.new(value: 0) }) expected_data = { "i1" => { "value" => 1, # All of these get `3` as lazy value. They're resolved together, # since they aren't _root_ mutation fields. "lazyValue" => 3, "i2" => { "value" => 2, "lazyValue" => 3 }, "i3" => { "value" => 3, "lazyValue" => 3 }, }, "i4" => { "value" => 4, "lazyValue" => 4}, "i5" => { "value" => 5, "lazyValue" => 5}, } assert_equal expected_data, result["data"] end it "runs skip and include" do query_str = <<-GRAPHQL query($truthy: Boolean!, $falsey: Boolean!){ exp1: expansion(sym: "RAV") @skip(if: true) { name } exp2: expansion(sym: "RAV") @skip(if: false) { name } exp3: expansion(sym: "RAV") @include(if: true) { name } exp4: expansion(sym: "RAV") @include(if: false) { name } exp5: expansion(sym: "RAV") @include(if: $truthy) { name } exp6: expansion(sym: "RAV") @include(if: $falsey) { name } } GRAPHQL vars = {truthy: true, falsey: false} result = InterpreterTest::Schema.execute(query_str, variables: vars) expected_data = { "exp2" => {"name" => "Ravnica, City of Guilds"}, "exp3" => {"name" => "Ravnica, City of Guilds"}, "exp5" => {"name" => "Ravnica, City of Guilds"}, } assert_equal expected_data, result["data"] end describe "temporary interpreter flag" do it "is set" do # This can be removed later, just a sanity check during migration res = InterpreterTest::Schema.execute("{ __typename }") assert_equal true, res.context.interpreter? end end describe "runtime info in context" do it "is available" do res = InterpreterTest::Schema.execute <<-GRAPHQL { fieldCounter { runtimeInfo(a: 1, b: 2) fieldCounter { runtimeInfo lazyRuntimeInfo(a: 1) } } } GRAPHQL assert_equal '<"field_counter_1"> ["fieldCounter", "runtimeInfo"] -> FieldCounter.runtimeInfo(2)', res["data"]["fieldCounter"]["runtimeInfo"] # These are both `field_counter_2`, but one is lazy assert_equal '<"field_counter_2"> ["fieldCounter", "fieldCounter", "runtimeInfo"] -> FieldCounter.runtimeInfo(0)', res["data"]["fieldCounter"]["fieldCounter"]["runtimeInfo"] assert_equal '<"field_counter_2"> ["fieldCounter", "fieldCounter", "lazyRuntimeInfo"] -> FieldCounter.lazyRuntimeInfo(1)', res["data"]["fieldCounter"]["fieldCounter"]["lazyRuntimeInfo"] end end describe "CI setup" do it "sets interpreter based on a constant" do # Force the plugins to be applied Jazz::Schema.graphql_definition Dummy::Schema.graphql_definition if TESTING_INTERPRETER assert_equal GraphQL::Execution::Interpreter, Jazz::Schema.query_execution_strategy else refute_equal GraphQL::Execution::Interpreter, Jazz::Schema.query_execution_strategy end end end describe "null propagation" do it "propagates nulls" do query_str = <<-GRAPHQL { expansion(sym: "XYZ") { name sym lazySym } } GRAPHQL res = InterpreterTest::Schema.execute(query_str) # Although the expansion was found, its name of `nil` # propagated to here assert_nil res["data"].fetch("expansion") assert_equal ["Cannot return null for non-nullable field Expansion.name"], res["errors"].map { |e| e["message"] } end it "propagates nulls in lists" do query_str = <<-GRAPHQL { expansions { name sym lazySym } } GRAPHQL res = InterpreterTest::Schema.execute(query_str) # A null in one of the list items removed the whole list assert_nil(res["data"]) end it "works with unions that fail .authorized?" do res = InterpreterTest::Schema.execute <<-GRAPHQL { find(id: "NOPE") { ... on Expansion { sym } } } GRAPHQL assert_equal ["Cannot return null for non-nullable field Query.find"], res["errors"].map { |e| e["message"] } end it "works with lists of unions" do res = InterpreterTest::Schema.execute <<-GRAPHQL { findMany(ids: ["RAV", "NOPE", "BOGUS"]) { ... on Expansion { sym } } } GRAPHQL assert_equal 3, res["data"]["findMany"].size assert_equal "RAV", res["data"]["findMany"][0]["sym"] assert_equal nil, res["data"]["findMany"][1] assert_equal nil, res["data"]["findMany"][2] assert_equal false, res.key?("errors") end it "works with union lists that have members of different kinds, with different nullabilities" do res = InterpreterTest::Schema.execute <<-GRAPHQL { findMany(ids: ["RAV", "Dark Confidant"]) { ... on Expansion { nullUnionFieldTest } ... on Card { nullUnionFieldTest } } } GRAPHQL assert_equal [1, nil], res["data"]["findMany"].map { |f| f["nullUnionFieldTest"] } end end describe "duplicated fields" do it "doesn't run them multiple times" do query_str = <<-GRAPHQL { fieldCounter { calls(expected: 1) # This should not be called since it matches the above calls(expected: 1) fieldCounter { calls(expected: 2) } ...ExtraFields } } fragment ExtraFields on FieldCounter { fieldCounter { # This should not be called since it matches the inline field: calls(expected: 2) # This _should_ be called c3: calls(expected: 3) } } GRAPHQL # It will raise an error if it doesn't match the expectation res = InterpreterTest::Schema.execute(query_str, context: { calls: 0 }) assert_equal 3, res["data"]["fieldCounter"]["fieldCounter"]["c3"] end end describe "backwards compatibility" do it "handles a legacy nodes field" do res = InterpreterTest::Schema.execute('{ node(id: "abc") { id } }') assert_equal "abc", res["data"]["node"]["id"] res = InterpreterTest::Schema.execute('{ nodes(ids: ["abc", "xyz"]) { id } }') assert_equal ["abc", "xyz"], res["data"]["nodes"].map { |n| n["id"] } end end describe "returning raw values" do it "returns raw value" do query_str = <<-GRAPHQL { expansionRaw { name sym alwaysCachedValue } } GRAPHQL res = InterpreterTest::Schema.execute(query_str) assert_equal({ sym: "RAW", name: "Raw expansion", always_cached_value: 42 }, res["data"]["expansionRaw"]) end end end graphql-ruby-1.11.10/spec/graphql/execution/lazy/000077500000000000000000000000001414121453000216355ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/graphql/execution/lazy/lazy_method_map_spec.rb000066400000000000000000000030751414121453000263550ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Execution::Lazy::LazyMethodMap do def self.test_lazy_method_map it "handles multithreaded access" do a = Class.new b = Class.new(a) c = Class.new(b) lazy_method_map.set(a, :a) threads = 1000.times.map do |i| Thread.new { d = Class.new(c) assert_equal :a, lazy_method_map.get(d.new) } end threads.map(&:join) end it "dups" do a = Class.new b = Class.new(a) c = Class.new(b) lazy_method_map.set(a, :a) lazy_method_map.get(b.new) lazy_method_map.get(c.new) dup_map = lazy_method_map.dup assert_equal 3, dup_map.instance_variable_get(:@storage).size assert_equal :a, dup_map.get(a.new) assert_equal :a, dup_map.get(b.new) assert_equal :a, dup_map.get(c.new) end end describe "with a plain hash" do let(:lazy_method_map) { GraphQL::Execution::Lazy::LazyMethodMap.new(use_concurrent: false) } test_lazy_method_map it "has a Ruby Hash inside" do storage = lazy_method_map .instance_variable_get(:@storage) .instance_variable_get(:@storage) assert_instance_of Hash, storage end end describe "with a Concurrent::Map" do let(:lazy_method_map) { GraphQL::Execution::Lazy::LazyMethodMap.new(use_concurrent: true) } test_lazy_method_map it "has a Concurrent::Map inside" do storage = lazy_method_map.instance_variable_get(:@storage) assert_instance_of Concurrent::Map, storage end end end graphql-ruby-1.11.10/spec/graphql/execution/lazy_spec.rb000066400000000000000000000144411414121453000232000ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Execution::Lazy do include LazyHelpers describe "resolving" do it "calls value handlers" do res = run_query('{ int(value: 2, plus: 1) }') assert_equal 3, res["data"]["int"] end it "Works with Query.new" do query_str = '{ int(value: 2, plus: 1) }' query = GraphQL::Query.new(LazyHelpers::LazySchema, query_str) res = query.result assert_equal 3, res["data"]["int"] end it "can do nested lazy values" do res = run_query %| { a: nestedSum(value: 3) { value nestedSum(value: 7) { value nestedSum(value: 1) { value nestedSum(value: -50) { value } } } } b: nestedSum(value: 2) { value nestedSum(value: 11) { value nestedSum(value: 2) { value nestedSum(value: -50) { value } } } } c: listSum(values: [1,2]) { nestedSum(value: 3) { value } } } | expected_data = { "a"=>{"value"=>14, "nestedSum"=>{ "value"=>46, "nestedSum"=>{ "value"=>95, "nestedSum"=>{"value"=>90} } }}, "b"=>{"value"=>14, "nestedSum"=>{ "value"=>46, "nestedSum"=>{ "value"=>95, "nestedSum"=>{"value"=>90} } }}, "c"=>[ {"nestedSum"=>{"value"=>14}}, {"nestedSum"=>{"value"=>14}} ], } assert_equal expected_data, res["data"] end [ [1, 2, LazyHelpers::MAGIC_NUMBER_WITH_LAZY_AUTHORIZED_HOOK], [2, LazyHelpers::MAGIC_NUMBER_WITH_LAZY_AUTHORIZED_HOOK, 1], [LazyHelpers::MAGIC_NUMBER_WITH_LAZY_AUTHORIZED_HOOK, 1, 2], ].each do |ordered_values| it "resolves each field at one depth before proceeding to the next depth (using #{ordered_values})" do res = run_query <<-GRAPHQL, variables: { values: ordered_values } query($values: [Int!]!) { listSum(values: $values) { nestedSum(value: 3) { value } } } GRAPHQL # Even though magic number `44`'s `.authorized?` hook returns a lazy value, # these fields should be resolved together and return the same value. assert_equal 56, res["data"]["listSum"][0]["nestedSum"]["value"] assert_equal 56, res["data"]["listSum"][1]["nestedSum"]["value"] assert_equal 56, res["data"]["listSum"][2]["nestedSum"]["value"] end end it "Handles fields that return nil" do values = [ LazyHelpers::MAGIC_NUMBER_THAT_RETURNS_NIL, LazyHelpers::MAGIC_NUMBER_WITH_LAZY_AUTHORIZED_HOOK, 1, 2, ] res = run_query <<-GRAPHQL, variables: { values: values } query($values: [Int!]!) { listSum(values: $values) { nullableNestedSum(value: 3) { value } } } GRAPHQL values = res["data"]["listSum"].map { |s| s && s["nullableNestedSum"]["value"] } assert_equal [nil, 56, 56, 56], values end it "propagates nulls to the root" do res = run_query %| { nestedSum(value: 1) { value nestedSum(value: 2) { nestedSum(value: 13) { value } } } }| assert_equal(nil, res["data"]) assert_equal 1, res["errors"].length end it "propagates partial nulls" do res = run_query %| { nullableNestedSum(value: 1) { value nullableNestedSum(value: 2) { ns: nestedSum(value: 13) { value } } } }| expected_data = { "nullableNestedSum" => { "value" => 1, "nullableNestedSum" => nil, } } assert_equal(expected_data, res["data"]) assert_equal 1, res["errors"].length end it "handles raised errors" do res = run_query %| { a: nullableNestedSum(value: 1) { value } b: nullableNestedSum(value: 13) { value } c: nullableNestedSum(value: 2) { value } }| expected_data = { "a" => { "value" => 3 }, "b" => nil, "c" => { "value" => 3 }, } assert_equal expected_data, res["data"] expected_errors = [{ "message"=>"13 is unlucky", "locations"=>[{"line"=>4, "column"=>9}], "path"=>["b"], }] assert_equal expected_errors, res["errors"] end it "resolves mutation fields right away" do res = run_query %| { a: nestedSum(value: 2) { value } b: nestedSum(value: 4) { value } c: nestedSum(value: 6) { value } }| assert_equal [12, 12, 12], res["data"].values.map { |d| d["value"] } res = run_query %| mutation { a: nestedSum(value: 2) { value } b: nestedSum(value: 4) { value } c: nestedSum(value: 6) { value } } | assert_equal [2, 4, 6], res["data"].values.map { |d| d["value"] } end end describe "Schema#sync_lazy(object)" do it "Passes objects to that hook at runtime" do res = run_query <<-GRAPHQL { a: nullableNestedSum(value: 1001) { value } b: nullableNestedSum(value: 1013) { value } c: nullableNestedSum(value: 1002) { value } } GRAPHQL # This odd, non-adding behavior is hacked into `#sync_lazy` assert_equal 101, res["data"]["a"]["value"] assert_equal 113, res["data"]["b"]["value"] assert_equal 102, res["data"]["c"]["value"] end end describe "LazyMethodMap" do class SubWrapper < LazyHelpers::Wrapper; end let(:map) { GraphQL::Execution::Lazy::LazyMethodMap.new } it "finds methods for classes and subclasses" do map.set(LazyHelpers::Wrapper, :item) map.set(LazyHelpers::SumAll, :value) b = LazyHelpers::Wrapper.new(1) sub_b = LazyHelpers::Wrapper.new(2) s = LazyHelpers::SumAll.new(3) assert_equal(:item, map.get(b)) assert_equal(:item, map.get(sub_b)) assert_equal(:value, map.get(s)) end end end graphql-ruby-1.11.10/spec/graphql/execution/lookahead_spec.rb000066400000000000000000000326611414121453000241540ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Execution::Lookahead do module LookaheadTest DATA = [ OpenStruct.new(name: "Cardinal", is_waterfowl: false, similar_species_names: ["Scarlet Tanager"], genus: OpenStruct.new(latin_name: "Piranga")), OpenStruct.new(name: "Scarlet Tanager", is_waterfowl: false, similar_species_names: ["Cardinal"], genus: OpenStruct.new(latin_name: "Cardinalis")), OpenStruct.new(name: "Great Egret", is_waterfowl: false, similar_species_names: ["Great Blue Heron"], genus: OpenStruct.new(latin_name: "Ardea")), OpenStruct.new(name: "Great Blue Heron", is_waterfowl: true, similar_species_names: ["Great Egret"], genus: OpenStruct.new(latin_name: "Ardea")), ] def DATA.find_by_name(name) DATA.find { |b| b.name == name } end module Node include GraphQL::Schema::Interface field :id, ID, null: false end class BirdGenus < GraphQL::Schema::Object implements Node field :name, String, null: false field :latin_name, String, null: false field :id, ID, null: false, method: :latin_name end class BirdSpecies < GraphQL::Schema::Object implements Node field :name, String, null: false field :id, ID, null: false, method: :name field :is_waterfowl, Boolean, null: false field :similar_species, [BirdSpecies], null: false def similar_species object.similar_species_names.map { |n| DATA.find_by_name(n) } end field :genus, BirdGenus, null: false, extras: [:lookahead] def genus(lookahead:) if lookahead.selects?(:latin_name) context[:lookahead_latin_name] += 1 end object.genus end end class Query < GraphQL::Schema::Object field :find_bird_species, BirdSpecies, null: true do argument :by_name, String, required: true end def find_bird_species(by_name:) DATA.find_by_name(by_name) end field :node, Node, null: true do argument :id, ID, required: true end def node(id:) if (node = DATA.find_by_name(id)) node else DATA.map { |d| d.genus }.select { |g| g.name == id } end end end class LookaheadInstrumenter def self.before_query(query) query.context[:root_lookahead_selections] = query.lookahead.selections end def self.after_query(q) end end class Schema < GraphQL::Schema query(Query) instrument :query, LookaheadInstrumenter if TESTING_INTERPRETER use GraphQL::Execution::Interpreter use GraphQL::Analysis::AST end end end describe "looking ahead" do let(:document) { GraphQL.parse <<-GRAPHQL query($name: String!){ findBirdSpecies(byName: $name) { name similarSpecies { likesWater: isWaterfowl } } t: __typename } GRAPHQL } let(:query) { GraphQL::Query.new(LookaheadTest::Schema, document: document, variables: { name: "Cardinal" }) } it "has a good test setup" do res = query.result assert_equal [false], res["data"]["findBirdSpecies"]["similarSpecies"].map { |s| s["likesWater"] } end it "can detect fields on objects with symbol or string" do lookahead = query.lookahead.selection("findBirdSpecies") assert_equal true, lookahead.selects?("similarSpecies") assert_equal true, lookahead.selects?(:similar_species) assert_equal false, lookahead.selects?("isWaterfowl") assert_equal false, lookahead.selects?(:is_waterfowl) end it "detects by name, not by alias" do assert_equal true, query.lookahead.selects?("__typename") end describe "fields on interfaces" do let(:document) { GraphQL.parse <<-GRAPHQL query { node(id: "Cardinal") { id ... on BirdSpecies { name } ...Other } } fragment Other on BirdGenus { latinName } GRAPHQL } it "finds fields on object types and interface types" do node_lookahead = query.lookahead.selection("node") assert_equal [:id, :name, :latin_name], node_lookahead.selections.map(&:name) end end describe "inspect" do it "works for root lookaheads" do assert_includes query.lookahead.inspect, "# "Cardinal" }) end it "is true when no constraints are given" do assert_equal true, lookahead.selects?(:find_bird_species, arguments: {}) assert_equal true, lookahead.selects?("__typename", arguments: {}) end it "is false when some given constraints aren't satisfied" do assert_equal false, lookahead.selects?(:find_bird_species, arguments: { by_name: "Chickadee" }) assert_equal false, lookahead.selects?(:find_bird_species, arguments: { by_name: "Cardinal", other: "Nonsense" }) end describe "with literal values" do let(:document) { GraphQL.parse <<-GRAPHQL { findBirdSpecies(byName: "Great Blue Heron") { isWaterfowl } } GRAPHQL } it "works" do assert_equal true, lookahead.selects?(:find_bird_species, arguments: { by_name: "Great Blue Heron" }) end end end it "can do a chained lookahead" do next_lookahead = query.lookahead.selection(:find_bird_species, arguments: { by_name: "Cardinal" }) assert_equal true, next_lookahead.selected? nested_selection = next_lookahead.selection(:similar_species).selection(:is_waterfowl, arguments: {}) assert_equal true, nested_selection.selected? assert_equal false, next_lookahead.selection(:similar_species).selection(:name).selected? end it "can detect fields on lists with symbol or string" do assert_equal true, query.lookahead.selection(:find_bird_species).selection(:similar_species).selection(:is_waterfowl).selected? assert_equal true, query.lookahead.selection("findBirdSpecies").selection("similarSpecies").selection("isWaterfowl").selected? end describe "merging branches and fragments" do let(:document) { GraphQL.parse <<-GRAPHQL { findBirdSpecies(name: "Cardinal") { similarSpecies { __typename } } ...F ... { findBirdSpecies(name: "Cardinal") { similarSpecies { isWaterfowl } } } } fragment F on Query { findBirdSpecies(name: "Cardinal") { similarSpecies { name } } } GRAPHQL } it "finds selections using merging" do merged_lookahead = query.lookahead.selection(:find_bird_species).selection(:similar_species) assert merged_lookahead.selects?(:__typename) assert merged_lookahead.selects?(:is_waterfowl) assert merged_lookahead.selects?(:name) end end end describe "in queries" do it "can be an extra" do query_str = <<-GRAPHQL { cardinal: findBirdSpecies(byName: "Cardinal") { genus { __typename } } scarletTanager: findBirdSpecies(byName: "Scarlet Tanager") { genus { latinName } } greatBlueHeron: findBirdSpecies(byName: "Great Blue Heron") { genus { latinName } } } GRAPHQL context = {lookahead_latin_name: 0} res = LookaheadTest::Schema.execute(query_str, context: context) refute res.key?("errors") assert_equal 2, context[:lookahead_latin_name] assert_equal [:find_bird_species], context[:root_lookahead_selections].map(&:name).uniq assert_equal( [{ by_name: "Cardinal" }, { by_name: "Scarlet Tanager" }, { by_name: "Great Blue Heron" }], context[:root_lookahead_selections].map(&:arguments) ) end it "works for invalid queries" do context = {lookahead_latin_name: 0} res = LookaheadTest::Schema.execute("{ doesNotExist }", context: context) assert res.key?("errors") assert_equal 0, context[:lookahead_latin_name] end end describe '#selections' do let(:document) { GraphQL.parse <<-GRAPHQL query { findBirdSpecies(byName: "Laughing Gull") { name similarSpecies { likesWater: isWaterfowl } } } GRAPHQL } def query(doc = document) GraphQL::Query.new(LookaheadTest::Schema, document: doc) end it "provides a list of all selections" do ast_node = document.definitions.first.selections.first field = LookaheadTest::Query.fields["findBirdSpecies"] lookahead = GraphQL::Execution::Lookahead.new(query: query, ast_nodes: [ast_node], field: field) assert_equal [:name, :similar_species], lookahead.selections.map(&:name) end it "filters outs selections which do not match arguments" do ast_node = document.definitions.first lookahead = GraphQL::Execution::Lookahead.new(query: query, ast_nodes: [ast_node], root_type: LookaheadTest::Query) arguments = { by_name: "Cardinal" } assert_equal lookahead.selections(arguments: arguments).map(&:name), [] end it "includes selections which match arguments" do ast_node = document.definitions.first lookahead = GraphQL::Execution::Lookahead.new(query: query, ast_nodes: [ast_node], root_type: LookaheadTest::Query) arguments = { by_name: "Laughing Gull" } assert_equal lookahead.selections(arguments: arguments).map(&:name), [:find_bird_species] end it 'handles duplicate selections across fragments' do doc = GraphQL.parse <<-GRAPHQL query { ... on Query { ...MoreFields } } fragment MoreFields on Query { findBirdSpecies(byName: "Laughing Gull") { name } findBirdSpecies(byName: "Laughing Gull") { ...EvenMoreFields } } fragment EvenMoreFields on BirdSpecies { similarSpecies { likesWater: isWaterfowl } } GRAPHQL lookahead = query(doc).lookahead root_selections = lookahead.selections assert_equal [:find_bird_species], root_selections.map(&:name), "Selections are merged" assert_equal 2, root_selections.first.ast_nodes.size, "It represents both nodes" assert_equal [:name, :similar_species], root_selections.first.selections.map(&:name), "Subselections are merged" end it "avoids merging selections for same field name on distinct types" do query = GraphQL::Query.new(LookaheadTest::Schema, <<-GRAPHQL) query { node(id: "Cardinal") { ... on BirdSpecies { name } ... on BirdGenus { name } id } } GRAPHQL node_lookahead = query.lookahead.selection("node") assert_equal( [[LookaheadTest::Node, :id], [LookaheadTest::BirdSpecies, :name], [LookaheadTest::BirdGenus, :name]], node_lookahead.selections.map { |s| [s.owner_type, s.name] } ) end it "works for missing selections" do ast_node = document.definitions.first.selections.first field = LookaheadTest::Query.fields["findBirdSpecies"] lookahead = GraphQL::Execution::Lookahead.new(query: query, ast_nodes: [ast_node], field: field) null_lookahead = lookahead.selection(:genus) # This is an implementation detail, but I want to make sure the test is set up right assert_instance_of GraphQL::Execution::Lookahead::NullLookahead, null_lookahead assert_equal [], null_lookahead.selections end it "excludes fields skipped by directives" do document = GraphQL.parse <<-GRAPHQL query($skipName: Boolean!, $includeGenus: Boolean!){ findBirdSpecies(byName: "Cardinal") { id name @skip(if: $skipName) genus @include(if: $includeGenus) } } GRAPHQL query = GraphQL::Query.new(LookaheadTest::Schema, document: document, variables: { skipName: false, includeGenus: true }) lookahead = query.lookahead.selection("findBirdSpecies") assert_equal [:id, :name, :genus], lookahead.selections.map(&:name) assert_equal true, lookahead.selects?(:name) query = GraphQL::Query.new(LookaheadTest::Schema, document: document, variables: { skipName: true, includeGenus: false }) lookahead = query.lookahead.selection("findBirdSpecies") assert_equal [:id], lookahead.selections.map(&:name) assert_equal false, lookahead.selects?(:name) end end end graphql-ruby-1.11.10/spec/graphql/execution/multiplex_spec.rb000066400000000000000000000155061414121453000242470ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Execution::Multiplex do def multiplex(*a, **kw) LazyHelpers::LazySchema.multiplex(*a, **kw) end let(:q1) { <<-GRAPHQL query Q1 { nestedSum(value: 3) { value nestedSum(value: 7) { value } } } GRAPHQL } let(:q2) { <<-GRAPHQL query Q2 { nestedSum(value: 2) { value nestedSum(value: 11) { value } } } GRAPHQL } let(:q3) { <<-GRAPHQL query Q3 { listSum(values: [1,2]) { nestedSum(value: 3) { value } } } GRAPHQL } let(:queries) { [{query: q1}, {query: q2}, {query: q3}] } describe "multiple queries in the same lazy context" do it "runs multiple queries in the same lazy context" do expected_data = [ {"data"=>{"nestedSum"=>{"value"=>14, "nestedSum"=>{"value"=>46}}}}, {"data"=>{"nestedSum"=>{"value"=>14, "nestedSum"=>{"value"=>46}}}}, {"data"=>{"listSum"=>[{"nestedSum"=>{"value"=>14}}, {"nestedSum"=>{"value"=>14}}]}}, ] res = multiplex(queries) assert_equal expected_data, res end it "returns responses in the same order as their respective requests" do queries = 2000.times.map do |index| case index % 3 when 0 {query: q1} when 1 {query: q2} when 2 {query: q3} end end responses = multiplex(queries) responses.each.with_index do |response, index| case index % 3 when 0 assert_equal "Q1", response.query.operation_name when 1 assert_equal "Q2", response.query.operation_name when 2 assert_equal "Q3", response.query.operation_name end end end end describe "when some have validation errors or runtime errors" do let(:q1) { " { success: nullableNestedSum(value: 1) { value } }" } let(:q2) { " { runtimeError: nullableNestedSum(value: 13) { value } }" } let(:q3) { "{ invalidNestedNull: nullableNestedSum(value: 1) { value nullableNestedSum(value: 2) { nestedSum(value: 13) { value } # This field will never get executed ns2: nestedSum(value: 13) { value } } } }" } let(:q4) { " { validationError: nullableNestedSum(value: true) }"} it "returns a mix of errors and values" do expected_res = [ { "data"=>{"success"=>{"value"=>2}} }, { "data"=>{"runtimeError"=>nil}, "errors"=>[{ "message"=>"13 is unlucky", "locations"=>[{"line"=>1, "column"=>4}], "path"=>["runtimeError"] }] }, { "data"=>{"invalidNestedNull"=>{"value" => 2,"nullableNestedSum" => nil}}, "errors"=>[{"message"=>"Cannot return null for non-nullable field LazySum.nestedSum"}], }, { "errors" => [{ "message"=>"Field must have selections (field 'nullableNestedSum' returns LazySum but has no selections. Did you mean 'nullableNestedSum { ... }'?)", "locations"=>[{"line"=>1, "column"=>4}], "path"=>["query", "validationError"], "extensions"=>{"code"=>"selectionMismatch", "nodeName"=>"field 'nullableNestedSum'", "typeName"=>"LazySum"} }] }, ] res = multiplex([ {query: q1}, {query: q2}, {query: q3}, {query: q4}, ]) assert_equal expected_res, res.map(&:to_h) end end describe "context shared by a multiplex run" do it "is provided as context:" do checks = [] multiplex(queries, context: { instrumentation_checks: checks }) assert_equal ["before multiplex 1", "before multiplex 2", "after multiplex 2", "after multiplex 1"], checks end end describe "instrumenting a multiplex run" do it "runs query instrumentation for each query and multiplex-level instrumentation" do checks = [] queries_with_context = queries.map { |q| q.merge(context: { instrumentation_checks: checks }) } multiplex(queries_with_context, context: { instrumentation_checks: checks }) assert_equal [ "before multiplex 1", "before multiplex 2", "before Q1", "before Q2", "before Q3", "after Q3", "after Q2", "after Q1", "after multiplex 2", "after multiplex 1", ], checks end end describe "max_complexity" do it "can successfully calculate complexity" do message = "Query has complexity of 11, which exceeds max complexity of 10" results = multiplex(queries, max_complexity: 10) results.each do |res| assert_equal message, res["errors"][0]["message"] end end end describe "after_query when errors are raised" do class InspectQueryInstrumentation class << self attr_reader :last_json def before_query(query) end def after_query(query) @last_json = query.result.to_json end end end InspectQueryType = GraphQL::ObjectType.define do name "Query" field :raiseExecutionError, types.String do resolve ->(object, args, ctx) { raise GraphQL::ExecutionError, "Whoops" } end field :raiseError, types.String do resolve ->(object, args, ctx) { raise GraphQL::Error, "Crash" } end field :raiseSyntaxError, types.String do resolve ->(object, args, ctx) { raise SyntaxError } end field :raiseException, types.String do resolve ->(object, args, ctx) { raise Exception } end end InspectSchema = GraphQL::Schema.define do query InspectQueryType instrument(:query, InspectQueryInstrumentation) end unhandled_err_json = '{}' it "can access the query results" do InspectSchema.execute("{ raiseExecutionError }") handled_err_json = '{"data":{"raiseExecutionError":null},"errors":[{"message":"Whoops","locations":[{"line":1,"column":3}],"path":["raiseExecutionError"]}]}' assert_equal handled_err_json, InspectQueryInstrumentation.last_json assert_raises(GraphQL::Error) do InspectSchema.execute("{ raiseError }") end assert_equal unhandled_err_json, InspectQueryInstrumentation.last_json end it "can access the query results when the error is not a StandardError" do assert_raises(SyntaxError) do InspectSchema.execute("{ raiseSyntaxError }") end assert_equal unhandled_err_json, InspectQueryInstrumentation.last_json assert_raises(Exception) do InspectSchema.execute("{ raiseException }") end assert_equal unhandled_err_json, InspectQueryInstrumentation.last_json end end end graphql-ruby-1.11.10/spec/graphql/execution/typecast_spec.rb000066400000000000000000000052551414121453000240600ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Execution::Typecast do describe ".subtype?" do def subtype?(*args) GraphQL::Execution::Typecast.subtype?(*args) end it "counts the same type as a subtype" do assert subtype?(Dummy::Milk.graphql_definition, Dummy::Milk.graphql_definition) assert !subtype?(Dummy::Milk.graphql_definition, Dummy::Cheese.graphql_definition) assert subtype?(Dummy::Milk.graphql_definition.to_list_type.to_non_null_type, Dummy::Milk.graphql_definition.to_list_type.to_non_null_type) end it "counts member types as subtypes" do assert subtype?(Dummy::Edible.graphql_definition, Dummy::Cheese.graphql_definition) assert subtype?(Dummy::Edible.graphql_definition, Dummy::Milk.graphql_definition) assert subtype?(Dummy::DairyProduct.graphql_definition, Dummy::Milk.graphql_definition) assert subtype?(Dummy::DairyProduct.graphql_definition, Dummy::Cheese.graphql_definition) assert !subtype?(Dummy::DairyAppQuery.graphql_definition, Dummy::DairyProduct.graphql_definition) assert !subtype?(Dummy::Cheese.graphql_definition, Dummy::DairyProduct.graphql_definition) assert !subtype?(Dummy::Edible.graphql_definition, Dummy::DairyProduct.graphql_definition) assert !subtype?(Dummy::Edible.graphql_definition, GraphQL::STRING_TYPE) assert !subtype?(Dummy::Edible.graphql_definition, Dummy::DairyProductInput.graphql_definition) end it "counts lists as subtypes if their inner types are subtypes" do assert subtype?(Dummy::Edible.graphql_definition.to_list_type, Dummy::Milk.graphql_definition.to_list_type) assert subtype?(Dummy::DairyProduct.graphql_definition.to_list_type, Dummy::Milk.graphql_definition.to_list_type) assert !subtype?(Dummy::Cheese.graphql_definition.to_list_type, Dummy::DairyProduct.graphql_definition.to_list_type) assert !subtype?(Dummy::Edible.graphql_definition.to_list_type, Dummy::DairyProduct.graphql_definition.to_list_type) assert !subtype?(Dummy::Edible.graphql_definition.to_list_type, GraphQL::STRING_TYPE.to_list_type) end it "counts non-null types as subtypes of nullable parent types" do assert subtype?(Dummy::Milk.graphql_definition, Dummy::Milk.graphql_definition.to_non_null_type) assert subtype?(Dummy::Edible.graphql_definition, Dummy::Milk.graphql_definition.to_non_null_type) assert subtype?(Dummy::Edible.graphql_definition.to_non_null_type, Dummy::Milk.graphql_definition.to_non_null_type) assert subtype?( GraphQL::STRING_TYPE.to_non_null_type.to_list_type, GraphQL::STRING_TYPE.to_non_null_type.to_list_type.to_non_null_type, ) end end end graphql-ruby-1.11.10/spec/graphql/execution_error_spec.rb000066400000000000000000000233651414121453000234370ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::ExecutionError do let(:result) { Dummy::Schema.execute(query_string) } describe "when returned from a field" do let(:query_string) {%| { cheese(id: 1) { id error1: similarCheese(source: [YAK]) { ... similarCheeseFields } error2: similarCheese(source: [YAK]) { ... similarCheeseFields } nonError: similarCheese(source: [SHEEP]) { ... similarCheeseFields } flavor } allDairy { ... on Cheese { flavor } ... on Milk { source executionError } } dairyErrors: allDairy(executionErrorAtIndex: 1) { __typename } dairy { milks { source executionError allDairy { __typename ... on Milk { origin executionError } } } } executionError valueWithExecutionError } fragment similarCheeseFields on Cheese { id, flavor } |} it "the error is inserted into the errors key and the rest of the query is fulfilled" do expected_result = { "data"=>{ "cheese"=>{ "id" => 1, "error1"=> nil, "error2"=> nil, "nonError"=> { "id" => 3, "flavor" => "Manchego", }, "flavor" => "Brie", }, "allDairy" => [ { "flavor" => "Brie" }, { "flavor" => "Gouda" }, { "flavor" => "Manchego" }, { "source" => "COW", "executionError" => nil } ], "dairyErrors" => [ { "__typename" => "Cheese" }, nil, { "__typename" => "Cheese" }, { "__typename" => "Milk" } ], "dairy" => { "milks" => [ { "source" => "COW", "executionError" => nil, "allDairy" => [ { "__typename" => "Cheese" }, { "__typename" => "Cheese" }, { "__typename" => "Cheese" }, { "__typename" => "Milk", "origin" => "Antiquity", "executionError" => nil } ] } ] }, "executionError" => nil, "valueWithExecutionError" => 0 }, "errors"=>[ { "message"=>"missing dairy", "locations"=>[{"line"=>25, "column"=>5}], "path"=>["dairyErrors", 1] }, { "message"=>"There was an execution error", "locations"=>[{"line"=>31, "column"=>9}], "path"=>["dairy", "milks", 0, "executionError"] }, { "message"=>"There was an execution error", "locations"=>[{"line"=>41, "column"=>5}], "path"=>["executionError"] }, { "message"=>"Could not fetch latest value", "locations"=>[{"line"=>42, "column"=>5}], "path"=>["valueWithExecutionError"] }, { "message"=>"No cheeses are made from Yak milk!", "locations"=>[{"line"=>5, "column"=>7}], "path"=>["cheese", "error1"] }, { "message"=>"No cheeses are made from Yak milk!", "locations"=>[{"line"=>8, "column"=>7}], "path"=>["cheese", "error2"] }, { "message"=>"There was an execution error", "locations"=>[{"line"=>22, "column"=>9}], "path"=>["allDairy", 3, "executionError"] }, { "message"=>"There was an execution error", "locations"=>[{"line"=>36, "column"=>13}], "path"=>["dairy", "milks", 0, "allDairy", 3, "executionError"] }, ] } assert_equal(expected_result, result.to_h) end end describe "named query when returned from a field" do let(:query_string) {%| query MilkQuery { dairy { milks { source executionError allDairy { __typename ... on Milk { origin executionError } } } } } |} it "the error is inserted into the errors key and the rest of the query is fulfilled" do expected_result = { "data"=>{ "dairy" => { "milks" => [ { "source" => "COW", "executionError" => nil, "allDairy" => [ { "__typename" => "Cheese" }, { "__typename" => "Cheese" }, { "__typename" => "Cheese" }, { "__typename" => "Milk", "origin" => "Antiquity", "executionError" => nil } ] } ] } }, "errors"=>[ { "message"=>"There was an execution error", "locations"=>[{"line"=>6, "column"=>11}], "path"=>["dairy", "milks", 0, "executionError"] }, { "message"=>"There was an execution error", "locations"=>[{"line"=>11, "column"=>15}], "path"=>["dairy", "milks", 0, "allDairy", 3, "executionError"] } ] } assert_equal(expected_result, result) end end describe "fragment query when returned from a field" do let(:query_string) {%| query MilkQuery { dairy { ...Dairy } } fragment Dairy on Dairy { milks { source executionError allDairy { __typename ...Milk } } } fragment Milk on Milk { origin executionError } |} it "the error is inserted into the errors key and the rest of the query is fulfilled" do expected_result = { "data"=>{ "dairy" => { "milks" => [ { "source" => "COW", "executionError" => nil, "allDairy" => [ { "__typename" => "Cheese" }, { "__typename" => "Cheese" }, { "__typename" => "Cheese" }, { "__typename" => "Milk", "origin" => "Antiquity", "executionError" => nil } ] } ] } }, "errors"=>[ { "message"=>"There was an execution error", "locations"=>[{"line"=>11, "column"=>9}], "path"=>["dairy", "milks", 0, "executionError"] }, { "message"=>"There was an execution error", "locations"=>[{"line"=>21, "column"=>7}], "path"=>["dairy", "milks", 0, "allDairy", 3, "executionError"] } ] } assert_equal(expected_result, result) end end describe "options in ExecutionError" do let(:query_string) {%| { executionErrorWithOptions } |} it "the error is inserted into the errors key and the rest of the query is fulfilled" do expected_result = { "data"=>{"executionErrorWithOptions"=>nil}, "errors"=> [{"message"=>"Permission Denied!", "locations"=>[{"line"=>3, "column"=>7}], "path"=>["executionErrorWithOptions"], "code"=>"permission_denied"}] } assert_equal(expected_result, result) end end describe "extensions in ExecutionError" do let(:query_string) {%| { executionErrorWithExtensions } |} it "the error is inserted into the errors key with custom data set in `extensions`" do expected_result = { "data"=>{"executionErrorWithExtensions"=>nil}, "errors"=> [{"message"=>"Permission Denied!", "locations"=>[{"line"=>3, "column"=>7}], "path"=>["executionErrorWithExtensions"], "extensions"=>{"code"=>"permission_denied"}}] } assert_equal(expected_result, result) end end describe "more than one ExecutionError" do let(:query_string) { %|{ multipleErrorsOnNonNullableField} |} it "the errors are inserted into the errors key and the data is nil even for a NonNullable field" do expected_result = { "data"=>nil, "errors"=> [{"message"=>"This is an error message for some error.", "locations"=>[{"line"=>1, "column"=>3}], "path"=>["multipleErrorsOnNonNullableField"]}, {"message"=>"This is another error message for a different error.", "locations"=>[{"line"=>1, "column"=>3}], "path"=>["multipleErrorsOnNonNullableField"]}], } assert_equal(expected_result, result) end describe "more than one ExecutionError on a field defined to return a list" do let(:query_string) { %|{ multipleErrorsOnNonNullableListField} |} it "the errors are inserted into the errors key and the data is nil even for a NonNullable field" do expected_result = { "data"=>nil, "errors"=> [{"message"=>"The first error message for a field defined to return a list of strings.", "locations"=>[{"line"=>1, "column"=>3}], "path"=>["multipleErrorsOnNonNullableListField", 0]}, {"message"=>"The second error message for a field defined to return a list of strings.", "locations"=>[{"line"=>1, "column"=>3}], "path"=>["multipleErrorsOnNonNullableListField", 1]}], } assert_equal(expected_result, result) end end end end graphql-ruby-1.11.10/spec/graphql/field_spec.rb000066400000000000000000000160021414121453000212740ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" # Must be top-level so it can be found by string FieldSpecReturnType = GraphQL::ObjectType.define do name "FieldReturn" field :id, types.Int field :source, types.String, hash_key: :source end describe GraphQL::Field do it "accepts a proc as type" do field = GraphQL::Field.define do type(-> { FieldSpecReturnType }) end assert_equal(FieldSpecReturnType, field.type) end it "accepts a string as a type" do field = GraphQL::Field.define do type("FieldSpecReturnType") end assert_equal(FieldSpecReturnType, field.type) end it "accepts arguments definition" do number = GraphQL::Argument.define(name: :number, type: -> { GraphQL::INT_TYPE }) field = GraphQL::Field.define(type: FieldSpecReturnType, arguments: [number]) assert_equal([number], field.arguments) end describe ".property " do let(:field) do GraphQL::Field.define do name "field_name" # satisfies 'can define by config' below property :internal_prop end end it "can define by config" do assert_equal(field.property, :internal_prop) end it "has nil property if not defined" do no_prop_field = GraphQL::Field.define { } assert_equal(no_prop_field.property, nil) end describe "default resolver" do def acts_like_default_resolver(field, old_prop, new_prop) object = OpenStruct.new(old_prop => "old value", new_prop => "new value", field.name.to_sym => "unset value") old_result = field.resolve(object, nil, nil) field.property = new_prop new_result = field.resolve(object, nil, nil) field.property = nil unset_result = field.resolve(object, nil, nil) assert_equal(old_result, "old value") assert_equal(new_result, "new value") assert_equal(unset_result, "unset value") end it "responds to changes in property" do acts_like_default_resolver(field, :internal_prop, :new_prop) end it "is reassigned if resolve is set to nil" do field.resolve = nil acts_like_default_resolver(field, :internal_prop, :new_prop) end end end describe "#name" do it "must be a string" do dummy_query = GraphQL::ObjectType.define do name "QueryType" end invalid_field = GraphQL::Field.new invalid_field.type = dummy_query invalid_field.name = :symbol_name dummy_query.fields["symbol_name"] = invalid_field err = assert_raises(GraphQL::Schema::InvalidTypeError) { GraphQL::Schema.define(query: dummy_query, raise_definition_error: true) } assert_equal "QueryType is invalid: field :symbol_name name must return String, not Symbol (:symbol_name)", err.message end end describe "#hash_key" do let(:source_field) { FieldSpecReturnType.get_field("source") } after { source_field.hash_key = :source } it "looks up a value with obj[hash_key]" do resolved_source = source_field.resolve({source: "Abc", "source" => "Xyz"}, nil, nil) assert_equal :source, source_field.hash_key assert_equal "Abc", resolved_source end it "can be reassigned" do source_field.hash_key = "source" resolved_source = source_field.resolve({source: "Abc", "source" => "Xyz"}, nil, nil) assert_equal "source", source_field.hash_key assert_equal "Xyz", resolved_source end end describe "#metadata" do it "accepts user-defined metadata" do similar_cheese_field = Dummy::Cheese.graphql_definition.get_field("similarCheese") assert_equal [:cheeses, :milks], similar_cheese_field.metadata[:joins] end end describe "reusing a GraphQL::Field" do let(:schema) { int_field = GraphQL::Field.define do type types.Int argument :value, types.Int resolve ->(obj, args, ctx) { args[:value] } end query_type = GraphQL::ObjectType.define do name "Query" field :int, int_field field :int2, int_field field :int3, field: int_field end GraphQL::Schema.define do query(query_type) end } it "can be used in two places" do res = schema.execute %|{ int(value: 1) int2(value: 2) int3(value: 3) }| assert_equal({ "int" => 1, "int2" => 2, "int3" => 3}, res["data"], "It works in queries") res = schema.execute %|{ __type(name: "Query") { fields { name } } }| query_field_names = res["data"]["__type"]["fields"].map { |f| f["name"] } assert_equal ["int", "int2", "int3"], query_field_names, "It works in introspection" end end describe "#redefine" do it "can add arguments" do int_field = GraphQL::Field.define do argument :value, types.Int end int_field_2 = int_field.redefine do argument :value_2, types.Int end assert_equal 1, int_field.arguments.size assert_equal 2, int_field_2.arguments.size end it "rebuilds when the resolve_proc is default NameResolve" do int_field = GraphQL::Field.define do name "a" end int_field_2 = int_field.redefine(name: "b") object = Struct.new(:a, :b).new(1, 2) assert_equal 1, int_field.resolve_proc.call(object, nil, nil) assert_equal 2, int_field_2.resolve_proc.call(object, nil, nil) end it "keeps the same resolve_proc when it is not a NameResolve" do int_field = GraphQL::Field.define do name "a" resolve ->(obj, _, _) { 'GraphQL is Kool' } end int_field_2 = int_field.redefine(name: "b") assert_equal( int_field.resolve_proc.call(nil, nil, nil), int_field_2.resolve_proc.call(nil, nil, nil) ) end it "keeps the same resolve_proc when it is a built in property resolve" do int_field = GraphQL::Field.define do name "a" property :c end int_field_2 = int_field.redefine(name: "b") object = Struct.new(:a, :b, :c).new(1, 2, 3) assert_equal 3, int_field.resolve_proc.call(object, nil, nil) assert_equal 3, int_field_2.resolve_proc.call(object, nil, nil) end it "copies metadata, even out-of-bounds assignments" do int_field = GraphQL::Field.define do metadata(:a, 1) argument :value, types.Int end int_field.metadata[:b] = 2 int_field_2 = int_field.redefine do metadata(:c, 3) argument :value_2, types.Int end assert_equal({a: 1, b: 2}, int_field.metadata) assert_equal({a: 1, b: 2, c: 3}, int_field_2.metadata) end end describe "#resolve_proc" do it "ensures the definition was called" do class SimpleResolver def self.call(*args) :whatever end end field_with_proc = GraphQL::Field.define do resolve ->(o, a, c) { :whatever } end field_with_class = GraphQL::Field.define do resolve SimpleResolver end assert_respond_to field_with_proc.resolve_proc, :call assert_respond_to field_with_class.resolve_proc, :call end end end graphql-ruby-1.11.10/spec/graphql/float_type_spec.rb000066400000000000000000000011431414121453000223570ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::FLOAT_TYPE do let(:enum) { GraphQL::Language::Nodes::Enum.new(name: 'MILK') } describe "coerce_input" do it "accepts ints and floats" do assert_equal 1.0, GraphQL::FLOAT_TYPE.coerce_isolated_input(1) assert_equal 6.1, GraphQL::FLOAT_TYPE.coerce_isolated_input(6.1) end it "rejects other types" do assert_nil GraphQL::FLOAT_TYPE.coerce_isolated_input("55") assert_nil GraphQL::FLOAT_TYPE.coerce_isolated_input(true) assert_nil GraphQL::FLOAT_TYPE.coerce_isolated_input(enum) end end end graphql-ruby-1.11.10/spec/graphql/function_spec.rb000066400000000000000000000107741414121453000220500ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Function do TestFuncPayload = GraphQL::ObjectType.define do name "TestFuncPayload" field :name, types.String, hash_key: :name end class TestFunc < GraphQL::Function argument :name, GraphQL::STRING_TYPE argument :age, types.Int type TestFuncPayload description "Returns the string you give it" deprecation_reason "It's useless" complexity 9 def call(o, a, c) { name: a[:name] } end end class TestFuncConn < GraphQL::Function argument :name, GraphQL::STRING_TYPE argument :age, types.Int type TestFuncPayload.connection_type description "Returns the string you give it" deprecation_reason "It's useless" complexity 9 def call(o, a, c) [{ name: a[:name] }] end end describe "function API" do it "exposes required info" do f = TestFunc.new assert_equal ["name", "age"], f.arguments.keys assert_equal "TestFuncPayload", f.type.name assert_equal "Returns the string you give it", f.description assert_equal "It's useless", f.deprecation_reason assert_equal({name: "stuff"}, f.call(nil, { name: "stuff" }, nil)) assert_equal 9, f.complexity assert_equal TestFunc.new.type, TestFunc.new.type end end it "has default values" do default_func = GraphQL::Function.new assert_equal 1, default_func.complexity assert_equal({}, default_func.arguments) assert_equal(nil, default_func.type) assert_equal(nil, default_func.description) assert_equal(nil, default_func.deprecation_reason) assert_raises(GraphQL::RequiredImplementationMissingError) { default_func.call(nil, nil, nil) } end describe "use in a schema" do let(:schema) { query_type = GraphQL::ObjectType.define do name "Query" field :test, function: TestFunc.new connection :testConn, function: TestFuncConn.new end relay_mutation = GraphQL::Relay::Mutation.define do name "Test" function TestFunc.new end mutation_type = GraphQL::ObjectType.define do name "Mutation" field :test, field: relay_mutation.field end GraphQL::Schema.define do query(query_type) mutation(mutation_type) end } it "gets attributes from the function" do field = schema.query.fields["test"] assert_equal ["name", "age"], field.arguments.keys assert_equal "TestFuncPayload", field.type.name assert_equal "Returns the string you give it", field.description assert_equal "It's useless", field.deprecation_reason assert_equal({name: "stuff"}, field.resolve(nil, { name: "stuff" }, nil)) assert_equal 9, field.complexity end it "can be used as a field" do query_str = <<-GRAPHQL { test(name: "graphql") { name }} GRAPHQL res = schema.execute(query_str) assert_equal "graphql", res["data"]["test"]["name"] end it "can be used as a connection" do query_str = <<-GRAPHQL { testConn(name: "graphql") { edges { node { name } } } } GRAPHQL res = schema.execute(query_str) assert_equal "graphql", res["data"]["testConn"]["edges"][0]["node"]["name"] end it "can be used as a mutation" do query_str = <<-GRAPHQL mutation { test(input: {clientMutationId: "123", name: "graphql"}) { name, clientMutationId } } GRAPHQL res = schema.execute(query_str) assert_equal "graphql", res["data"]["test"]["name"] end end describe "when overriding" do let(:schema) { query_type = GraphQL::ObjectType.define do name "Query" field :blockOverride, function: TestFunc.new do description "I have altered the description" argument :anArg, types.Int argument :oneMoreArg, types.String end field :argOverride, types.String, "New Description", function: TestFunc.new end GraphQL::Schema.define do query(query_type) end } it "can override description" do field = schema.query.fields["blockOverride"] assert_equal "I have altered the description", field.description assert_equal ["name", "age", "anArg", "oneMoreArg"], field.arguments.keys end it "can add to arguments" do field = schema.query.fields["argOverride"] assert_equal "New Description", field.description assert_equal GraphQL::STRING_TYPE, field.type assert_equal ["name", "age"], field.arguments.keys end end end graphql-ruby-1.11.10/spec/graphql/id_type_spec.rb000066400000000000000000000022411414121453000216460ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::ID_TYPE do let(:result) { Dummy::Schema.execute(query_string)} describe "coercion for int inputs" do let(:query_string) { %|query getMilk { cow: milk(id: 1) { id } }| } it "coerces IDs from ints and serializes as strings" do expected = {"data" => {"cow" => {"id" => "1"}}} assert_equal(expected, result) end end describe "coercion for string inputs" do let(:query_string) { %|query getMilk { cow: milk(id: "1") { id } }| } it "coerces IDs from strings and serializes as strings" do expected = {"data" => {"cow" => {"id" => "1"}}} assert_equal(expected, result) end end describe "coercion for float" do let(:query_string) { %|query getMilk { cow: milk(id: 1.0) { id } }| } it "results in an error" do assert_nil result["data"] assert_equal 1, result["errors"].length end end describe "coercion for enum values" do let(:query_string) { %|query getMilk { milk(id: dairy_rocks) { id } }|} it "results in an error" do assert_nil result["data"] assert_equal 1, result["errors"].length end end end graphql-ruby-1.11.10/spec/graphql/input_object_type_spec.rb000066400000000000000000000027361414121453000237500ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' describe GraphQL::InputObjectType do describe 'default values' do describe 'when the type is an enum with underlying ruby values' do it 'provides the default value' do TestEnum = GraphQL::EnumType.define do name 'Test' value 'A', 'Represents an authorized agent in our system.', value: 'a' value 'B', 'Agent is disabled, web app access is denied.', value: 'b' end class TestInput < GraphQL::Schema::InputObject argument :foo, TestEnum, 'TestEnum', required: false, default_value: 'a' end test_input_type = TestInput.to_graphql default_test_input_value = test_input_type.coerce_isolated_input({}) assert_equal default_test_input_value[:foo], 'a' end end describe "when it's an empty object" do it "is passed in" do input_obj = GraphQL::InputObjectType.define do name "InputObj" argument :s, types.String end query = GraphQL::ObjectType.define do name "Query" field(:f, types.String) do argument(:arg, input_obj, default_value: {}) resolve ->(obj, args, ctx) { args[:arg].to_h.inspect } end end schema = GraphQL::Schema.define do query(query) end res = schema.execute("{ f } ") assert_equal "{}", res["data"]["f"] end end end end graphql-ruby-1.11.10/spec/graphql/int_type_spec.rb000066400000000000000000000031771414121453000220550ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::INT_TYPE do describe "coerce_input" do it "accepts ints within the bounds" do assert_equal -(2**31), GraphQL::INT_TYPE.coerce_isolated_input(-(2**31)) assert_equal 1, GraphQL::INT_TYPE.coerce_isolated_input(1) assert_equal (2**31)-1, GraphQL::INT_TYPE.coerce_isolated_input((2**31)-1) end it "rejects other types and ints outside the bounds" do assert_nil GraphQL::INT_TYPE.coerce_isolated_input("55") assert_nil GraphQL::INT_TYPE.coerce_isolated_input(true) assert_nil GraphQL::INT_TYPE.coerce_isolated_input(6.1) assert_nil GraphQL::INT_TYPE.coerce_isolated_input(2**31) assert_nil GraphQL::INT_TYPE.coerce_isolated_input(-(2**31 + 1)) end describe "handling boundaries" do let(:context) { GraphQL::Query.new(Dummy::Schema, "{ __typename }").context } it "accepts result values in bounds" do assert_equal 0, GraphQL::INT_TYPE.coerce_result(0, context) assert_equal (2**31) - 1, GraphQL::INT_TYPE.coerce_result((2**31) - 1, context) assert_equal -(2**31), GraphQL::INT_TYPE.coerce_result(-(2**31), context) end it "replaces values, if configured to do so" do assert_equal Dummy::Schema::MAGIC_INT_COERCE_VALUE, GraphQL::INT_TYPE.coerce_result(99**99, context) end it "raises on values out of bounds" do assert_raises(GraphQL::IntegerEncodingError) { GraphQL::INT_TYPE.coerce_result(2**31, context) } assert_raises(GraphQL::IntegerEncodingError) { GraphQL::INT_TYPE.coerce_result(-(2**31 + 1), context) } end end end end graphql-ruby-1.11.10/spec/graphql/interface_type_spec.rb000066400000000000000000000130451414121453000232160ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::InterfaceType do let(:interface) { Dummy::Edible.graphql_definition } let(:dummy_query_context) { OpenStruct.new(schema: Dummy::Schema) } it "has possible types" do expected_defns = [Dummy::Cheese, Dummy::Milk, Dummy::Honey, Dummy::Aspartame] assert_equal(expected_defns, Dummy::Schema.possible_types(interface)) end describe "query evaluation" do let(:result) { Dummy::Schema.execute(query_string, variables: {"cheeseId" => 2})} let(:query_string) {%| query fav { favoriteEdible { fatContent } } |} it "gets fields from the type for the given object" do expected = {"data"=>{"favoriteEdible"=>{"fatContent"=>0.04}}} assert_equal(expected, result) end end describe "mergable query evaluation" do let(:result) { Dummy::Schema.execute(query_string, variables: {"cheeseId" => 2})} let(:query_string) {%| query fav { favoriteEdible { fatContent } favoriteEdible { origin } } |} it "gets fields from the type for the given object" do expected = {"data"=>{"favoriteEdible"=>{"fatContent"=>0.04, "origin"=>"Antiquity"}}} assert_equal(expected, result) end end describe "fragments" do let(:query_string) {%| { favoriteEdible { fatContent ... on LocalProduct { origin } } } |} let(:result) { Dummy::Schema.execute(query_string) } it "can apply interface fragments to an interface" do expected_result = { "data" => { "favoriteEdible" => { "fatContent" => 0.04, "origin" => "Antiquity", } } } assert_equal(expected_result, result) end describe "filtering members by type" do let(:query_string) {%| { allEdible { __typename ... on LocalProduct { origin } } } |} it "only applies fields to the right object" do expected_data = [ {"__typename"=>"Cheese", "origin"=>"France"}, {"__typename"=>"Cheese", "origin"=>"Netherlands"}, {"__typename"=>"Cheese", "origin"=>"Spain"}, {"__typename"=>"Milk", "origin"=>"Antiquity"}, ] assert_equal expected_data, result["data"]["allEdible"] end end end describe "#dup" do it "copies the fields without altering the original" do interface_2 = interface.dup interface_2.fields["extra"] = GraphQL::Field.define(name: "extra", type: GraphQL::BOOLEAN_TYPE) assert_equal 3, interface.fields.size assert_equal 4, interface_2.fields.size end it "copies orphan types without affecting the original" do interface = GraphQL::InterfaceType.define do name "AInterface" orphan_types [Dummy::Honey] end interface_2 = interface.dup interface_2.orphan_types << Dummy::Cheese assert_equal 1, interface.orphan_types.size assert_equal 2, interface_2.orphan_types.size end end describe "#resolve_type" do let(:result) { Dummy::Schema.execute(query_string) } let(:query_string) {%| { allEdible { __typename ... on Milk { milkFatContent: fatContent } ... on Cheese { cheeseFatContent: fatContent } } allEdibleAsMilk { __typename ... on Milk { fatContent } } } |} it 'returns correct types for general schema and specific interface' do expected_result = { # Uses schema-level resolve_type "allEdible"=>[ {"__typename"=>"Cheese", "cheeseFatContent"=>0.19}, {"__typename"=>"Cheese", "cheeseFatContent"=>0.3}, {"__typename"=>"Cheese", "cheeseFatContent"=>0.065}, {"__typename"=>"Milk", "milkFatContent"=>0.04} ], # Uses type-level resolve_type "allEdibleAsMilk"=>[ {"__typename"=>"Milk", "fatContent"=>0.19}, {"__typename"=>"Milk", "fatContent"=>0.3}, {"__typename"=>"Milk", "fatContent"=>0.065}, {"__typename"=>"Milk", "fatContent"=>0.04} ] } assert_equal expected_result, result["data"] end end describe "#get_possible_type" do let(:query_string) {%| query fav { favoriteEdible { fatContent } } |} let(:query) { GraphQL::Query.new(Dummy::Schema, query_string) } it "returns the type definition if the type exists and is a possible type of the interface" do assert interface.get_possible_type("Cheese", query.context) end it "returns nil if the type is not found in the schema" do assert_nil interface.get_possible_type("Foo", query.context) end it "returns nil if the type is not a possible type of the interface" do assert_nil interface.get_possible_type("Beverage", query.context) end end describe "#possible_type?" do let(:query_string) {%| query fav { favoriteEdible { fatContent } } |} let(:query) { GraphQL::Query.new(Dummy::Schema, query_string) } it "returns true if the type exists and is a possible type of the interface" do assert interface.possible_type?("Cheese", query.context) end it "returns false if the type is not found in the schema" do refute interface.possible_type?("Foo", query.context) end it "returns false if the type is not a possible type of the interface" do refute interface.possible_type?("Beverage", query.context) end end end graphql-ruby-1.11.10/spec/graphql/internal_representation/000077500000000000000000000000001414121453000236115ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/graphql/internal_representation/print_spec.rb000066400000000000000000000015311414121453000263040ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" if !TESTING_INTERPRETER describe GraphQL::InternalRepresentation::Print do # rubocop:disable Layout/IndentationWidth describe "printing queries" do let(:query_str) { <<-GRAPHQL { cheese(id: 1) { flavor ...EdibleFields ... on Edible { o2: origin } } } fragment EdibleFields on Edible { o: origin } GRAPHQL } it "prints the rewritten query" do query_plan = GraphQL::InternalRepresentation::Print.print(Dummy::Schema.graphql_definition, query_str) expected_plan = <<-GRAPHQL query { ... on Query { cheese(id: 1) { ... on Cheese { flavor() o2: origin() o: origin() } } } } GRAPHQL assert_equal expected_plan, query_plan end end end end graphql-ruby-1.11.10/spec/graphql/internal_representation/rewrite_spec.rb000066400000000000000000000256461414121453000266460ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::InternalRepresentation::Rewrite do class RewriteSchema < GraphQL::Schema class LeafTypeType < GraphQL::Schema::Enum value "NEEDLE" value "LEAF" end module Environ include GraphQL::Schema::Interface field :seasons, [String], null: true end class Habitat < GraphQL::Schema::Object implements Environ field :residentName, String, null: false field :averageWeight, Int, null: false end module Tree include GraphQL::Schema::Interface field :name, String, null: false field :leaf_type, LeafTypeType, null: true field :habitats, [Habitat], null: true end class Fruit < GraphQL::Schema::Object implements Tree field :color, [Int], null: false end class Vegetable < GraphQL::Schema::Object field :name, String, null: false field :edibleParts, [String], null: false end class Grain < GraphQL::Schema::Object field :name, String, null: false end class Nut < GraphQL::Schema::Object implements Tree field :name, String, null: false field :diameter, Int, null: false end class Plant < GraphQL::Schema::Union possible_types Grain, Fruit, Vegetable, Nut end class Query < GraphQL::Schema::Object field :plant, Plant, null: true do argument :id, ID, required: true end field :fruit, [Fruit], null: true do argument :id, ID, required: true end end query(Query) end let(:schema) { RewriteSchema.graphql_definition } let(:validator) { GraphQL::StaticValidation::Validator.new(schema: schema) } let(:query) { GraphQL::Query.new(schema, query_string) } let(:rewrite_result) { res = validator.validate(query) res[:errors].any? && raise(res[:errors].map(&:message).join("; ")) res[:irep] } describe "building a tree over concrete types with fragments" do let(:query_string) { <<-GRAPHQL query getPlant($id: ID!) { plant(id: $id) { __typename ... on Tree { leafType habitats { averageWeight } } ... on Fruit { name color ... on Tree { leafType } } ... on Nut { leafType ...NutFields } } } fragment NutFields on Nut { leafType ... TreeFields } fragment TreeFields on Tree { habitats { ... HabitatFields } } fragment HabitatFields on Habitat { seasons } GRAPHQL } it "groups selections by object types which they apply to" do doc = rewrite_result.operation_definitions["getPlant"] assert_nil doc.definition plant_scoped_selection = doc.scoped_children[schema.types["Query"]]["plant"] assert_equal ["Fruit", "Nut", "Plant"], plant_scoped_selection.scoped_children.keys.map(&:name).sort plant_selection = doc.typed_children[schema.types["Query"]]["plant"] assert_equal ["Fruit", "Grain", "Nut", "Vegetable"], plant_selection.typed_children.keys.map(&:name).sort fruit_selections = plant_selection.typed_children[schema.types["Fruit"]] assert_equal ["__typename", "color", "habitats", "leafType", "name"], fruit_selections.keys.sort assert_equal 2, fruit_selections["leafType"].ast_nodes.size nut_selections = plant_selection.typed_children[schema.types["Nut"]] # `... on Tree`, `... on Nut`, and `NutFields`, but not `... on Fruit { ... on Tree }` if RUBY_VERSION < "2.5" # Ruby 2.5 changed how hash key collisions worked a little bit. # This test is "broken", but honestly, it doesn't matter. # Apparently nobody uses `IrepNode#ast_nodes`, and that's for the best. assert_equal 3, nut_selections["leafType"].ast_nodes.size end # Multi-level merging when including fragments: habitats_selections = nut_selections["habitats"].typed_children[schema.types["Habitat"]] assert_equal ["averageWeight", "seasons"], habitats_selections.keys end it "tracks parent nodes" do doc = rewrite_result.operation_definitions["getPlant"] assert_nil doc.parent plant_selection = doc.typed_children[schema.types["Query"]]["plant"] assert_equal doc, plant_selection.parent leaf_type_selection = plant_selection.typed_children[schema.types["Nut"]]["leafType"] assert_equal plant_selection, leaf_type_selection.parent habitats_selection = plant_selection.typed_children[schema.types["Nut"]]["habitats"] assert_equal plant_selection, habitats_selection.parent seasons_selection = habitats_selection.typed_children[schema.types["Habitat"]]["seasons"] average_weight_selection = habitats_selection.typed_children[schema.types["Habitat"]]["averageWeight"] assert_equal habitats_selection, seasons_selection.parent assert_equal habitats_selection, average_weight_selection.parent end it "tracks field return type" do doc = rewrite_result.operation_definitions["getPlant"] assert plant_selection = doc.typed_children[schema.types["Query"]]["plant"] assert_equal "Plant", plant_selection.return_type.to_s assert tree_selection = plant_selection.typed_children[schema.types["Fruit"]] assert_equal "[Int!]!", tree_selection["color"].return_type.to_s end end describe "tracking directives on fragment spreads" do let(:query_string) { <<-GRAPHQL query getPlant($id: ID!) { plant(id: $id) { ... on Nut @skip(if: true) { leafType } ... on Tree { leafType @include(if: true) } ... on Nut { leafType # no directives } ... NutFields @include(if: false) } } fragment NutFields on Nut { leafType @skip(if: false) } GRAPHQL } it "applies directives from all contexts" do doc = rewrite_result.operation_definitions["getPlant"] plant_selection = doc.typed_children[schema.types["Query"]]["plant"] leaf_type_selection = plant_selection.typed_children[schema.types["Nut"]]["leafType"] # Only unskipped occurrences in the AST assert_equal 2, leaf_type_selection.ast_nodes.size end end describe "deep fragment merging" do let(:query_string) { <<-GRAPHQL { plant(id: 1) { ...TreeFields ...NutFields } } fragment TreeFields on Tree { habitats { seasons } } fragment NutFields on Nut { habitats { residentName ... HabitatFields } } fragment HabitatFields on Habitat { averageWeight } GRAPHQL } it "applies spreads to their parents only" do doc = rewrite_result.operation_definitions[nil] plant_selection = doc.typed_children[schema.types["Query"]]["plant"] nut_habitat_selections = plant_selection.typed_children[schema.types["Nut"]]["habitats"].typed_children[schema.types["Habitat"]] assert_equal ["averageWeight", "residentName", "seasons"], nut_habitat_selections.keys.sort fruit_habitat_selections = plant_selection.typed_children[schema.types["Fruit"]]["habitats"].typed_children[schema.types["Habitat"]] assert_equal ["seasons"], fruit_habitat_selections.keys end end describe "nested fields on typed fragments" do let(:result) { Dummy::Schema.execute(query_string) } let(:query_string) {%| { allDairy { __typename ... on Milk { selfAsEdible { milkInlineOrigin: origin } } ... on Cheese { selfAsEdible { cheeseInlineOrigin: origin } } ... on Edible { selfAsEdible { edibleInlineOrigin: origin } } ... { ... on Edible { selfAsEdible { untypedInlineOrigin: origin } } } ...milkFields ...cheeseFields } } fragment cheeseFields on Cheese { selfAsEdible { cheeseFragmentOrigin: origin } } fragment milkFields on Milk { selfAsEdible { milkFragmentOrigin: origin } } |} it "distinguishes between nested fields with the same name on different typed fragments" do all_dairy = result["data"]["allDairy"] cheeses = all_dairy.select { |d| d["__typename"] == "Cheese" } milks = all_dairy.select { |d| d["__typename"] == "Milk" } # Make sure all the data is there: assert_equal 3, cheeses.length assert_equal 1, milks.length expected_cheese_fields = ["cheeseFragmentOrigin", "cheeseInlineOrigin", "edibleInlineOrigin", "untypedInlineOrigin"] cheeses.each do |cheese| assert_equal expected_cheese_fields, cheese["selfAsEdible"].keys.sort end expected_milk_fields = ["edibleInlineOrigin", "milkFragmentOrigin", "milkInlineOrigin", "untypedInlineOrigin"] milks.each do |milk| assert_equal expected_milk_fields, milk["selfAsEdible"].keys.sort end end end describe "fragment merging bug" do let(:query_string) { <<-GRAPHQL { ...Frag1 __type(name: "Query") { ...Frag2 } } fragment Frag1 on Query { __type(name: "Query") { ...Frag2 } } fragment Frag2 on __Type { __typename } GRAPHQL } it "finishes" do doc = rewrite_result.operation_definitions[nil] type_node = doc.typed_children[schema.types["Query"]]["__type"] typename_node = type_node.typed_children[schema.types["__Type"]]["__typename"] assert_equal 1, typename_node.ast_nodes.size assert_equal 15, typename_node.ast_node.line assert_equal 9, typename_node.ast_node.col end end describe "fragment definition without query" do let(:query_string) { <<-GRAPHQL fragment NutFields on Nut { leafType ... TreeFields } fragment TreeFields on Tree { habitats { ... HabitatFields } } fragment HabitatFields on Habitat { seasons } GRAPHQL } let(:validator) { rules = GraphQL::StaticValidation::ALL_RULES - [ GraphQL::StaticValidation::FragmentsAreUsed ] GraphQL::StaticValidation::Validator.new(schema: schema, rules: rules) } it "tracks fragment definitions without operation" do doc = rewrite_result.fragment_definitions["NutFields"] assert doc.typed_children[schema.types["Nut"]]["leafType"] assert nut_selections = doc.typed_children[schema.types["Nut"]] habitats_selections = nut_selections["habitats"].typed_children[schema.types["Habitat"]] assert_equal ["seasons"], habitats_selections.keys end end end graphql-ruby-1.11.10/spec/graphql/introspection/000077500000000000000000000000001414121453000215535ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/graphql/introspection/directive_type_spec.rb000066400000000000000000000035331414121453000261350ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Introspection::DirectiveType do let(:query_string) {%| query getDirectives { __schema { directives { name, args { name, type { kind, name, ofType { name } } }, locations # Deprecated fields: onField onFragment onOperation } } } |} let(:schema) { Class.new(Dummy::Schema) } let(:result) { schema.execute(query_string) } before do schema.max_depth(100) end it "shows directive info " do expected = { "data" => { "__schema" => { "directives" => [ { "name" => "include", "args" => [ {"name"=>"if", "type"=>{"kind"=>"NON_NULL", "name"=>nil, "ofType"=>{"name"=>"Boolean"}}} ], "locations"=>["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], "onField" => true, "onFragment" => true, "onOperation" => false, }, { "name" => "skip", "args" => [ {"name"=>"if", "type"=>{"kind"=>"NON_NULL", "name"=>nil, "ofType"=>{"name"=>"Boolean"}}} ], "locations"=>["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], "onField" => true, "onFragment" => true, "onOperation" => false, }, { "name" => "deprecated", "args" => [ {"name"=>"reason", "type"=>{"kind"=>"SCALAR", "name"=>"String", "ofType"=>nil}} ], "locations"=>["FIELD_DEFINITION", "ENUM_VALUE", "ARGUMENT_DEFINITION", "INPUT_FIELD_DEFINITION"], "onField" => false, "onFragment" => false, "onOperation" => false, }, ] } }} assert_equal(expected, result) end end graphql-ruby-1.11.10/spec/graphql/introspection/entry_points_spec.rb000066400000000000000000000031121414121453000256440ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Introspection::EntryPoints do describe "#__type" do let(:schema) do nested_invisible_type = Class.new(GraphQL::Schema::Object) do graphql_name 'NestedInvisible' field :foo, String, null: false end invisible_type = Class.new(GraphQL::Schema::Object) do graphql_name 'Invisible' field :foo, String, null: false field :nested_invisible, nested_invisible_type, null: false def self.visible?(context) false end end visible_type = Class.new(GraphQL::Schema::Object) do graphql_name 'Visible' field :foo, String, null: false end query_type = Class.new(GraphQL::Schema::Object) do graphql_name 'Query' field :foo, String, null: false field :invisible, invisible_type, null: false field :visible, visible_type, null: false end Class.new(GraphQL::Schema) do query query_type end end let(:query_string) {%| query getType($name: String!) { __type(name: $name) { name } } |} it "returns reachable types" do result = schema.execute(query_string, variables: { name: 'Visible' }) type_name = result['data']['__type']['name'] assert_equal('Visible', type_name) end it "returns nil for unreachable types" do result = schema.execute(query_string, variables: { name: 'NestedInvisible' }) type_name = result['data']['__type'] assert_nil(type_name) end end end graphql-ruby-1.11.10/spec/graphql/introspection/input_value_type_spec.rb000066400000000000000000000066701414121453000265170ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Introspection::InputValueType do let(:query_string) {%| { __type(name: "DairyProductInput") { name description kind inputFields { name type { kind, name } defaultValue description } } } |} let(:result) { Dummy::Schema.execute(query_string) } it "exposes metadata about input objects, giving extra quotes for strings" do expected = { "data" => { "__type" => { "name"=>"DairyProductInput", "description"=>"Properties for finding a dairy product", "kind"=>"INPUT_OBJECT", "inputFields"=>[ {"name"=>"source", "type"=>{"kind"=>"NON_NULL", "name" => nil}, "defaultValue"=>nil, "description" => "Where it came from"}, {"name"=>"originDairy", "type"=>{"kind"=>"SCALAR", "name" => "String"}, "defaultValue"=>"\"Sugar Hollow Dairy\"", "description" => "Dairy which produced it"}, {"name"=>"fatContent", "type"=>{"kind"=>"SCALAR", "name" => "Float"}, "defaultValue"=>"0.3", "description" => "How much fat it has"}, {"name"=>"organic", "type"=>{"kind"=>"SCALAR", "name" => "Boolean"}, "defaultValue"=>"false", "description" => nil}, {"name"=>"order_by", "type"=>{"kind"=>"INPUT_OBJECT", "name"=>"ResourceOrderType"}, "defaultValue"=>"{direction: \"ASC\"}", "description" => nil}, ] } }} assert_equal(expected, result.to_h) end let(:cheese_type) { Dummy::Schema.execute(%| { __type(name: "Cheese") { fields { name args { name defaultValue } } } } |) } it "converts default values to GraphQL values" do field = cheese_type['data']['__type']['fields'].detect { |f| f['name'] == 'similarCheese' } arg = field['args'].detect { |a| a['name'] == 'nullableSource' } assert_equal('[COW]', arg['defaultValue']) end it "supports list of enum default values" do schema = GraphQL::Schema.from_definition(%| type Query { hello(enums: [MyEnum] = [A, B]): String } enum MyEnum { A B } |) result = schema.execute(%| { __type(name: "Query") { fields { args { defaultValue } } } } |) expected = { "data" => { "__type" => { "fields" => [{ "args" => [{ "defaultValue" => "[A, B]" }] }] } } } assert_equal expected, result end it "supports null default values" do schema = GraphQL::Schema.from_definition(%| type Query { hello(person: Person): String } input Person { firstName: String! lastName: String = null } |) result = schema.execute(%| { __type(name: "Person") { inputFields { name defaultValue } } } |) expected = { "data" => { "__type" => { "inputFields" => [ { "name" => "firstName", "defaultValue" => nil}, { "name" => "lastName", "defaultValue" => "null"} ] } } } assert_equal expected, result end end graphql-ruby-1.11.10/spec/graphql/introspection/introspection_query_spec.rb000066400000000000000000000030511414121453000272360ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe "GraphQL::Introspection::INTROSPECTION_QUERY" do let(:schema) { Class.new(Dummy::Schema) do max_depth(15) end } let(:query_string) { GraphQL::Introspection::INTROSPECTION_QUERY } let(:result) { schema.execute(query_string) } it "runs" do assert(result["data"]) end it "is limited to the max query depth" do query_type = GraphQL::ObjectType.define do name "DeepQuery" field :foo do type !GraphQL::ListType.new( of_type: !GraphQL::ListType.new( of_type: !GraphQL::ListType.new( of_type: GraphQL::FLOAT_TYPE ) ) ) end end deep_schema = GraphQL::Schema.define do query query_type end result = deep_schema.execute(query_string) assert(GraphQL::Schema::Loader.load(result)) end it "doesn't handle too deeply nested (< 8) schemas" do query_type = GraphQL::ObjectType.define do name "DeepQuery" field :foo do type !GraphQL::ListType.new( of_type: !GraphQL::ListType.new( of_type: !GraphQL::ListType.new( of_type: !GraphQL::ListType.new( of_type: GraphQL::FLOAT_TYPE ) ) ) ) end end deep_schema = GraphQL::Schema.define do query query_type end result = deep_schema.execute(query_string) assert_raises(KeyError) { GraphQL::Schema::Loader.load(result) } end end graphql-ruby-1.11.10/spec/graphql/introspection/schema_type_spec.rb000066400000000000000000000102721414121453000254150ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Introspection::SchemaType do let(:schema) { Class.new(Dummy::Schema) } let(:query_string) {%| query getSchema { __schema { types { name } queryType { fields { name }} mutationType { fields { name }} } } |} let(:result) { schema.execute(query_string) } it "exposes the schema" do expected = { "data" => { "__schema" => { "types" => schema.types.values.sort_by(&:graphql_name).map { |t| t.graphql_name.nil? ? (p t; raise("no name for #{t}")) : {"name" => t.graphql_name} }, "queryType"=>{ "fields"=>[ {"name"=>"allAnimal"}, {"name"=>"allAnimalAsCow"}, {"name"=>"allDairy"}, {"name"=>"allEdible"}, {"name"=>"allEdibleAsMilk"}, {"name"=>"cheese"}, {"name"=>"cow"}, {"name"=>"dairy"}, {"name"=>"deepNonNull"}, {"name"=>"error"}, {"name"=>"executionError"}, {"name"=>"executionErrorWithExtensions"}, {"name"=>"executionErrorWithOptions"}, {"name"=>"favoriteEdible"}, {"name"=>"fromSource"}, {"name"=>"maybeNull"}, {"name"=>"milk"}, {"name"=>"multipleErrorsOnNonNullableField"}, {"name"=>"multipleErrorsOnNonNullableListField"}, {"name"=>"root"}, {"name"=>"searchDairy"}, {"name"=>"tracingScalar"}, {"name"=>"valueWithExecutionError"}, ] }, "mutationType"=> { "fields"=>[ {"name"=>"pushValue"}, {"name"=>"replaceValues"}, ] }, } }} assert_equal(expected, result.to_h) end describe "when the schema has types that are only reachable through hidden types" do let(:schema) do nested_invisible_type = Class.new(GraphQL::Schema::Object) do graphql_name 'NestedInvisible' field :foo, String, null: false end invisible_type = Class.new(GraphQL::Schema::Object) do graphql_name 'Invisible' field :foo, String, null: false field :nested_invisible, nested_invisible_type, null: false def self.visible?(context) false end end invisible_input_type = Class.new(GraphQL::Schema::InputObject) do graphql_name 'InvisibleInput' argument :foo, String, required: false def self.visible?(context) false end end visible_input_type = Class.new(GraphQL::Schema::InputObject) do graphql_name 'VisibleInput' argument :foo, String, required: false end visible_type = Class.new(GraphQL::Schema::Object) do graphql_name 'Visible' field :foo, String, null: false end invisible_orphan_type = Class.new(GraphQL::Schema::Object) do graphql_name 'InvisibleOrphan' field :foo, String, null: false def self.visible?(context) false end end query_type = Class.new(GraphQL::Schema::Object) do graphql_name 'Query' field :foo, String, null: false field :invisible, invisible_type, null: false field :visible, visible_type, null: false field :with_invisible_args, String, null: false do argument :invisible, invisible_input_type, required: false argument :visible, visible_input_type, required: true end end Class.new(GraphQL::Schema) do query query_type orphan_types invisible_orphan_type end end let(:query_string) {%| query getSchema { __schema { types { name } } } |} it "only returns reachable types" do expected_types = [ 'Boolean', 'Query', 'String', 'Visible', 'VisibleInput', '__Directive', '__DirectiveLocation', '__EnumValue', '__Field', '__InputValue', '__Schema', '__Type', '__TypeKind' ] types = result['data']['__schema']['types'].map { |type| type.fetch('name') } assert_equal(expected_types, types) end end end graphql-ruby-1.11.10/spec/graphql/introspection/type_type_spec.rb000066400000000000000000000217041414121453000251400ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Introspection::TypeType do let(:query_string) {%| query introspectionQuery { cheeseType: __type(name: "Cheese") { name, kind, fields { name, isDeprecated, type { kind, name, ofType { name } } } } milkType: __type(name: "Milk") { interfaces { name }, fields { type { kind, name, ofType { name } } } } dairyAnimal: __type(name: "DairyAnimal") { name, kind, enumValues(includeDeprecated: false) { name, isDeprecated } } dairyProduct: __type(name: "DairyProduct") { name, kind, possibleTypes { name } } animalProduct: __type(name: "AnimalProduct") { name, kind, possibleTypes { name }, fields { name } } missingType: __type(name: "NotAType") { name } } |} let(:result) { Dummy::Schema.execute(query_string, context: {}, variables: {"cheeseId" => 2}) } let(:cheese_fields) {[ {"name"=>"deeplyNullableCheese", "isDeprecated" => false, "type"=>{ "kind" => "OBJECT", "name" => "Cheese", "ofType" => nil}}, {"name"=>"flavor", "isDeprecated" => false, "type" => { "kind" => "NON_NULL", "name" => nil, "ofType" => { "name" => "String"}}}, {"name"=>"id", "isDeprecated" => false, "type" => { "kind" => "NON_NULL", "name" => nil, "ofType" => { "name" => "Int"}}}, {"name"=>"nullableCheese", "isDeprecated"=>false, "type"=>{ "kind" => "OBJECT", "name" => "Cheese", "ofType"=>nil}}, {"name"=>"origin", "isDeprecated" => false, "type" => { "kind" => "NON_NULL", "name" => nil, "ofType" => { "name" => "String"}}}, {"name"=>"selfAsEdible", "isDeprecated"=>false, "type"=>{"kind"=>"INTERFACE", "name"=>"Edible", "ofType"=>nil}}, {"name"=>"similarCheese", "isDeprecated"=>false, "type"=>{ "kind" => "OBJECT", "name"=>"Cheese", "ofType"=>nil}}, {"name"=>"source", "isDeprecated" => false, "type" => { "kind" => "NON_NULL", "name" => nil, "ofType" => { "name" => "DairyAnimal"}}}, ]} let(:dairy_animals) {[ {"name"=>"COW", "isDeprecated"=> false }, {"name"=>"DONKEY", "isDeprecated"=> false }, {"name"=>"GOAT", "isDeprecated"=> false }, {"name"=>"REINDEER", "isDeprecated"=> false }, {"name"=>"SHEEP", "isDeprecated"=> false }, ]} it "exposes metadata about types" do expected = {"data"=> { "cheeseType" => { "name"=> "Cheese", "kind" => "OBJECT", "fields"=> cheese_fields }, "milkType"=>{ "interfaces"=>[ {"name"=>"Edible"}, {"name"=>"EdibleAsMilk"}, {"name"=>"AnimalProduct"}, {"name"=>"LocalProduct"}, ], "fields"=>[ {"type"=>{"kind"=>"LIST","name"=>nil, "ofType"=>{"name"=>"DairyProduct"}}}, {"type"=>{"kind"=>"SCALAR","name"=>"String", "ofType"=>nil}}, {"type"=>{"kind"=>"NON_NULL","name"=>nil, "ofType"=>{"name"=>"Float"}}}, {"type"=>{"kind"=>"LIST","name"=>nil, "ofType"=>{"name"=>"String"}}}, {"type"=>{"kind"=>"NON_NULL","name"=>nil, "ofType"=>{"name"=>"ID"}}}, {"type"=>{"kind"=>"NON_NULL","name"=>nil, "ofType"=>{"name"=>"String"}}}, {"type"=>{"kind"=>"INTERFACE", "name"=>"Edible", "ofType"=>nil}}, {"type"=>{"kind"=>"NON_NULL","name"=>nil,"ofType"=>{"name"=>"DairyAnimal"}}}, ] }, "dairyAnimal"=>{ "name"=>"DairyAnimal", "kind"=>"ENUM", "enumValues"=> dairy_animals, }, "dairyProduct"=>{ "name"=>"DairyProduct", "kind"=>"UNION", "possibleTypes"=>[{"name"=>"Cheese"}, {"name"=>"Milk"}], }, "animalProduct" => { "name"=>"AnimalProduct", "kind"=>"INTERFACE", "possibleTypes"=>[{"name"=>"Cheese"}, {"name"=>"Honey"}, {"name"=>"Milk"}], "fields"=>[ {"name"=>"source"}, ] }, "missingType" => nil, }} assert_equal(expected, result.to_h) end describe "deprecated fields" do let(:query_string) {%| query introspectionQuery { cheeseType: __type(name: "Cheese") { name, kind, fields(includeDeprecated: true) { name, isDeprecated, type { kind, name, ofType { name } } } } dairyAnimal: __type(name: "DairyAnimal") { name, kind, enumValues(includeDeprecated: true) { name, isDeprecated } } } |} let(:deprecated_fields) { {"name"=>"fatContent", "isDeprecated"=>true, "type"=>{"kind"=>"NON_NULL","name"=>nil, "ofType"=>{"name"=>"Float"}}} } it "can expose deprecated fields" do new_cheese_fields = ([deprecated_fields] + cheese_fields).sort_by { |f| f["name"] } expected = { "data" => { "cheeseType" => { "name"=> "Cheese", "kind" => "OBJECT", "fields"=> new_cheese_fields }, "dairyAnimal"=>{ "name"=>"DairyAnimal", "kind"=>"ENUM", "enumValues"=> dairy_animals + [{"name" => "YAK", "isDeprecated" => true}], }, }} assert_equal(expected, result) end it "hides deprecated field arguments by default" do result = Dummy::Schema.execute <<-GRAPHQL { __type(name: "Query") { fields { name args { name } } } } GRAPHQL from_source_field = result['data']['__type']['fields'].find { |f| f['name'] == 'fromSource' } expected = [ {"name" => "source"} ] assert_equal(expected, from_source_field['args']) end it "can expose deprecated field arguments" do result = Dummy::Schema.execute <<-GRAPHQL { __type(name: "Query") { fields { name args(includeDeprecated: true) { name isDeprecated deprecationReason } } } } GRAPHQL from_source_field = result['data']['__type']['fields'].find { |f| f['name'] == 'fromSource' } expected = [ {"name" => "source", "isDeprecated" => false, "deprecationReason" => nil}, {"name" => "oldSource", "isDeprecated" => true, "deprecationReason" => "No longer supported"} ] assert_equal(expected, from_source_field['args']) end end describe "input objects" do let(:query_string) {%| query introspectionQuery { __type(name: "DairyProductInput") { name, description, kind, inputFields { name, type { kind, name }, defaultValue } } } |} it "exposes metadata about input objects" do expected = { "data" => { "__type" => { "name"=>"DairyProductInput", "description"=>"Properties for finding a dairy product", "kind"=>"INPUT_OBJECT", "inputFields"=>[ {"name"=>"source", "type"=>{"kind"=>"NON_NULL","name"=>nil, }, "defaultValue"=>nil}, {"name"=>"originDairy", "type"=>{"kind"=>"SCALAR","name"=>"String"}, "defaultValue"=>"\"Sugar Hollow Dairy\""}, {"name"=>"fatContent", "type"=>{"kind"=>"SCALAR","name" => "Float"}, "defaultValue"=>"0.3"}, {"name"=>"organic", "type"=>{"kind"=>"SCALAR","name" => "Boolean"}, "defaultValue"=>"false"}, {"name"=>"order_by", "type"=>{"kind"=>"INPUT_OBJECT", "name"=>"ResourceOrderType"}, "defaultValue"=>"{direction: \"ASC\"}"}, ] } }} assert_equal(expected, result) end it "can expose deprecated input fields" do result = Dummy::Schema.execute <<-GRAPHQL { __type(name: "DairyProductInput") { inputFields(includeDeprecated: true) { name isDeprecated deprecationReason } } } GRAPHQL expected = { "data" => { "__type" => { "inputFields" => [ {"name" => "source", "isDeprecated" => false, "deprecationReason" => nil}, {"name" => "originDairy", "isDeprecated" => false, "deprecationReason" => nil}, {"name" => "fatContent", "isDeprecated" => false, "deprecationReason" => nil}, {"name" => "organic", "isDeprecated" => false, "deprecationReason" => nil}, {"name" => "order_by", "isDeprecated" => false, "deprecationReason" => nil}, {"name" => "oldSource", "isDeprecated" => true, "deprecationReason" => "No longer supported"}, ] } } } assert_equal(expected, result) end it "includes Relay fields" do res = StarWars::Schema.execute <<-GRAPHQL { __schema { types { name fields { name args { name } } } } } GRAPHQL type_result = res["data"]["__schema"]["types"].find { |t| t["name"] == "Faction" } field_result = type_result["fields"].find { |f| f["name"] == "bases" } all_arg_names = ["after", "before", "first", "last", "nameIncludes", "complexOrder"] returned_arg_names = field_result["args"].map { |a| a["name"] } assert_equal all_arg_names, returned_arg_names end end end graphql-ruby-1.11.10/spec/graphql/language/000077500000000000000000000000001414121453000204365ustar00rootroot00000000000000graphql-ruby-1.11.10/spec/graphql/language/block_string_spec.rb000066400000000000000000000036241414121453000244620ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Language::BlockString do describe "trimming whitespace" do def trim_whitespace(str) GraphQL::Language::BlockString.trim_whitespace(str) end it "matches the examples in graphql-js" do # these are taken from: # https://github.com/graphql/graphql-js/blob/36ec0e9d34666362ff0e2b2b18edeb98e3c9abee/src/language/__tests__/blockStringValue-test.js#L12 # A set of [before, after] pairs: examples = [ [ # Removes common whitespace: " Hello, World! Yours, GraphQL. ", "Hello,\n World!\n\nYours,\n GraphQL." ], [ # Removes leading and trailing newlines: " Hello, World! Yours, GraphQL. ", "Hello,\n World!\n\nYours,\n GraphQL." ], [ # Removes blank lines (with whitespace _and_ newlines:) "\n \n Hello, World! Yours, GraphQL. \n \n", "Hello,\n World!\n\nYours,\n GraphQL." ], [ # Retains indentation from the first line " Hello,\n World!\n\n Yours,\n GraphQL.", " Hello,\n World!\n\nYours,\n GraphQL.", ], [ # Doesn't alter trailing spaces "\n \n Hello, \n World! \n\n Yours, \n GraphQL. ", "Hello, \n World! \n\nYours, \n GraphQL. ", ], [ # Doesn't crash when the string is only a newline "\n", "" ] ] examples.each_with_index do |(before, after), idx| transformed_str = trim_whitespace(before) assert_equal(after, transformed_str, "Example ##{idx + 1}") end end end end graphql-ruby-1.11.10/spec/graphql/language/definition_slice_spec.rb000066400000000000000000000124141414121453000253060ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Language::DefinitionSlice do let(:document) { GraphQL::Language::Parser.parse(query_string) } describe "anonymous query with no dependencies" do let(:query_string) {%| { version } |} it "is already the smallest slice" do assert_equal document.to_query_string, document.slice_definition(nil).to_query_string end end describe "anonymous mutation with no dependencies" do let(:query_string) {%| mutation { ping { message } } |} it "is already the smallest slice" do assert_equal document.to_query_string, document.slice_definition(nil).to_query_string end end describe "anonymous fragment with no dependencies" do let(:query_string) {%| fragment on User { name } |} it "is already the smallest slice" do assert_equal document.to_query_string, document.slice_definition(nil).to_query_string end end describe "named query with no dependencies" do let(:query_string) {%| query getVersion { version } |} it "is already the smallest slice" do assert_equal document.to_query_string, document.slice_definition("getVersion").to_query_string end end describe "named fragment with no dependencies" do let(:query_string) {%| fragment profileFields on User { firstName lastName } |} it "is already the smallest slice" do assert_equal document.to_query_string, document.slice_definition("profileFields").to_query_string end end describe "document with multiple queries but no subdependencies" do let(:query_string) {%| query getVersion { version } query getTime { time } |} it "returns just the query definition" do assert_equal GraphQL::Language::Nodes::Document.new(definitions: [document.definitions[0]]).to_query_string, document.slice_definition("getVersion").to_query_string assert_equal GraphQL::Language::Nodes::Document.new(definitions: [document.definitions[1]]).to_query_string, document.slice_definition("getTime").to_query_string end end describe "document with multiple fragments but no subdependencies" do let(:query_string) {%| fragment profileFields on User { firstName lastName } fragment avatarFields on User { avatarURL(size: 80) } |} it "returns just the fragment definition" do assert_equal GraphQL::Language::Nodes::Document.new(definitions: [document.definitions[0]]).to_query_string, document.slice_definition("profileFields").to_query_string assert_equal GraphQL::Language::Nodes::Document.new(definitions: [document.definitions[1]]).to_query_string, document.slice_definition("avatarFields").to_query_string end end describe "query with missing spread" do let(:query_string) {%| query getUser { viewer { ...profileFields } } |} it "is ignored" do assert_equal document.to_query_string, document.slice_definition("getUser").to_query_string end end describe "query and fragment subdependency" do let(:query_string) {%| query getUser { viewer { ...profileFields } } fragment profileFields on User { firstName lastName } |} it "returns query and fragment dependency" do assert_equal document.to_query_string, document.slice_definition("getUser").to_query_string end end describe "query and fragment nested subdependencies" do let(:query_string) {%| query getUser { viewer { ...viewerInfo } } fragment viewerInfo on User { ...profileFields } fragment profileFields on User { firstName lastName ...avatarFields } fragment avatarFields on User { avatarURL(size: 80) } |} it "returns query and all fragment dependencies" do assert_equal document.to_query_string, document.slice_definition("getUser").to_query_string end end describe "fragment subdependency referenced multiple times" do let(:query_string) {%| query getUser { viewer { ...viewerInfo ...moreViewerInfo } } fragment viewerInfo on User { ...profileFields } fragment moreViewerInfo on User { ...profileFields } fragment profileFields on User { firstName lastName } |} it "is only returned once" do assert_equal document.to_query_string, document.slice_definition("getUser").to_query_string end end describe "query and unused fragment" do let(:query_string) {%| query getUser { viewer { id } } fragment profileFields on User { firstName lastName } |} it "returns just the query definition" do assert_equal GraphQL::Language::Nodes::Document.new(definitions: [document.definitions[0]]).to_query_string, document.slice_definition("getUser").to_query_string end end end graphql-ruby-1.11.10/spec/graphql/language/document_from_schema_definition_spec.rb000066400000000000000000000524571414121453000304030ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Language::DocumentFromSchemaDefinition do let(:subject) { GraphQL::Language::DocumentFromSchemaDefinition } describe "#document" do let(:schema_idl) { <<-GRAPHQL type QueryType { foo: Foo u: Union } type Foo implements Bar { one: Type two(argument: InputType!): Site three(argument: InputType, other: String): CustomScalar four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: String): Type } interface Bar { one: Type four(argument: String = "string"): String } type Type { a: String } input InputType { key: String! answer: Int = 42 } type MutationType { a(input: InputType): String } # Scalar description scalar CustomScalar enum Site { DESKTOP MOBILE } union Union = Type | QueryType schema { query: QueryType mutation: MutationType } GRAPHQL } let(:schema) { GraphQL::Schema.from_definition(schema_idl) } let(:expected_document) { GraphQL.parse(expected_idl) } describe "when schemas have enums" do let(:schema_idl) { <<-GRAPHQL.chomp directive @locale(lang: LangEnum!) on FIELD enum LangEnum { en ru } type Query { i: Int } GRAPHQL } class DirectiveSchema < GraphQL::Schema class Query < GraphQL::Schema::Object field :i, Int, null: true end class Locale < GraphQL::Schema::Directive class LangEnum < GraphQL::Schema::Enum value "en" value "ru" end locations GraphQL::Schema::Directive::FIELD argument :lang, LangEnum, required: true end query(Query) directive(Locale) end it "dumps them into the string" do assert_equal schema_idl, DirectiveSchema.to_definition end end describe "when printing and schema respects root name conventions" do let(:schema_idl) { <<-GRAPHQL type Query { foo: Foo u: Union } type Foo implements Bar { one: Type two(argument: InputType!): Site three(argument: InputType, other: String): CustomScalar four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: String): Type } interface Bar { one: Type four(argument: String = "string"): String } type Type { a: String } input InputType { key: String! answer: Int = 42 } type Mutation { a(input: InputType): String } # Scalar description scalar CustomScalar enum Site { DESKTOP MOBILE } union Union = Type | Query schema { query: Query mutation: Mutation } GRAPHQL } let(:expected_idl) { <<-GRAPHQL type QueryType { foo: Foo u: Union } type Foo implements Bar { one: Type two(argument: InputType!): Site three(argument: InputType, other: String): CustomScalar four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: String): Type } interface Bar { one: Type four(argument: String = "string"): String } type Type { a: String } input InputType { key: String! answer: Int = 42 } type MutationType { a(input: InputType): String } # Scalar description scalar CustomScalar enum Site { DESKTOP MOBILE } union Union = Type | QueryType GRAPHQL } let(:document) { subject.new( schema ).document } it "returns the IDL without introspection, built ins and schema root" do assert equivalent_node?(expected_document, document) end end describe "with defaults" do let(:expected_idl) { <<-GRAPHQL type QueryType { foo: Foo u: Union } type Foo implements Bar { one: Type two(argument: InputType!): Site three(argument: InputType, other: String): CustomScalar four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: String): Type } interface Bar { one: Type four(argument: String = "string"): String } type Type { a: String } input InputType { key: String! answer: Int = 42 } type MutationType { a(input: InputType): String } # Scalar description scalar CustomScalar enum Site { DESKTOP MOBILE } union Union = Type | QueryType schema { query: QueryType mutation: MutationType } GRAPHQL } let(:document) { subject.new( schema ).document } it "returns the IDL without introspection, built ins and schema if it doesnt respect name conventions" do assert equivalent_node?(expected_document, document) end end describe "with an except filter" do let(:expected_idl) { <<-GRAPHQL type QueryType { foo: Foo u: Union } type Foo implements Bar { three(argument: InputType, other: String): CustomScalar four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): Site } interface Bar { one: Type four(argument: String = "string"): String } input InputType { key: String! answer: Int = 42 } type MutationType { a(input: InputType): String } # Scalar description scalar CustomScalar enum Site { DESKTOP MOBILE } schema { query: QueryType mutation: MutationType } GRAPHQL } let(:document) { subject.new( schema, except: ->(m, _ctx) { m.is_a?(GraphQL::BaseType) && m.name == "Type" } ).document } it "returns the IDL minus the filtered members" do assert equivalent_node?(expected_document, document) end end describe "with an only filter" do let(:expected_idl) { <<-GRAPHQL type QueryType { foo: Foo u: Union } type Foo implements Bar { three(argument: InputType, other: String): CustomScalar four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): Site } interface Bar { one: Type four(argument: String = "string"): String } input InputType { key: String! answer: Int = 42 } type MutationType { a(input: InputType): String } enum Site { DESKTOP MOBILE } schema { query: QueryType mutation: MutationType } GRAPHQL } let(:document) { subject.new( schema, only: ->(m, _ctx) { !(m.is_a?(GraphQL::ScalarType) && m.name == "CustomScalar") } ).document } it "returns the IDL minus the filtered members" do assert equivalent_node?(expected_document, document) end end describe "when excluding built ins and introspection types" do let(:expected_idl) { <<-GRAPHQL type QueryType { foo: Foo u: Union } type Foo implements Bar { one: Type two(argument: InputType!): Site three(argument: InputType, other: String): CustomScalar four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: String): Type } interface Bar { one: Type four(argument: String = "string"): String } type Type { a: String } input InputType { key: String! answer: Int = 42 } type MutationType { a(input: InputType): String } # Scalar description scalar CustomScalar enum Site { DESKTOP MOBILE } union Union = Type | QueryType schema { query: QueryType mutation: MutationType } GRAPHQL } let(:document) { subject.new( schema, always_include_schema: true ).document } it "returns the schema idl besides introspection types and built ins" do assert equivalent_node?(expected_document, document) end end describe "when printing excluding only introspection types" do let(:expected_idl) { <<-GRAPHQL # Represents `true` or `false` values. scalar Boolean # Represents textual data as UTF-8 character sequences. This type is most often # used by GraphQL to represent free-form human-readable text. scalar String type QueryType { foo: Foo } type Foo implements Bar { one: Type two(argument: InputType!): Type three(argument: InputType, other: String): CustomScalar four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: String): Type } interface Bar { one: Type four(argument: String = "string"): String } type Type { a: String } input InputType { key: String! answer: Int = 42 } # Represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. scalar Int type MutationType { a(input: InputType): String } # Represents signed double-precision fractional values as specified by [IEEE # 754](https://en.wikipedia.org/wiki/IEEE_floating_point). scalar Float # Represents a unique identifier that is Base64 obfuscated. It is often used to # refetch an object or as key for a cache. The ID type appears in a JSON response # as a String; however, it is not intended to be human-readable. When expected as # an input type, any string (such as `"VXNlci0xMA=="`) or integer (such as `4`) # input value will be accepted as an ID. scalar ID # Scalar description scalar CustomScalar enum Site { DESKTOP MOBILE } union Union = Type | QueryType directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT # Marks an element of a GraphQL schema as no longer supported. directive @deprecated(reason: String = "No longer supported") on FIELD_DEFINITION | ENUM_VALUE schema { query: QueryType mutation: MutationType } GRAPHQL } let(:document) { subject.new( schema, include_built_in_scalars: true, include_built_in_directives: true, ).document } it "returns the schema IDL including only the built ins and not introspection types" do assert equivalent_node?(expected_document, document) end end describe "when printing the full schema" do let(:expected_idl) { <<-GRAPHQL # Represents `true` or `false` values. scalar Boolean # Represents textual data as UTF-8 character sequences. This type is most often # used by GraphQL to represent free-form human-readable text. scalar String # 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. type __Type { kind: __TypeKind! name: String description: String fields(includeDeprecated: Boolean = false): [__Field!] interfaces: [__Type!] possibleTypes: [__Type!] enumValues(includeDeprecated: Boolean = false): [__EnumValue!] inputFields: [__InputValue!] ofType: __Type } # An enum describing what kind of type a given `__Type` is. 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. INPUT_OBJECT # Indicates this type is a list. `ofType` is a valid field. LIST # Indicates this type is a non-null. `ofType` is a valid field. NON_NULL } # 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. type __Field { name: String! description: String args: [__InputValue!]! type: __Type! isDeprecated: Boolean! deprecationReason: String } # 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. type __InputValue { name: String! description: String type: __Type! # A GraphQL-formatted string representing the default value for this input value. defaultValue: String } # 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. type __EnumValue { name: String! description: String isDeprecated: Boolean! deprecationReason: String } type QueryType { foo: Foo } type Foo implements Bar { 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: String): Type } interface Bar { one: Type four(argument: String = "string"): String } type Type { a: String } input InputType { key: String! answer: Int = 42 } # Represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. scalar Int type MutationType { a(input: InputType): String } # 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. type __Schema { # A list of all types supported by this server. types: [__Type!]! # The type that query operations will be rooted at. queryType: __Type! # If this server supports mutation, the type that mutation operations will be rooted at. mutationType: __Type # If this server support subscription, the type that subscription operations will be rooted at. subscriptionType: __Type # A list of all directives supported by this server. directives: [__Directive!]! } # 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. type __Directive { name: String! description: String locations: [__DirectiveLocation!]! args: [__InputValue!]! onOperation: Boolean! onFragment: Boolean! onField: Boolean! } # A Directive can be adjacent to many parts of the GraphQL language, a # __DirectiveLocation describes one such possible adjacencies. 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 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 } # Represents signed double-precision fractional values as specified by [IEEE # 754](https://en.wikipedia.org/wiki/IEEE_floating_point). scalar Float # Represents a unique identifier that is Base64 obfuscated. It is often used to # refetch an object or as key for a cache. The ID type appears in a JSON response # as a String; however, it is not intended to be human-readable. When expected as # an input type, any string (such as `"VXNlci0xMA=="`) or integer (such as `4`) # input value will be accepted as an ID. scalar ID # Scalar description scalar CustomScalar enum Site { DESKTOP MOBILE } union Union = Type | QueryType directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT # Marks an element of a GraphQL schema as no longer supported. directive @deprecated(reason: String = "No longer supported") on FIELD_DEFINITION | ENUM_VALUE schema { query: QueryType mutation: MutationType } GRAPHQL } let(:document) { subject.new( schema, include_introspection_types: true, include_built_in_directives: true, include_built_in_scalars: true, always_include_schema: true, ).document } it "returns the full document AST from the given schema including built ins and introspection" do assert equivalent_node?(expected_document, document) end end end private def equivalent_node?(expected, node) return false unless expected.is_a?(node.class) if expected.respond_to?(:children) && expected.respond_to?(:scalars) children_equal = expected.children.all? do |expected_child| node.children.find { |child| equivalent_node?(expected_child, child) } end scalars_equal = expected.children.all? do |expected_child| node.children.find { |child| equivalent_node?(expected_child, child) } end children_equal && scalars_equal else expected == node end end end graphql-ruby-1.11.10/spec/graphql/language/equality_spec.rb000066400000000000000000000042571414121453000236420ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Language::Nodes::AbstractNode do describe ".eql?" do let(:document1) { GraphQL::Language::Parser.parse(query_string1) } let(:document2) { GraphQL::Language::Parser.parse(query_string2) } describe "large identical document" do let(:query_string1) {%| query getStuff($someVar: Int = 1, $anotherVar: [String!], $skipNested: Boolean! = false) @skip(if: false) { myField: someField(someArg: $someVar, ok: 1.4) @skip(if: $anotherVar) @thing(or: "Whatever") anotherField(someArg: [1, 2, 3]) { nestedField ...moreNestedFields @skip(if: $skipNested) } ... on OtherType @include(unless: false) { field(arg: [{ key: "value", anotherKey: 0.9, anotherAnotherKey: WHATEVER }]) anotherField } ... { id } } fragment moreNestedFields on NestedType @or(something: "ok") { anotherNestedField } |} let(:query_string2) { query_string1 } it "should be equal" do assert document1 == document2 assert document2 == document1 end end describe "different operations" do let(:query_string1) { "query { field }" } let(:query_string2) { "mutation { setField }" } it "should not be equal" do refute document1 == document2 refute document2 == document1 end end describe "different query fields" do let(:query_string1) { "query { foo }" } let(:query_string2) { "query { bar }" } it "should not be equal" do refute document1 == document2 refute document2 == document1 end end describe "different schemas" do let(:query_string1) {%| schema { query: Query } type Query { field: String! } |} let(:query_string2) {%| schema { query: Query } type Query { field: Int! } |} it "should not be equal" do refute document1.eql?(document2) refute document2.eql?(document1) end end end end graphql-ruby-1.11.10/spec/graphql/language/generation_spec.rb000066400000000000000000000015121414121453000241270ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" describe GraphQL::Language::Generation do describe "#to_query_tring" do let(:document) { GraphQL.parse('type Query { a: String! }') } let(:custom_printer_class) { Class.new(GraphQL::Language::Printer) { def print_field_definition(print_field_definition) "