[
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "<!--\n  This template is for bug reports. If you are reporting a bug, please continue on. If you are here for another reason,\n  feel free to skip the rest of this template.\n-->\n\n### Tell us about your environment\n\n**Ruby Version:**\n\n**Framework Version (Rails, whatever):**\n\n**Action Policy Version:**\n\n**Action Policy GraphQL Version:**\n\n### What did you do?\n\n### What did you expect to happen?\n\n### What actually happened?\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\n  First of all, thanks for contributing!\n\n  If it's a typo fix or minor documentation update feel free to skip the rest of this template!\n-->\n\n<!--\n  If it's a bug fix, then link it to the issue, for example:\n\n  Fixes #xxx\n-->\n\n\n<!--\n  Otherwise, describe the changes: \n\n### What is the purpose of this pull request?\n\n### What changes did you make? (overview)\n\n### Is there anything you'd like reviewers to focus on?\n\n-->\n\nPR checklist:\n\n- [ ] Tests included\n- [ ] Documentation updated\n- [ ] Changelog entry added\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release gems\non:\n  workflow_dispatch:\n  push:\n    tags:\n      - v*\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      id-token: write\n\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0 # Fetch current tag as annotated. See https://github.com/actions/checkout/issues/290\n      - uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: 3.2\n          bundler-cache: true\n      - name: Configure RubyGems Credentials\n        uses: rubygems/configure-rubygems-credentials@main\n      - name: Publish to RubyGems\n        run: |\n          gem install gem-release\n          gem release\n"
  },
  {
    "path": ".github/workflows/rubocop.yml",
    "content": "name: Lint Ruby\n\non:\n  push:\n    branches:\n    - master\n  pull_request:\n\njobs:\n  rubocop:\n    runs-on: ubuntu-latest\n    env:\n      BUNDLE_JOBS: 4\n      BUNDLE_RETRY: 3\n      BUNDLE_GEMFILE: \"gemfiles/rubocop.gemfile\"\n      CI: true\n    steps:\n    - uses: actions/checkout@v4\n    - uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: 3.2\n        bundler-cache: true\n    - name: Lint Ruby code with RuboCop\n      run: |\n        bundle exec rubocop\n"
  },
  {
    "path": ".github/workflows/test-jruby.yml",
    "content": "name: JRuby Build\n\non:\n  push:\n    branches:\n    - master\n  pull_request:\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    env:\n      BUNDLE_JOBS: 4\n      BUNDLE_RETRY: 3\n      CI: true\n    steps:\n    - uses: actions/checkout@v4\n    - uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: jruby\n        bundler-cache: true\n    - name: Run RSpec tests\n      run: |\n        bundle exec rspec --force-color\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Build\n\non:\n  push:\n    branches:\n    - master\n  pull_request:\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    env:\n      BUNDLE_JOBS: 4\n      BUNDLE_RETRY: 3\n      CI: true\n    strategy:\n      fail-fast: false\n      matrix:\n        ruby: [\"3.3\"]\n        gemfile: [\n          \"Gemfile\"\n        ]\n        include:\n        - ruby: \"3.2\"\n          gemfile: \"gemfiles/action_policy/master.gemfile\"\n        - ruby: \"3.0\"\n          gemfile: \"Gemfile\"\n        - ruby: \"3.0\"\n          gemfile: \"gemfiles/action_policy/master.gemfile\"\n        - ruby: \"2.7\"\n          gemfile: \"Gemfile\"\n    steps:\n    - uses: actions/checkout@v4\n    - uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: ${{ matrix.ruby }}\n        bundler-cache: true\n    - name: Run RSpec tests\n      run: |\n        bundle exec rspec --force-color\n"
  },
  {
    "path": ".gitignore",
    "content": "/.bundle/\n/.yardoc\n/Gemfile.lock\n/_yardoc/\n/coverage/\n/doc/\n/pkg/\n/spec/reports/\n/tmp/\nGemfile.local\n*.lock"
  },
  {
    "path": ".rubocop-md.yml",
    "content": "inherit_from: \".rubocop.yml\"\n\nrequire:\n  - rubocop-md\n\nAllCops:\n  Include:\n    - '**/*.md'\n\nLint/Void:\n  Exclude:\n    - '**/*.md'\n\nLint/DuplicateMethods:\n  Exclude:\n    - '**/*.md'\n"
  },
  {
    "path": ".rubocop.yml",
    "content": "require:\n  - standard/cop/block_single_line_braces\n\ninherit_gem:\n  standard: config/base.yml\n\nAllCops:\n  Exclude:\n    - 'bin/*'\n    - 'tmp/**/*'\n    - 'Gemfile'\n    - 'vendor/**/*'\n    - 'gemfiles/**/*'\n    - 'lib/generators/**/templates/**/*'\n  DisplayCopNames: true\n  SuggestExtensions: false\n  TargetRubyVersion: 2.6\n\nStandard/BlockSingleLineBraces:\n  Enabled: false\n\nStyle/FrozenStringLiteralComment:\n  Enabled: true\n\nNaming/FileName:\n  Exclude:\n   - '**/*.md'\n   - 'lib/action_policy-graphql.rb'\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change log\n\n## master (unreleased)\n\n## 0.6.0 (2024-07-08)\n\n- Fix compatibility with Action Policy 0.7.0. ([@palkan][])\n\n## 0.5.4 (2023-12-22)\n\n- Do not mutate passed field options. ([@palkan][])\n\n  Fixes compatibility with `with_options` approach.\n\n## 0.5.3 (2021-02-26)\n\n- Fix compatibility with graphql-ruby 1.12.4 ([@haines][])\n\n## 0.5.2 (2020-10-20)\n\n- Fix modules reloading in development. ([@rzaharenkov][])\n\n## 0.5.1 (2020-10-08)\n\n- Fix mutations authorization (clean up around `authorize_mutation_raise_exception` configuration parameter). ([@rzaharenkov][])\n\n- Add deprecation for using `authorize` for mutation fields. ([@rzaharenkov][])\n\n## 0.5.0 (2020-10-07)\n\n- Add `preauthorize_mutation_raise_exception` configuration parameter. ([@palkan][])\n\nSimilar to `preauthorize_raise_exception` but only for mutations.\nFallbacks to `preauthorize_raise_exception` unless explicitly specified.\n\n- Add `preauthorize_raise_exception` configuration parameter. ([@palkan][])\n\nSimilar to `authorize_raise_exception` but for `preauthorize: true` fields.\nFallbacks to `authorize_raise_exception` unless explicitly specified.\n\n- Add ability to specify custom field options for `expose_authorization_rules`. ([@bibendi][])\n\nNow you can add additional options for underflying `field` call via `field_options` parameter:\n\n```ruby\nexpose_authorization_rules :show?, field_options: {camelize: false}\n\n# equals to\nfield :can_show, ActionPolicy::GraphQL::Types::AuthorizationResult, null: false, camelize: false\n```\n\n## 0.4.0 (2020-03-11)\n\n- **Require Ruby 2.5+**. ([@palkan][])\n\n- Add `authorized_field: *` option to perform authorization on the base of the upper object policy prior to resolving fields. ([@sponomarev][])\n\n## 0.3.2 (2019-12-12)\n\n- Fix compatibility with Action Policy 0.4.0 ([@haines][])\n\n## 0.3.1 (2019-10-23)\n\n- Add support for using Action Policy methods in `self.authorized?`. ([@palkan][])\n\n## 0.3.0 (2019-10-21)\n\n- Add `preauthorize: *` option to perform authorization prior to resolving fields. ([@palkan][])\n\n## 0.2.0 (2019-08-15)\n\n- Add ability to specify a field name explicitly. ([@palkan][])\n\nNow you can write, for example:\n\n```ruby\nexpose_authorization_rules :create?, with: PostPolicy, field_name: :can_create_post\n```\n\n- Add support for resolvers. ([@palkan][])\n\nNow it's possible to `include ActionPolicy::GraphQL::Behaviour` into resolver class to use\nAction Policy helpers there.\n\n## 0.1.0 (2019-05-20)\n\n- Initial version. ([@palkan][])\n\n[@palkan]: https://github.com/palkan\n[@haines]: https://github.com/haines\n[@sponomarev]: https://github.com/sponomarev\n[@bibendi]: https://github.com/bibendi\n[@rzaharenkov]: https://github.com/rzaharenkov\n"
  },
  {
    "path": "Gemfile",
    "content": "source \"https://rubygems.org\"\n\n# Specify your gem's dependencies in action_policy-graphql.gemspec\ngemspec\n\ngem \"debug\", platform: :mri\n\neval_gemfile \"gemfiles/rubocop.gemfile\"\n\nlocal_gemfile = File.join(__dir__, \"Gemfile.local\")\n\nif File.exist?(local_gemfile)\n  # Specify custom action_policy/graphql-ruby version in Gemfile.local\n  eval(File.read(local_gemfile)) # rubocop:disable Security/Eval\nelse\n  gem \"action_policy\", \">= 0.5.0\"\n  gem \"graphql\", \">= 1.9.3\"\nend\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2019 Vladimir Dementyev\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![Gem Version](https://badge.fury.io/rb/action_policy-graphql.svg)](https://badge.fury.io/rb/action_policy-graphql)\n![Build](https://github.com/palkan/action_policy-graphql/workflows/Build/badge.svg)\n![JRuby Build](https://github.com/palkan/action_policy-graphql/workflows/JRuby%20Build/badge.svg)\n[![Documentation](https://img.shields.io/badge/docs-link-brightgreen.svg)](https://actionpolicy.evilmartians.io/#/graphql)\n\n# Action Policy GraphQL\n\n<img align=\"right\" height=\"150\" width=\"129\"\n     title=\"Action Policy logo\" src=\"./assets/logo.svg\">\n\nThis gem provides an integration for using [Action Policy](https://github.com/palkan/action_policy) as an authorization framework for GraphQL applications (built with [`graphql` ruby gem](https://graphql-ruby.org)).\n\nThis integration includes the following features:\n\n- Fields & mutations authorization\n- List and connections scoping\n- [**Exposing permissions/authorization rules in the API**](https://evilmartians.com/chronicles/exposing-permissions-in-graphql-apis-with-action-policy).\n\n📑 [Documentation](https://actionpolicy.evilmartians.io/#/graphql)\n\n<a href=\"https://evilmartians.com/?utm_source=action_policy-graphql\">\n<img src=\"https://evilmartians.com/badges/sponsored-by-evil-martians.svg\" alt=\"Sponsored by Evil Martians\" width=\"236\" height=\"54\"></a>\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem \"action_policy-graphql\"\n```\n\n## Usage\n\n**NOTE:** this is a quick overview of the functionality provided by the gem. For more information see the [documentation](https://actionpolicy.evilmartians.io/#/graphql).\n\nTo start using Action Policy in GraphQL-related code, you need to enhance your base classes with `ActionPolicy::GraphQL::Behaviour`:\n\n```ruby\n# For fields authorization, lists scoping and rules exposing\nclass Types::BaseObject < GraphQL::Schema::Object\n  include ActionPolicy::GraphQL::Behaviour\nend\n\n# For using authorization helpers in mutations\nclass Types::BaseMutation < GraphQL::Schema::Mutation\n  include ActionPolicy::GraphQL::Behaviour\nend\n\n# For using authorization helpers in resolvers\nclass Types::BaseResolver < GraphQL::Schema::Resolver\n  include ActionPolicy::GraphQL::Behaviour\nend\n```\n\n### `authorize: *`\n\nYou can add authorization to the fields by specifying the `authorize: *` option:\n\n```ruby\nfield :home, Home, null: false, authorize: true do\n  argument :id, ID, required: true\nend\n\n# field resolver method\ndef home(id:)\n  Home.find(id)\nend\n```\n\nThe code above is equal to:\n\n```ruby\nfield :home, Home, null: false do\n  argument :id, ID, required: true\nend\n\ndef home(id:)\n  Home.find(id).tap { |home| authorize! home, to: :show? }\nend\n```\n\nYou can customize the authorization options, e.g. `authorize: {to: :preview?, with: CustomPolicy}`.\n\nIf you don't want to raise an exception but return a null instead, you should set a `raise: false` option.\n\nNote: it does not make too much sense to use `authorize` in mutations since it's checking authorization rules after mutation is executed. Therefore `authorize` marked as deprecated when used in mutations and will raise error in future releases.\n\n### `authorized_scope: *`\n\nYou can add `authorized_scope: true` option to the field (list or _connection_ field) to\napply the corresponding policy rules to the data:\n\n```ruby\nclass CityType < ::Common::Graphql::Type\n  # It would automatically apply the relation scope from the EventPolicy to\n  # the relation (city.events)\n  field :events, EventType.connection_type, null: false, authorized_scope: true\n\n  # you can specify the policy explicitly\n  field :events, EventType.connection_type, null: false, authorized_scope: {with: CustomEventPolicy}\nend\n```\n\n**NOTE:** you cannot use `authorize: *` and `authorized_scope: *` at the same time but you can combine `preauthorize: *` or `authorize_field: *` with `authorized_scope: *`.\n\n### `preauthorize: *`\n\nIf you want to perform authorization before resolving the field value, you can use `preauthorize: *` option:\n\n```ruby\nfield :homes, [Home], null: false, preauthorize: {with: HomePolicy}\n\ndef homes\n  Home.all\nend\n```\n\nThe code above is equal to:\n\n```ruby\nfield :homes, [Home], null: false\n\ndef homes\n  authorize! \"homes\", to: :index?, with: HomePolicy\n  Home.all\nend\n```\n\n**NOTE:** we pass the field's name as the `record` to the policy rule. We assume that preauthorization rules do not depend on\nthe record itself and pass the field's name for debugging purposes only.\n\nYou can customize the authorization options, e.g. `preauthorize: {to: :preview?, with: CustomPolicy}`.\n\n**NOTE:** unlike `authorize: *` you MUST specify the `with: SomePolicy` option.\nThe default authorization rule depends on the type of the field:\n\n- for lists we use `index?` (configured by `ActionPolicy::GraphQL.default_preauthorize_list_rule` parameter)\n- for _singleton_ fields we use `show?` (configured by `ActionPolicy::GraphQL.default_preauthorize_node_rule` parameter)\n\n### `authorize_field: *`\n\nIf you want to perform authorization before resolving the field value _on the base of the upper object_, you can use `authorize_field: *` option:\n\n```ruby\nfield :homes, Home, null: false, authorize_field: true\n\ndef homes\n  Home.all\nend\n```\n\nThe code above is equal to:\n\n```ruby\nfield :homes, [Home], null: false\n\ndef homes\n  authorize! object, to: :homes?\n  Home.all\nend\n```\nBy default we use `#{underscored_field_name}?` authorization rule.\n\nYou can customize the authorization options, e.g. `authorize_field: {to: :preview?, with: CustomPolicy}`.\n\n### `expose_authorization_rules`\n\nYou can add permissions/authorization exposing fields to \"tell\" clients which actions could be performed against the object or not (and why).\n\nFor example:\n\n```ruby\nclass ProfileType < ::Common::Graphql::Type\n  # Adds can_edit, can_destroy fields with\n  # AuthorizationResult type.\n\n  # NOTE: prefix \"can_\" is used by default, no need to specify it explicitly\n  expose_authorization_rules :edit?, :destroy?, prefix: \"can_\"\nend\n```\n\nThen the client could perform the following query:\n\n```gql\n{\n  post(id: $id) {\n    canEdit {\n      # (bool) true|false; not null\n      value\n      # top-level decline message (\"Not authorized\" by default); null if value is true\n      message\n      # detailed information about the decline reasons; null if value is true\n      reasons {\n        details # JSON-encoded hash of the failure reasons (e.g., {\"event\" => [:seats_available?]})\n        fullMessages # Array of human-readable reasons (e.g., [\"This event is sold out\"])\n      }\n    }\n\n    canDestroy {\n      # ...\n    }\n  }\n}\n```\n\nYou can specify a custom field name as well (only for a single rule):\n\n```ruby\nclass ProfileType < ::Common::Graphql::Type\n  # Adds can_create_post field.\n\n  expose_authorization_rules :create?, with: PostPolicy, field_name: \"can_create_post\"\nend\n```\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/palkan/action_policy-graphql.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n"
  },
  {
    "path": "Rakefile",
    "content": "# frozen_string_literal: true\n\nrequire \"bundler/gem_tasks\"\nrequire \"rspec/core/rake_task\"\n\nRSpec::Core::RakeTask.new(:spec)\n\nbegin\n  require \"rubocop/rake_task\"\n  RuboCop::RakeTask.new\n\n  RuboCop::RakeTask.new(\"rubocop:md\") do |task|\n    task.options << %w[-c .rubocop-md.yml]\n  end\nrescue LoadError\n  task(:rubocop) {}\n  task(\"rubocop:md\") {}\nend\n\ntask default: %w[rubocop rubocop:md spec]\n"
  },
  {
    "path": "action_policy-graphql.gemspec",
    "content": "# frozen_string_literal: true\n\nrequire_relative \"lib/action_policy/graphql/version\"\n\nGem::Specification.new do |spec|\n  spec.name = \"action_policy-graphql\"\n  spec.version = ActionPolicy::GraphQL::VERSION\n  spec.authors = [\"Vladimir Dementyev\"]\n  spec.email = [\"dementiev.vm@gmail.com\"]\n\n  spec.summary = \"Action Policy integration for GraphQL-Ruby\"\n  spec.description = \"Action Policy integration for GraphQL-Ruby\"\n  spec.homepage = \"https://github.com/palkan/action_policy-graphql\"\n  spec.license = \"MIT\"\n\n  spec.files = Dir.glob(\"lib/**/*\") + %w[README.md LICENSE.txt CHANGELOG.md]\n\n  spec.metadata = {\n    \"bug_tracker_uri\" => \"https://github.com/palkan/action_policy-graphql/issues\",\n    \"changelog_uri\" => \"https://github.com/palkan/action_policy-graphql/blob/master/CHANGELOG.md\",\n    \"documentation_uri\" => \"https://actionpolicy.evilmartians.io/#/graphql\",\n    \"homepage_uri\" => \"https://github.com/palkan/action_policy-graphql\",\n    \"source_code_uri\" => \"https://github.com/palkan/action_policy-graphql\"\n  }\n\n  spec.require_paths = [\"lib\"]\n\n  spec.required_ruby_version = \">= 2.5.0\"\n\n  spec.add_dependency \"action_policy\", \"~> 0.7\"\n  spec.add_dependency \"ruby-next-core\", \"~> 1.0\"\n  spec.add_dependency \"graphql\", \">= 1.9.3\"\n\n  spec.add_development_dependency \"bundler\", \">= 1.15\"\n  spec.add_development_dependency \"rake\", \">= 13.0\"\n  spec.add_development_dependency \"rspec\", \">= 3.8\"\n  spec.add_development_dependency \"i18n\"\nend\n"
  },
  {
    "path": "bin/console",
    "content": "#!/usr/bin/env ruby\n\nrequire \"bundler/setup\"\nrequire \"action_policy/graphql\"\n\n# You can add fixtures and/or initialization code here to make experimenting\n# with your gem easier. You can also use a different console, if you like.\n\n# (If you use this, don't forget to add pry to your Gemfile!)\n# require \"pry\"\n# Pry.start\n\nrequire \"irb\"\nIRB.start(__FILE__)\n"
  },
  {
    "path": "bin/setup",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\nIFS=$'\\n\\t'\nset -vx\n\nbundle install\n\n# Do any other automated setup that you need to do here\n"
  },
  {
    "path": "gemfiles/action_policy/master.gemfile",
    "content": "source \"https://rubygems.org\"\n\ngem \"action_policy\", github: \"palkan/action_policy\"\n\ngemspec path: \"../..\"\n"
  },
  {
    "path": "gemfiles/graphql/master.gemfile",
    "content": "source \"https://rubygems.org\"\n\ngem \"graphql\", github: \"rmosolgo/graphql-ruby\"\n\ngemspec path: \"../..\"\n"
  },
  {
    "path": "gemfiles/jruby.gemfile",
    "content": "source \"https://rubygems.org\"\n\ngemspec path: \"..\"\n"
  },
  {
    "path": "gemfiles/rubocop.gemfile",
    "content": "source \"https://rubygems.org\" do\n  gem \"rubocop-md\", \"~> 1.0\"\n  gem \"standard\", \"~> 1.0\"\n  gem \"ruby-next\", \">= 0.15.0\"\nend\n"
  },
  {
    "path": "lib/action_policy/graphql/authorized_field.rb",
    "content": "# frozen_string_literal: true\n\nmodule ActionPolicy\n  module GraphQL\n    # Add `authorized` option to the field\n    #\n    # Example:\n    #\n    #   class PostType < ::GraphQL::Schema::Object\n    #     field :comments, null: false, authorized: true\n    #\n    #     # or with options\n    #     field :comments, null: false, authorized: { type: :relation, with: MyPostPolicy }\n    #   end\n    module AuthorizedField\n      class Extension < ::GraphQL::Schema::FieldExtension\n        def initialize(field:, options:)\n          super(field: field, options: options&.dup || {})\n        end\n\n        def extract_option(key, &default)\n          value = options.fetch(key, &default)\n          options.delete key\n          value\n        end\n      end\n\n      class AuthorizeExtension < Extension\n        DEPRECATION_MESSAGE = \"`authorize: *` for mutation fields is deprecated.  Please use `preauthorize: *` instead.\"\n\n        class << self\n          def show_authorize_mutation_deprecation\n            return if defined?(@authorize_mutation_deprecation_shown)\n\n            if defined?(ActiveSupport::Deprecation)\n              ActiveSupport::Deprecation.warn(DEPRECATION_MESSAGE)\n            else\n              warn(DEPRECATION_MESSAGE)\n            end\n\n            @authorize_mutation_deprecation_shown = true\n          end\n        end\n\n        def apply\n          self.class.show_authorize_mutation_deprecation if field.mutation && field.mutation < ::GraphQL::Schema::Mutation\n\n          @to = extract_option(:to) { ::ActionPolicy::GraphQL.default_authorize_rule }\n          @raise = extract_option(:raise) { ::ActionPolicy::GraphQL.authorize_raise_exception }\n        end\n\n        def after_resolve(value:, context:, object:, **_rest)\n          return value if value.nil?\n\n          if @raise\n            object.authorize! value, to: @to, **options\n            value\n          else\n            object.allowed_to?(@to, value, **options) ? value : nil\n          end\n        end\n      end\n\n      class PreauthorizeExtension < Extension\n        def apply\n          if options[:with].nil?\n            raise ArgumentError, \"You must specify the policy for preauthorization: \" \\\n                                 \"`field :#{field.name}, preauthorize: {with: SomePolicy}`\"\n          end\n\n          @to = extract_option(:to) do\n            if field.type.list?\n              ::ActionPolicy::GraphQL.default_preauthorize_list_rule\n            else\n              ::ActionPolicy::GraphQL.default_preauthorize_node_rule\n            end\n          end\n\n          @raise = extract_option(:raise) do\n            if field.mutation\n              ::ActionPolicy::GraphQL.preauthorize_mutation_raise_exception\n            else\n              ::ActionPolicy::GraphQL.preauthorize_raise_exception\n            end\n          end\n        end\n\n        def resolve(context:, object:, arguments:, **_rest)\n          if @raise\n            object.authorize! field.name, to: @to, **options\n            yield object, arguments\n          elsif object.allowed_to?(@to, field.name, **options)\n            yield object, arguments\n          end\n        end\n      end\n\n      class AuthorizeFieldExtension < Extension\n        def apply\n          @to = extract_option(:to) { underscored_field_name }\n          @raise = extract_option(:raise) { ::ActionPolicy::GraphQL.authorize_raise_exception }\n        end\n\n        def resolve(context:, object:, arguments:, **_rest)\n          if @raise\n            object.authorize! object.object, to: @to, **options\n            yield object, arguments\n          elsif object.allowed_to?(@to, object.object, **options)\n            yield object, arguments\n          end\n        end\n\n        private\n\n        def underscored_field_name\n          :\"#{field.instance_variable_get(:@underscored_name)}?\"\n        end\n      end\n\n      class ScopeExtension < Extension\n        def resolve(context:, object:, arguments:, **_rest)\n          value = yield(object, arguments)\n          return value if value.nil?\n\n          object.authorized_scope(value, **options)\n        end\n      end\n\n      def initialize(*args, preauthorize: nil, authorize: nil, authorized_scope: nil, authorize_field: nil, **kwargs, &block)\n        if authorize && authorized_scope\n          raise ArgumentError, \"Only one of `authorize` and `authorized_scope` \" \\\n                               \"options could be specified. You can use `preauthorize` or `authorize_field` along with scoping\"\n        end\n\n        if (!!authorize == !!preauthorize) ? authorize : authorize_field\n          raise ArgumentError, \"Only one of `authorize`, `preauthorize` or `authorize_field` \" \\\n                               \"options could be specified.\"\n        end\n\n        extensions = (kwargs[:extensions] ||= [])\n\n        add_extension! extensions, AuthorizeExtension, authorize\n        add_extension! extensions, ScopeExtension, authorized_scope\n        add_extension! extensions, PreauthorizeExtension, preauthorize\n        add_extension! extensions, AuthorizeFieldExtension, authorize_field\n\n        super(*args, **kwargs, &block)\n      end\n\n      private\n\n      def add_extension!(extensions, extension_class, options)\n        return unless options\n\n        options = {} if options == true\n\n        extension = {extension_class => options}\n\n        if extensions.is_a?(Hash)\n          extensions.merge!(extension)\n        else\n          extensions << extension\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/action_policy/graphql/behaviour.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"action_policy/graphql/fields\"\nrequire \"action_policy/graphql/authorized_field\"\n\nmodule ActionPolicy\n  module GraphQL\n    module Behaviour\n      require \"action_policy/ext/module_namespace\"\n      using ActionPolicy::Ext::ModuleNamespace\n\n      # When used with self.authorized?\n      def self.extended(base)\n        base.extend ActionPolicy::Behaviour\n        base.extend ActionPolicy::Behaviours::ThreadMemoized\n        base.extend ActionPolicy::Behaviours::Memoized\n        base.extend ActionPolicy::Behaviours::Namespaced\n\n        # Authorization context could't be defined for the class\n        def base.build_authorization_context\n          {}\n        end\n\n        # Override authorization_namespace to use the class itself\n        def base.authorization_namespace\n          return @authorization_namespace if instance_variable_defined?(:@authorization_namespace)\n          @authorization_namespace = namespace\n        end\n      end\n\n      def self.included(base)\n        base.include ActionPolicy::Behaviour\n        base.include ActionPolicy::Behaviours::ThreadMemoized\n        base.include ActionPolicy::Behaviours::Memoized\n        base.include ActionPolicy::Behaviours::Namespaced\n\n        base.authorize :user, through: :current_user\n\n        if base.respond_to?(:field_class)\n          unless base.field_class < ActionPolicy::GraphQL::AuthorizedField\n            base.field_class.prepend(ActionPolicy::GraphQL::AuthorizedField)\n          end\n\n          unless base < ActionPolicy::GraphQL::Fields\n            base.include ActionPolicy::GraphQL::Fields\n          end\n        end\n\n        base.extend self\n      end\n\n      def current_user\n        context[:current_user]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/action_policy/graphql/fields.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"action_policy/graphql/types/authorization_result\"\n\nmodule ActionPolicy\n  using RubyNext\n\n  module GraphQL\n    # Add DSL to add policy rules as fields\n    #\n    # Example:\n    #\n    #   class PostType < ::GraphQL::Schema::Object\n    #     # Adds can_edit, can_destroy fields with\n    #     # AuthorizationResult type.\n    #\n    #     expose_authorization_rules :edit?, :destroy?, prefix: \"can_\"\n    #   end\n    #\n    # Prefix is \"can_\" by default.\n    module Fields\n      def self.included(base)\n        base.extend ClassMethods\n      end\n\n      module ClassMethods\n        def expose_authorization_rules(*rules, field_name: nil, prefix: ::ActionPolicy::GraphQL.default_authorization_field_prefix, field_options: {}, **options)\n          raise ArgumentError, \"Cannot specify field_name for multiple rules\" if rules.size > 1 && !field_name.nil?\n\n          rules.each do |rule|\n            gql_field_name = field_name || \"#{prefix}#{rule.to_s.delete(\"?\")}\"\n\n            field gql_field_name,\n              ActionPolicy::GraphQL::Types::AuthorizationResult,\n              null: false,\n              **field_options\n\n            define_method(gql_field_name) do\n              allowance_to(rule, object, **options)\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/action_policy/graphql/types/authorization_result.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"action_policy/graphql/types/failure_reasons\"\n\nmodule ActionPolicy\n  module GraphQL\n    module Types\n      class AuthorizationResult < ::GraphQL::Schema::Object\n        field :value, Boolean, null: false, description: \"Result of applying a policy rule\"\n        field :message, String, null: true, description: \"Human-readable error message\"\n        field :reasons, FailureReasons, null: true, description: \"Reasons of check failure\"\n\n        def message\n          return if object.value == true\n          object.message\n        end\n\n        def reasons\n          return if object.value == true\n          object.reasons\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/action_policy/graphql/types/failure_reasons.rb",
    "content": "# frozen_string_literal: true\n\nmodule ActionPolicy\n  module GraphQL\n    module Types\n      class FailureReasons < ::GraphQL::Schema::Object\n        field :details, String, null: false, description: \"JSON-encoded map of reasons\"\n        field :full_messages, [String], null: false, description: \"Human-readable errors\"\n\n        def details\n          object.details.to_json\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/action_policy/graphql/version.rb",
    "content": "# frozen_string_literal: true\n\nmodule ActionPolicy\n  module GraphQL\n    VERSION = \"0.6.0\"\n  end\nend\n"
  },
  {
    "path": "lib/action_policy/graphql.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"graphql\"\nrequire \"action_policy\"\n\nrequire \"action_policy/graphql/behaviour\"\n\nmodule ActionPolicy\n  module GraphQL\n    class << self\n      # Which rule to use when no specified (e.g. `authorize: true`)\n      # Defaults to `:show?`\n      attr_accessor :default_authorize_rule\n\n      # Which rule to use when no specified for preauthorization (e.g. `preauthorize: true`)\n      # of a list-like field.\n      # Defaults to `:index?`\n      attr_accessor :default_preauthorize_list_rule\n\n      # Which rule to use when no specified for preauthorization (e.g. `preauthorize: true`)\n      # of a singleton-like field.\n      # Defaults to `:show?`\n      attr_accessor :default_preauthorize_node_rule\n\n      # Whether to raise an exeption if field is not authorized\n      # or return `nil`.\n      # Defaults to `true`.\n      attr_accessor :authorize_raise_exception\n\n      # Which prefix to use for authorization fields\n      # Defaults to `\"can_\"`\n      attr_accessor :default_authorization_field_prefix\n\n      attr_writer :preauthorize_raise_exception\n\n      # Whether to raise an exception if preauthorization fails\n      # Equals to authorize_raise_exception unless explicitly set\n      def preauthorize_raise_exception\n        return authorize_raise_exception if @preauthorize_raise_exception.nil?\n        @preauthorize_raise_exception\n      end\n\n      # Whether to raise an exception if preauthorization fails\n      # Equals to preauthorize_raise_exception unless explicitly set\n      attr_writer :preauthorize_mutation_raise_exception\n\n      def preauthorize_mutation_raise_exception\n        return preauthorize_raise_exception if @preauthorize_mutation_raise_exception.nil?\n\n        @preauthorize_mutation_raise_exception\n      end\n    end\n\n    self.default_authorize_rule = :show?\n    self.default_preauthorize_list_rule = :index?\n    self.default_preauthorize_node_rule = :show?\n    self.authorize_raise_exception = true\n    self.preauthorize_raise_exception = nil\n    self.preauthorize_mutation_raise_exception = nil\n    self.default_authorization_field_prefix = \"can_\"\n  end\nend\n"
  },
  {
    "path": "lib/action_policy-graphql.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"ruby-next\"\nrequire \"action_policy/graphql\"\n"
  },
  {
    "path": "spec/action_policy/graphql/authorized_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"spec_helper\"\n\ndescribe \"field extensions\", :aggregate_failures do\n  include_context \"common:graphql\"\n\n  let(:user) { :user }\n\n  let(:schema) { Schema }\n  let(:context) { {user: user} }\n\n  context \"authorized_scope: *\" do\n    let(:posts) { [Post.new(\"private-a\"), Post.new(\"public-b\")] }\n    let(:query) do\n      %({\n          posts {\n            title\n          }\n        })\n    end\n\n    before do\n      allow(Schema).to receive(:posts) { PostList.new(posts) }\n    end\n\n    it \"has authorized scope\" do\n      expect { subject }.to have_authorized_scope(:data)\n        .with(PostPolicy)\n    end\n\n    specify \"as user\" do\n      expect(data.size).to eq 1\n      expect(data.first.fetch(\"title\")).to eq \"public-b\"\n    end\n\n    context \"as admin\" do\n      let(:user) { :admin }\n\n      specify do\n        expect(data.size).to eq 2\n        expect(data.map { |v| v.fetch(\"title\") }).to match_array(\n          [\n            \"private-a\",\n            \"public-b\"\n          ]\n        )\n      end\n    end\n\n    context \"namespaced\" do\n      let(:posts) { [Post.new(\"not mine\"), Post.new(\"story of my life\")] }\n      let(:query) do\n        %({\n            me {\n              posts {\n                title\n              }\n            }\n          })\n      end\n\n      before do\n        allow(Me).to receive(:posts) { PostList.new(posts) }\n      end\n\n      it \"has authorized scope\" do\n        expect { subject }.to have_authorized_scope(:data)\n          .with(Me::PostPolicy)\n      end\n    end\n\n    context \"connections\" do\n      let(:query) do\n        %({\n          connectedPosts(first: 1) {\n            nodes {\n              title\n            }\n          }\n        })\n      end\n\n      it \"has authorized scope\" do\n        expect { subject }.to have_authorized_scope(:data)\n          .with(PostPolicy)\n      end\n\n      context \"with connection: true\" do\n        let(:query) do\n          %({\n            anotherConnectedPosts(first: 1) {\n              totalCount\n              nodes {\n                title\n              }\n            }\n          })\n        end\n\n        it \"has authorized scope\" do\n          expect { subject }.to have_authorized_scope(:data)\n            .with(PostPolicy)\n        end\n\n        specify do\n          expect(data[\"totalCount\"]).to eq 1\n          expect(data[\"nodes\"][0][\"title\"]).to eq \"public-b\"\n        end\n      end\n    end\n  end\n\n  context \"authorize: *\" do\n    let(:post) { Post.new(\"private-a\") }\n    let(:query) do\n      %({\n          authPost {\n            title\n          }\n        })\n    end\n\n    before do\n      allow(Schema).to receive(:post) { post }\n    end\n\n    it \"is authorized\" do\n      expect { subject }.to be_authorized_to(:show?, post)\n        .with(PostPolicy)\n    end\n\n    specify \"as user\" do\n      expect { subject }.to raise_error(ActionPolicy::Unauthorized)\n    end\n\n    context \"accessible resource\" do\n      let(:post) { Post.new(\"post-c-visible\") }\n\n      specify do\n        expect(data.fetch(\"title\")).to eq \"post-c-visible\"\n      end\n    end\n\n    context \"as admin\" do\n      let(:user) { :admin }\n\n      specify do\n        expect(data.fetch(\"title\")).to eq \"private-a\"\n      end\n    end\n\n    context \"with options\" do\n      let(:query) do\n        %({\n            anotherPost {\n              title\n            }\n          })\n      end\n\n      it \"is authorized\" do\n        expect { subject }.to be_authorized_to(:preview?, post)\n          .with(AnotherPostPolicy)\n      end\n    end\n\n    context \"non-raising authorize\" do\n      let(:query) do\n        %({\n            nonRaisingPost {\n              title\n            }\n          })\n      end\n\n      it \"returns nil\" do\n        expect(data).to be_nil\n      end\n    end\n\n    context \"namespaced\" do\n      let(:query) do\n        %({\n            me {\n              bio {\n                title\n              }\n            }\n          })\n      end\n\n      before do\n        allow(Me).to receive(:post) { post }\n      end\n\n      it \"is authorized\" do\n        expect { subject }.to be_authorized_to(:show?, post)\n          .with(Me::PostPolicy)\n      end\n    end\n\n    context \"with resolver\" do\n      let(:query) do\n        %({\n            resolvedPost {\n              title\n            }\n          })\n      end\n\n      before do\n        allow(Me).to receive(:post) { post }\n      end\n\n      it \"is authorized\" do\n        expect { subject }.to be_authorized_to(:show?, post)\n          .with(PostPolicy)\n      end\n    end\n\n    context \"with mutation\" do\n      let(:query) do\n        %(mutation {\n          createPost(title: \"GQL\") {\n            post {\n              title\n            }\n          }\n        })\n      end\n\n      it \"is authorized\" do\n        expect { subject }.to be_authorized_to(:publish?, anything)\n          .with(PostPolicy)\n      end\n\n      # See spec_helper.rb for default settings\n      it \"raises if authorize_raise_exception is set to true\" do\n        expect { subject }.to raise_error(ActionPolicy::Unauthorized)\n      end\n    end\n  end\n\n  context \"preauthorize: *\" do\n    context \"collection\" do\n      let(:posts) { [Post.new(\"private-a\"), Post.new(\"public-b\")] }\n      let(:query) do\n        %({\n            secretPosts {\n              title\n            }\n          })\n      end\n\n      before do\n        allow(Schema).to receive(:posts) { PostList.new(posts) }\n      end\n\n      it \"is authorized\" do\n        expect { subject }.to be_authorized_to(:view_secret_posts?, \"secretPosts\")\n          .with(PostPolicy)\n      end\n\n      specify \"as user\" do\n        expect { subject }.to raise_error(ActionPolicy::Unauthorized)\n      end\n\n      context \"as admin\" do\n        let(:user) { :admin }\n\n        specify do\n          expect(data.size).to eq 2\n        end\n      end\n\n      context \"with default collection rule\" do\n        let(:query) do\n          %({\n              allPosts {\n                title\n              }\n            })\n        end\n\n        it \"is authorized\" do\n          expect { subject }.to be_authorized_to(:index?, \"allPosts\")\n            .with(PostPolicy)\n        end\n      end\n    end\n\n    context \"field\" do\n      let(:post) { Post.new(\"private-a\") }\n      let(:query) do\n        %({\n            secretPost {\n              title\n            }\n          })\n      end\n\n      before do\n        allow(Schema).to receive(:post) { post }\n      end\n\n      it \"doesn't resolve field if auth failed\" do\n        expect(data).to be_nil\n        expect(Schema).to_not have_received(:post)\n      end\n\n      context \"as admin\" do\n        let(:user) { :admin }\n\n        specify do\n          expect(data.fetch(\"title\")).to eq(post.title)\n          expect(Schema).to have_received(:post)\n        end\n      end\n    end\n  end\n\n  context \"authorize_field: *\" do\n    let(:post) { Post.new(\"title\") }\n    let(:query) do\n      %({\n          post {\n            secretTitle\n          }\n        })\n    end\n\n    before do\n      allow(post).to receive(:title).and_call_original\n      allow(Schema).to receive(:post) { post }\n    end\n\n    it \"is authorized with object policy\" do\n      expect { subject }.to be_authorized_to(:secret_title?, post)\n        .with(PostPolicy)\n    end\n\n    it \"doesn't resolve field if auth failed\" do\n      expect { subject }.to raise_error(ActionPolicy::Unauthorized)\n      expect(post).to_not have_received(:title)\n    end\n\n    context \"as admin\" do\n      let(:user) { :admin }\n\n      specify do\n        expect(data.fetch(\"secretTitle\")).to eq(\"Secret #{post.title}\")\n      end\n    end\n\n    context \"non-raising authorize\" do\n      let(:query) do\n        %({\n            post {\n              silentSecretTitle\n            }\n          })\n      end\n\n      it \"returns nil\" do\n        expect(data.fetch(\"silentSecretTitle\")).to be_nil\n        expect(post).to_not have_received(:title)\n      end\n\n      context \"as admin\" do\n        let(:user) { :admin }\n\n        specify do\n          expect(data.fetch(\"silentSecretTitle\")).to eq(\"Secret #{post.title}\")\n        end\n      end\n    end\n\n    context \"with options\" do\n      let(:query) do\n        %({\n            post {\n              anotherSecretTitle\n            }\n          })\n      end\n\n      it \"is authorized\" do\n        expect { subject }.to be_authorized_to(:preview?, post)\n          .with(AnotherPostPolicy)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/action_policy/graphql/behaviour_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"spec_helper\"\n\ndescribe ActionPolicy::GraphQL::Behaviour do\n  include_context \"common:graphql\"\n\n  let(:user) { :user }\n  let(:post) { Post.new(\"private\") }\n\n  let(:schema) { Schema }\n  let(:context) { {current_user: user} }\n\n  describe \".authorized? + allowed_to?\" do\n    let(:query) do\n      %({\n        authorizedPost {\n          title\n        }\n      })\n    end\n\n    before { allow(Schema).to receive(:post) { post } }\n\n    specify do\n      expect(data).to be_nil\n    end\n\n    context \"when admin\" do\n      let(:user) { :admin }\n\n      specify do\n        expect(data.fetch(\"title\")).to eq \"private\"\n      end\n    end\n\n    context \"namespaced\" do\n      let(:post) { Post.new(\"namespaced\") }\n\n      let(:query) do\n        %({\n          authorizedNamespacedPost {\n            title\n          }\n        })\n      end\n\n      specify do\n        expect(data.fetch(\"title\")).to eq \"namespaced\"\n      end\n\n      context \"mutation\" do\n        let(:query) do\n          %(mutation {\n            adminCreatePost(title: \"GQL\") {\n              post {\n                title\n              }\n            }\n          })\n        end\n\n        it \"is authorized\" do\n          expect { subject }.to be_authorized_to(:create?, Post)\n            .with(MyNamespace::PostPolicy)\n        end\n      end\n\n      context \"authorized mutation\" do\n        let(:query) do\n          %(mutation {\n            adminCreatePostAuthorized(title: \"GQL\") {\n              post {\n                title\n              }\n            }\n          })\n        end\n\n        it \"is authorized\" do\n          expect { subject }.to be_authorized_to(:create?, Post)\n            .with(MyNamespace::PostPolicy)\n        end\n      end\n\n      context \"overriden namespace\" do\n        let(:post) { Post.new(\"deleted\") }\n\n        let(:query) do\n          %(mutation {\n            deletePost(id: \"42\") {\n              deletedId\n            }\n          })\n        end\n\n        it \"is authorized\" do\n          expect { subject }.to be_authorized_to(:destroy?, post)\n            .with(MyNamespace::PostPolicy)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/action_policy/graphql/expose_authorization_rules_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"spec_helper\"\n\ndescribe \"#expose_authorization_rules\", :aggregate_failures do\n  include_context \"common:graphql\"\n\n  let(:post) { Post.new(\"private\") }\n\n  let(:schema) { Schema }\n  let(:query) do\n    %({\n        post {\n          title\n          canShow {\n            value\n            message\n            reasons {\n              details\n              fullMessages\n            }\n          }\n          canEdit {\n            value\n            message\n            reasons {\n              details\n              fullMessages\n            }\n          }\n          can_i_destroy {\n            value\n            message\n            reasons {\n              details\n              fullMessages\n            }\n          }\n        }\n      })\n  end\n\n  before { allow(Schema).to receive(:post) { post } }\n\n  context \"when failure\" do\n    specify do\n      expect(data.fetch(\"canShow\").fetch(\"value\")).to eq false\n      expect(data.fetch(\"canShow\").fetch(\"message\")).to eq \"Cannot show post\"\n\n      expect(data.fetch(\"canEdit\").fetch(\"value\")).to eq false\n      expect(data.fetch(\"canEdit\").fetch(\"message\")).to eq \"You shall not do this\"\n\n      expect(data.fetch(\"can_i_destroy\").fetch(\"value\")).to eq false\n      expect(data.fetch(\"can_i_destroy\").fetch(\"message\")).to eq \"You shall not do this\"\n    end\n\n    specify \"#reasons\" do\n      reasons = data.fetch(\"can_i_destroy\").fetch(\"reasons\")\n\n      expect(reasons.fetch(\"details\")).to eq(\n        {post: [:public?]}.to_json\n      )\n      expect(reasons.fetch(\"fullMessages\")).to eq(\n        [\n          \"Post is not public\"\n        ]\n      )\n    end\n  end\n\n  context \"when success\" do\n    let(:post) { Post.new(\"public-visible\") }\n\n    specify do\n      expect(data.fetch(\"canShow\").fetch(\"value\")).to eq true\n      expect(data.fetch(\"canShow\").fetch(\"message\")).to be_nil\n      expect(data.fetch(\"canShow\").fetch(\"reasons\")).to be_nil\n\n      expect(data.fetch(\"can_i_destroy\").fetch(\"value\")).to eq true\n      expect(data.fetch(\"can_i_destroy\").fetch(\"message\")).to be_nil\n      expect(data.fetch(\"can_i_destroy\").fetch(\"reasons\")).to be_nil\n    end\n  end\n\n  context \"namespaced\" do\n    let(:query) do\n      %({\n          me {\n            allPosts {\n              title\n              canShow {\n                value\n                message\n                reasons {\n                  details\n                  fullMessages\n                }\n              }\n            }\n          }\n        })\n    end\n\n    let(:field) { \"me->allPosts\" }\n\n    before do\n      allow(Me).to receive(:posts) { [post] }\n    end\n\n    context \"when failure\" do\n      let(:post) { Post.new(\"not mine\") }\n\n      specify do\n        expect(data.first.fetch(\"canShow\").fetch(\"value\")).to eq false\n        expect(data.first.fetch(\"canShow\").fetch(\"message\")).to eq \"Cannot show post\"\n      end\n    end\n\n    context \"when success\" do\n      let(:post) { Post.new(\"story of my life\") }\n\n      specify do\n        expect(data.first.fetch(\"canShow\").fetch(\"value\")).to eq true\n        expect(data.first.fetch(\"canShow\").fetch(\"message\")).to be_nil\n        expect(data.first.fetch(\"canShow\").fetch(\"reasons\")).to be_nil\n      end\n    end\n  end\n\n  context \"with field_name\" do\n    let(:query) do\n      %({\n        me {\n          canCreatePost {\n            value\n            message\n            reasons {\n              details\n              fullMessages\n            }\n          }\n        }\n      })\n    end\n\n    let(:field) { \"me\" }\n\n    specify do\n      expect(data.fetch(\"canCreatePost\").fetch(\"value\")).to eq true\n      expect(data.fetch(\"canCreatePost\").fetch(\"message\")).to be_nil\n      expect(data.fetch(\"canCreatePost\").fetch(\"reasons\")).to be_nil\n    end\n  end\nend\n"
  },
  {
    "path": "spec/spec_helper.rb",
    "content": "# frozen_string_literal: true\n\n$LOAD_PATH.unshift File.expand_path(\"../lib\", __dir__)\n\n# This turns off per-thread caching in action_policy\nENV[\"RACK_ENV\"] = \"test\"\n\nrequire \"i18n\"\nrequire \"action_policy-graphql\"\nbegin\n  require \"debug\" unless ENV[\"CI\"]\nrescue LoadError\nend\n\nrequire \"action_policy/rspec\"\n\nActionPolicy::GraphQL.preauthorize_raise_exception = false\nActionPolicy::GraphQL.preauthorize_mutation_raise_exception = true\n\nDir[\"#{__dir__}/support/**/*.rb\"].sort.each { |f| require f }\n\nRSpec.configure do |config|\n  config.mock_with :rspec\n\n  config.example_status_persistence_file_path = \"tmp/rspec_examples.txt\"\n  config.filter_run :focus\n  config.run_all_when_everything_filtered = true\n\n  config.order = :random\n  Kernel.srand config.seed\nend\n"
  },
  {
    "path": "spec/support/graphql_context.rb",
    "content": "# frozen_string_literal: true\n\nshared_context \"common:graphql\" do\n  let(:context) { {} }\n  let(:variables) { {} }\n  let(:field) { result.fetch(\"data\").keys.first }\n  let(:schema) { raise NotImplementedError.new(\"Specify schema under test, e.g. `let(:schema) { MySchema }`\") }\n\n  let(:data) do\n    raise \"API Query failed:\\n\\tquery: #{query}\\n\\terrors: #{result[\"errors\"]}\" if result.key?(\"errors\")\n    result.fetch(\"data\").dig(*field.split(\"->\"))\n  end\n\n  let(:errors) { result[\"errors\"]&.map { |err| err[\"message\"] } }\n\n  # for connection responses\n  let(:edges) { data.fetch(\"edges\").map { |node| node.fetch(\"node\") } }\n  let(:page_info) { data.fetch(\"pageInfo\") }\n\n  subject(:result) do\n    schema.execute(\n      query,\n      context: context,\n      variables: variables\n    )\n  end\nend\n"
  },
  {
    "path": "spec/support/schema.rb",
    "content": "# frozen_string_literal: true\n\nclass Post < Struct.new(:title); end\n\nclass PostList < Array\n  def policy_name\n    \"#{first.class}Policy\"\n  end\nend\n\nclass PostPolicy < ActionPolicy::Base\n  scope_matcher :data, PostList\n\n  scope_for :data do |data|\n    next data if admin?\n\n    data.select { |post| post.title.start_with? \"public\" }\n  end\n\n  pre_check :allow_admins\n\n  def index?\n    true\n  end\n\n  def view_secret_posts?\n    # allow_admins pre-check allows admin access\n    false\n  end\n\n  def create?\n    true\n  end\n\n  def publish?\n    false\n  end\n\n  def public?\n    record.title.start_with?(\"public\")\n  end\n\n  def show?\n    record.title.end_with?(\"visible\")\n  end\n\n  def manage?\n    check?(:public?) && allowed_to?(:show?)\n  end\n\n  def secret_title?\n    false\n  end\n\n  def silent_secret_title?\n    false\n  end\n\n  private\n\n  def allow_admins\n    allow! if admin?\n  end\n\n  def admin?\n    user == :admin\n  end\nend\n\nclass AnotherPostPolicy < PostPolicy\n  def preview?\n    public? && show?\n  end\n\n  def maybe_preview?\n    # only admins can preview (controlled by pre-check)\n    false\n  end\nend\n\nI18n.backend = I18n::Backend::Simple.new.tap do |backend|\n  backend.store_translations(\n    :en,\n    {\n      action_policy: {\n        policy: {\n          post: {\n            manage?: \"You shall not do this\",\n            edit?: \"Cannot edit post\",\n            show?: \"Cannot show post\",\n            public?: \"Post is not public\"\n          }\n        }\n      }\n    }\n  )\nend\n\nclass BaseType < ::GraphQL::Schema::Object\n  include ActionPolicy::GraphQL::Behaviour\n\n  def current_user\n    context.fetch(:user, :user)\n  end\nend\n\nclass BaseMutation < ::GraphQL::Schema::Mutation\n  include ActionPolicy::GraphQL::Behaviour\n\n  def current_user\n    context.fetch(:user, :user)\n  end\nend\n\nclass BaseResolver < ::GraphQL::Schema::Resolver\n  include ActionPolicy::GraphQL::Behaviour\n\n  def current_user\n    context.fetch(:user, :user)\n  end\nend\n\nclass PostType < BaseType\n  field :title, String, null: false\n  field :secret_title, String, null: false, authorize_field: true\n  field :silent_secret_title, String, null: true, authorize_field: {raise: false}\n  field :another_secret_title, String, null: true, authorize_field: {to: :preview?, with: AnotherPostPolicy}\n\n  expose_authorization_rules :edit?, :show?, prefix: \"can_\"\n  expose_authorization_rules :destroy?, prefix: \"can_i_\", field_options: {camelize: false}\n\n  def secret_title\n    \"Secret #{object.title}\"\n  end\n\n  alias_method :silent_secret_title, :secret_title\n  alias_method :another_secret_title, :secret_title\nend\n\nclass PostConnectionWithTotalCountType < GraphQL::Types::Relay::BaseConnection\n  edge_type(PostType.edge_type)\n\n  field :total_count, Integer, null: false\n\n  def total_count\n    object.nodes.size\n  end\nend\n\nclass AuthorizedPostType < PostType\n  def self.authorized?(object, context)\n    super &&\n      allowed_to?(\n        :show?,\n        object,\n        context: {user: context[:current_user]}\n      )\n  end\nend\n\nmodule MyNamespace\n  class PostPolicy < ::PostPolicy\n    def show?\n      true\n    end\n\n    def create?\n      admin?\n    end\n\n    def destroy?\n      true\n    end\n  end\n\n  class PostType < ::AuthorizedPostType\n    graphql_name \"MyNamespacePost\"\n  end\nend\n\n# Namespaced types\nmodule Me\n  class << self\n    attr_accessor :posts, :post\n  end\n\n  class PostPolicy < ::PostPolicy\n    scope_for :data do |data|\n      data.select { |post| post.title.match?(/\\bmy\\b/) }\n    end\n\n    def show?\n      record.title.match?(/\\bmy\\b/)\n    end\n  end\n\n  class PostType < BaseType\n    graphql_name \"MyPostType\"\n\n    field :title, String, null: false\n\n    expose_authorization_rules :show?, prefix: \"can_\"\n\n    def title\n      \"My #{object.title}\"\n    end\n  end\n\n  class RootType < BaseType\n    field :bio, PostType, null: false, authorize: true\n    field :posts, [PostType], null: false, authorized_scope: true\n    field :all_posts, [PostType], null: false\n\n    expose_authorization_rules :create?, with: PostPolicy, field_name: :can_create_post\n\n    def bio\n      Me.post\n    end\n\n    def posts\n      Me.posts\n    end\n\n    alias_method :all_posts, :posts\n  end\nend\n\nclass CreatePostMutation < BaseMutation\n  argument :title, String, required: true\n\n  field :post, PostType, null: false\n\n  def resolve(title:)\n    {post: Post.new(title: title)}\n  end\nend\n\nmodule WithMyNamespace\n  def authorization_namespace\n    ::MyNamespace\n  end\nend\n\nclass DeletePostMutation < BaseMutation\n  include WithMyNamespace\n\n  argument :id, ID, required: true\n\n  field :deleted_id, String, null: false\n\n  def resolve(id:)\n    post = Schema.post\n    authorize! post, to: :destroy?\n    {deleted_id: post.title}\n  end\nend\n\n# Namespaced mutation\nmodule Mutations\n  module MyNamespace\n    class CreatePostMutation < BaseMutation\n      graphql_name \"MyCreatePostMutation\"\n\n      argument :title, String, required: true\n\n      field :post, PostType, null: false\n\n      def resolve(title:)\n        authorize! Post, to: :create?\n        {post: Post.new(title: title)}\n      end\n\n      private\n\n      def authorization_namespace\n        ::MyNamespace\n      end\n    end\n  end\nend\n\nclass Schema < GraphQL::Schema\n  class << self\n    attr_accessor :posts, :post\n  end\n\n  class PostResolver < BaseResolver\n    type PostType, null: false\n\n    def resolve\n      Schema.post.tap do |post|\n        authorize! post, to: :show?\n      end\n    end\n  end\n\n  query(Class.new(BaseType) do\n    def self.name\n      \"Query\"\n    end\n\n    field :me, Me::RootType, null: false\n\n    field :post, PostType, null: false\n    field :authorized_post, AuthorizedPostType, null: true\n    field :authorized_namespaced_post, MyNamespace::PostType, null: true\n    field :resolved_post, resolver: PostResolver\n    field :auth_post, PostType, null: false, authorize: true\n    field :non_raising_post, PostType, null: true, authorize: {raise: false}\n    field :secret_post, PostType, null: true, preauthorize: {with: AnotherPostPolicy, to: :maybe_preview?}\n    field :another_post, PostType, null: false, authorize: {to: :preview?, with: AnotherPostPolicy}\n\n    field :posts, [PostType], null: false, authorized_scope: {type: :data, with: PostPolicy}\n\n    # Test that shared options object is not mutated\n    base_options = {with: PostPolicy, raise: true}\n    field :all_posts_new, [PostType], null: false, preauthorize: base_options\n    field :all_posts, [PostType], null: false, preauthorize: base_options\n\n    field :secret_posts, [PostType], null: false, preauthorize: {to: :view_secret_posts?, with: PostPolicy, raise: true}\n    field :connected_posts, PostType.connection_type, null: false, authorized_scope: true\n    field :another_connected_posts, PostConnectionWithTotalCountType, null: false, authorized_scope: true, connection: true\n\n    def me\n      {}\n    end\n\n    def post\n      Schema.post\n    end\n\n    alias_method :authorized_post, :post\n    alias_method :authorized_namespaced_post, :post\n    alias_method :auth_post, :post\n    alias_method :another_post, :post\n    alias_method :non_raising_post, :post\n    alias_method :secret_post, :post\n\n    def posts\n      Schema.posts\n    end\n\n    alias_method :secret_posts, :posts\n    alias_method :all_posts, :posts\n    alias_method :connected_posts, :posts\n    alias_method :another_connected_posts, :posts\n  end)\n\n  mutation(Class.new(BaseType) do\n    def self.name\n      \"Mutation\"\n    end\n\n    field :create_post, mutation: CreatePostMutation, null: false, preauthorize: {with: PostPolicy, to: :publish?}\n    field :delete_post, mutation: DeletePostMutation, null: false\n    field :admin_create_post, mutation: Mutations::MyNamespace::CreatePostMutation, null: false\n    field :admin_create_post_authorized, mutation: Mutations::MyNamespace::CreatePostMutation, null: false, authorize: {to: :create?}\n  end)\nend\n"
  }
]